1
1
import pathlib
2
+ import shlex
2
3
import tempfile
3
4
from unittest .mock import patch
4
5
5
6
import pytest
6
7
8
+ from ddtrace .settings ._inferred_base_service import _module_exists
7
9
from ddtrace .settings ._inferred_base_service import detect_service
8
10
9
11
@@ -21,7 +23,9 @@ def mock_file_system():
21
23
(base_path / "venv" / "bin" / "gunicorn" ).mkdir (parents = True )
22
24
23
25
# add a test dir
24
- (base_path / "tests" / "contrib" / "aiohttp" ).mkdir (parents = True )
26
+ (base_path / "tests" / "contrib" / "aiohttp" / "app" ).mkdir (parents = True )
27
+
28
+ # other cases
25
29
26
30
(base_path / "modules" / "m1" / "first" / "nice" / "package" ).mkdir (parents = True )
27
31
(base_path / "modules" / "m2" ).mkdir (parents = True )
@@ -35,15 +39,19 @@ def mock_file_system():
35
39
(base_path / "venv" / "bin" / "python3.11" / "gunicorn" / "__init__.py" ).mkdir (parents = True )
36
40
(base_path / "venv" / "bin" / "gunicorn" / "__init__.py" ).touch ()
37
41
42
+ # Create `__init__.py` files that indicate packages
38
43
(base_path / "modules" / "m1" / "first" / "nice" / "package" / "__init__.py" ).touch ()
39
44
(base_path / "modules" / "m1" / "first" / "nice" / "__init__.py" ).touch ()
40
- (base_path / "modules" / "m1" / "first" / "nice" / "something .py" ).touch ()
45
+ (base_path / "modules" / "m1" / "first" / "nice" / "app .py" ).touch ()
41
46
(base_path / "modules" / "m1" / "first" / "__init__.py" ).touch ()
42
47
(base_path / "modules" / "m1" / "__init__.py" ).touch ()
48
+ (base_path / "modules" / "m2" / "__init__.py" ).touch ()
43
49
(base_path / "apps" / "app1" / "__main__.py" ).touch ()
44
50
(base_path / "apps" / "app2" / "cmd" / "run.py" ).touch ()
45
51
(base_path / "apps" / "app2" / "setup.py" ).touch ()
46
52
53
+ (base_path / "tests" / "contrib" / "aiohttp" / "app" / "web.py" ).touch ()
54
+ (base_path / "tests" / "contrib" / "aiohttp" / "app" / "__init__.py" ).touch ()
47
55
(base_path / "tests" / "contrib" / "aiohttp" / "test.py" ).touch ()
48
56
(base_path / "tests" / "contrib" / "aiohttp" / "__init__.py" ).touch ()
49
57
(base_path / "tests" / "contrib" / "__init__.py" ).touch ()
@@ -59,9 +67,13 @@ def mock_file_system():
59
67
@pytest .mark .parametrize (
60
68
"cmd,expected" ,
61
69
[
70
+ ("python tests/contrib/aiohttp/app/web.py" , "tests.contrib.aiohttp.app" ),
71
+ ("python tests/contrib/aiohttp" , "tests.contrib.aiohttp" ),
72
+ ("python tests/contrib" , "tests.contrib" ),
73
+ ("python tests" , "tests" ),
62
74
("python modules/m1/first/nice/package" , "m1.first.nice.package" ),
63
75
("python modules/m1/first/nice" , "m1.first.nice" ),
64
- ("python modules/m1/first/nice/something .py" , "m1.first.nice" ),
76
+ ("python modules/m1/first/nice/app .py" , "m1.first.nice" ),
65
77
("python modules/m1/first" , "m1.first" ),
66
78
("python modules/m2" , "m2" ),
67
79
("python apps/app1" , "app1" ),
@@ -75,17 +87,98 @@ def mock_file_system():
75
87
("venv/bin/python3.11/ddtrace-run python apps/app2/setup.py" , "app2" ),
76
88
("ddtrace-run python apps/app2/setup.py" , "app2" ),
77
89
("python3.12 apps/app2/cmd/run.py" , "app2" ),
78
- ("python -m m1.first.nice.package" , "m1.first.nice.package" ),
90
+ ("python -m tests.contrib.aiohttp.app.web" , "tests.contrib.aiohttp.app.web" ),
91
+ ("python -m tests.contrib.aiohttp.app" , "tests.contrib.aiohttp.app" ),
92
+ ("python -m tests.contrib.aiohttp" , "tests.contrib.aiohttp" ),
93
+ ("python -m tests.contrib" , "tests.contrib" ),
94
+ ("python -m tests" , "tests" ),
79
95
("python -m http.server 8000" , "http.server" ),
96
+ ("python --some-flag apps/app1" , "app1" ),
80
97
# pytest
81
98
("pytest tests/contrib/aiohttp" , "tests.contrib.aiohttp" ),
82
99
("pytest --ddtrace tests/contrib/aiohttp" , "tests.contrib.aiohttp" ),
100
+ ("pytest --no-cov tests/contrib/aiohttp" , "tests.contrib.aiohttp" ),
83
101
],
84
102
)
85
- def test_python_detector (cmd , expected , mock_file_system ):
103
+ def test_python_detector_service_name_should_exist_file_exists (cmd , expected , mock_file_system ):
86
104
# Mock the current working directory to the test_modules path
87
105
with patch ("os.getcwd" , return_value = str (mock_file_system )):
88
- cmd_parts = cmd .split (" " )
106
+ cmd_parts = shlex .split (cmd )
89
107
detected_name = detect_service (cmd_parts )
90
108
91
109
assert detected_name == expected , f"Test failed for command: [{ cmd } ]"
110
+
111
+
112
+ @pytest .mark .parametrize (
113
+ "cmd,expected" ,
114
+ [
115
+ # Commands that should not produce a service name
116
+ ("" , None ), # Empty command
117
+ ("python non_existing_file.py" , None ), # Non-existing Python script
118
+ ("python invalid_script.py" , None ), # Invalid script that isn't found
119
+ ("gunicorn app:app" , None ), # Non-Python command
120
+ ("ls -la" , None ), # Non-Python random command
121
+ ("cat README.md" , None ), # Another random command
122
+ ("python -m non_existing_module" , None ), # Non-existing Python module
123
+ ("python -c 'print([])'" , None ), # Python inline code not producing a service
124
+ ("python -m -c 'print([]])'" , None ), # Inline code with module flag
125
+ ("echo 'Hello, World!'" , None ), # Not a Python service
126
+ ("python3.11 /path/to/some/non_python_file.txt" , None ), # Non-Python file
127
+ ("/usr/bin/ls" , None ), # Another system command
128
+ ("some_executable --ddtrace hello" , None ),
129
+ ("python version" , None ),
130
+ ("python -m -v --hello=maam" , None ),
131
+ # error produced from a test, ensure an arg that is very long doesn't break stuff
132
+ (
133
+ "ddtrace-run pytest -k 'not test_reloader and not test_reload_listeners and not "
134
+ + "test_no_exceptions_when_cancel_pending_request and not test_add_signal and not "
135
+ + "test_ode_removes and not test_skip_touchup and not test_dispatch_signal_triggers"
136
+ + " and not test_keep_alive_connection_context and not test_redirect_with_params and"
137
+ + " not test_keep_alive_client_timeout and not test_logger_vhosts and not test_ssl_in_multiprocess_mode'" ,
138
+ None ,
139
+ ),
140
+ ],
141
+ )
142
+ def test_no_service_name (cmd , expected , mock_file_system ):
143
+ with patch ("os.getcwd" , return_value = str (mock_file_system )):
144
+ cmd_parts = shlex .split (cmd )
145
+ detected_name = detect_service (cmd_parts )
146
+
147
+ assert detected_name == expected , f"Test failed for command: [{ cmd } ]"
148
+
149
+
150
+ @pytest .mark .parametrize (
151
+ "cmd,expected" ,
152
+ [
153
+ # Command that is too long
154
+ ("python " + " " .join (["arg" ] * 1000 ), None ), # Excessively long command
155
+ # Path with special characters
156
+ (r"python /path/with/special/characters/!@#$%^&*()_/some_script.py" , None ), # Special characters
157
+ # Path too deep
158
+ (f"python { '/' .join (['deep' * 50 ])} /script.py" , None ), # Excessively deep path
159
+ ],
160
+ )
161
+ def test_chaos (cmd , expected , mock_file_system ):
162
+ with patch ("os.getcwd" , return_value = str (mock_file_system )):
163
+ cmd_parts = shlex .split (cmd )
164
+ detected_name = detect_service (cmd_parts )
165
+
166
+ assert detected_name == expected , f"Chaos test failed for command: [{ cmd } ]"
167
+
168
+
169
+ @pytest .mark .parametrize (
170
+ "module_name,should_exist" ,
171
+ [
172
+ ("tests.contrib.aiohttp.app.web" , True ),
173
+ ("tests.contrib.aiohttp.app" , True ),
174
+ ("tests.contrib.aiohttp" , True ),
175
+ ("tests.contrib" , True ),
176
+ ("tests" , True ),
177
+ ("tests.releasenotes" , False ),
178
+ ("non_existing_module" , False ),
179
+ ],
180
+ )
181
+ def test_module_exists (module_name , should_exist ):
182
+ exists = _module_exists (module_name )
183
+
184
+ assert exists == should_exist , f"Module { module_name } existence check failed."
0 commit comments