|
| 1 | +from unittest.mock import patch |
| 2 | + |
| 3 | +import pytest |
| 4 | + |
| 5 | +from ddtrace.internal import process_tags |
| 6 | +from ddtrace.internal.constants import PROCESS_TAGS |
| 7 | +from ddtrace.internal.process_tags import ENTRYPOINT_BASEDIR_TAG |
| 8 | +from ddtrace.internal.process_tags import ENTRYPOINT_NAME_TAG |
| 9 | +from ddtrace.internal.process_tags import ENTRYPOINT_TYPE_TAG |
| 10 | +from ddtrace.internal.process_tags import ENTRYPOINT_WORKDIR_TAG |
| 11 | +from ddtrace.internal.process_tags import normalize_tag_value |
| 12 | +from ddtrace.internal.settings._config import config |
| 13 | +from tests.subprocesstest import run_in_subprocess |
| 14 | +from tests.utils import TracerTestCase |
| 15 | +from tests.utils import process_tag_reload |
| 16 | + |
| 17 | + |
| 18 | +TEST_SCRIPT_PATH = "/path/to/test_script.py" |
| 19 | +TEST_WORKDIR_PATH = "/path/to/workdir" |
| 20 | + |
| 21 | + |
| 22 | +@pytest.mark.parametrize( |
| 23 | + "input_tag,expected", |
| 24 | + [ |
| 25 | + ("#test_starting_hash", "test_starting_hash"), |
| 26 | + ("TestCAPSandSuch", "testcapsandsuch"), |
| 27 | + ("Test Conversion Of Weird !@#$%^&**() Characters", "test_conversion_of_weird_characters"), |
| 28 | + ("$#weird_starting", "weird_starting"), |
| 29 | + ("allowed:c0l0ns", "allowed:c0l0ns"), |
| 30 | + ("1love", "1love"), |
| 31 | + ("/love2", "/love2"), |
| 32 | + ("ünicöde", "ünicöde"), |
| 33 | + ("ünicöde:metäl", "ünicöde:metäl"), |
| 34 | + ("Data🐨dog🐶 繋がっ⛰てて", "data_dog_繋がっ_てて"), |
| 35 | + (" spaces ", "spaces"), |
| 36 | + (" #hashtag!@#spaces #__<># ", "hashtag_spaces"), |
| 37 | + (":testing", ":testing"), |
| 38 | + ("_foo", "foo"), |
| 39 | + (":::test", ":::test"), |
| 40 | + ("contiguous_____underscores", "contiguous_underscores"), |
| 41 | + ("foo_", "foo"), |
| 42 | + ("\u017fodd_\u017fcase\u017f", "\u017fodd_\u017fcase\u017f"), |
| 43 | + ("", ""), |
| 44 | + (" ", ""), |
| 45 | + ("ok", "ok"), |
| 46 | + ("™Ö™Ö™™Ö™", "ö_ö_ö"), |
| 47 | + ("AlsO:ök", "also:ök"), |
| 48 | + (":still_ok", ":still_ok"), |
| 49 | + ("___trim", "trim"), |
| 50 | + ("12.:trim@", "12.:trim"), |
| 51 | + ("12.:trim@@", "12.:trim"), |
| 52 | + ("fun:ky__tag/1", "fun:ky_tag/1"), |
| 53 | + ("fun:ky@tag/2", "fun:ky_tag/2"), |
| 54 | + ("fun:ky@@@tag/3", "fun:ky_tag/3"), |
| 55 | + ("tag:1/2.3", "tag:1/2.3"), |
| 56 | + ("---fun:k####y_ta@#g/1_@@#", "---fun:k_y_ta_g/1"), |
| 57 | + ("AlsO:œ#@ö))œk", "also:œ_ö_œk"), |
| 58 | + ("test\x99\x8faaa", "test_aaa"), |
| 59 | + ("test\x99\x8f", "test"), |
| 60 | + ("a" * 888, "a" * 200), |
| 61 | + ("a" + "🐶" * 799 + "b", "a"), |
| 62 | + ("a" + "\ufffd", "a"), |
| 63 | + ("a" + "\ufffd" + "\ufffd", "a"), |
| 64 | + ("a" + "\ufffd" + "\ufffd" + "b", "a_b"), |
| 65 | + ( |
| 66 | + "A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
| 67 | + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
| 68 | + " 000000000000", |
| 69 | + "a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
| 70 | + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
| 71 | + "_0", |
| 72 | + ), |
| 73 | + ], |
| 74 | +) |
| 75 | +def test_normalize_tag(input_tag, expected): |
| 76 | + assert normalize_tag_value(input_tag) == expected |
| 77 | + |
| 78 | + |
| 79 | +class TestProcessTags(TracerTestCase): |
| 80 | + def setUp(self): |
| 81 | + super(TestProcessTags, self).setUp() |
| 82 | + self._original_process_tags_enabled = config._process_tags_enabled |
| 83 | + self._original_process_tags = process_tags.process_tags |
| 84 | + |
| 85 | + def tearDown(self): |
| 86 | + config._process_tags_enabled = self._original_process_tags_enabled |
| 87 | + process_tags.process_tags = self._original_process_tags |
| 88 | + super().tearDown() |
| 89 | + |
| 90 | + @pytest.mark.snapshot |
| 91 | + def test_process_tags_deactivated(self): |
| 92 | + config._process_tags_enabled = False |
| 93 | + process_tag_reload() |
| 94 | + |
| 95 | + with self.tracer.trace("test"): |
| 96 | + pass |
| 97 | + |
| 98 | + @pytest.mark.snapshot |
| 99 | + def test_process_tags_activated(self): |
| 100 | + with patch("sys.argv", [TEST_SCRIPT_PATH]), patch("os.getcwd", return_value=TEST_WORKDIR_PATH): |
| 101 | + config._process_tags_enabled = True |
| 102 | + process_tag_reload() |
| 103 | + |
| 104 | + with self.tracer.trace("parent"): |
| 105 | + with self.tracer.trace("child"): |
| 106 | + pass |
| 107 | + |
| 108 | + @pytest.mark.snapshot |
| 109 | + def test_process_tags_edge_case(self): |
| 110 | + with patch("sys.argv", ["/test_script"]), patch("os.getcwd", return_value=TEST_WORKDIR_PATH): |
| 111 | + config._process_tags_enabled = True |
| 112 | + process_tag_reload() |
| 113 | + |
| 114 | + with self.tracer.trace("span"): |
| 115 | + pass |
| 116 | + |
| 117 | + @pytest.mark.snapshot |
| 118 | + def test_process_tags_error(self): |
| 119 | + with patch("sys.argv", []), patch("os.getcwd", return_value=TEST_WORKDIR_PATH): |
| 120 | + config._process_tags_enabled = True |
| 121 | + |
| 122 | + with self.override_global_config(dict(_telemetry_enabled=False)): |
| 123 | + with patch("ddtrace.internal.process_tags.log") as mock_log: |
| 124 | + process_tag_reload() |
| 125 | + |
| 126 | + with self.tracer.trace("span"): |
| 127 | + pass |
| 128 | + |
| 129 | + # Check if debug log was called |
| 130 | + mock_log.debug.assert_called_once() |
| 131 | + call_args = mock_log.debug.call_args[0] |
| 132 | + assert "failed to get process_tags" in call_args[0], ( |
| 133 | + f"Expected error message not found. Got: {call_args[0]}" |
| 134 | + ) |
| 135 | + |
| 136 | + @pytest.mark.snapshot |
| 137 | + @run_in_subprocess(env_overrides=dict(DD_TRACE_PARTIAL_FLUSH_ENABLED="true", DD_TRACE_PARTIAL_FLUSH_MIN_SPANS="2")) |
| 138 | + def test_process_tags_partial_flush(self): |
| 139 | + with patch("sys.argv", [TEST_SCRIPT_PATH]), patch("os.getcwd", return_value=TEST_WORKDIR_PATH): |
| 140 | + config._process_tags_enabled = True |
| 141 | + process_tag_reload() |
| 142 | + |
| 143 | + with self.override_global_config(dict(_partial_flush_enabled=True, _partial_flush_min_spans=2)): |
| 144 | + with self.tracer.trace("parent"): |
| 145 | + with self.tracer.trace("child1"): |
| 146 | + pass |
| 147 | + with self.tracer.trace("child2"): |
| 148 | + pass |
| 149 | + |
| 150 | + @run_in_subprocess(env_overrides=dict(DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED="True")) |
| 151 | + def test_process_tags_activated_with_env(self): |
| 152 | + with self.tracer.trace("test"): |
| 153 | + pass |
| 154 | + |
| 155 | + span = self.get_spans()[0] |
| 156 | + |
| 157 | + assert span is not None |
| 158 | + assert PROCESS_TAGS in span._meta |
| 159 | + |
| 160 | + process_tags = span._meta[PROCESS_TAGS] |
| 161 | + assert ENTRYPOINT_BASEDIR_TAG in process_tags |
| 162 | + assert ENTRYPOINT_NAME_TAG in process_tags |
| 163 | + assert ENTRYPOINT_TYPE_TAG in process_tags |
| 164 | + assert ENTRYPOINT_WORKDIR_TAG in process_tags |
0 commit comments