@@ -118,6 +118,12 @@ def test_pytest_xdist_itr_skips_tests_at_test_level_by_pytest_addopts_env_var(se
118
118
return_value=itr_settings
119
119
).start()
120
120
121
+ # Mock fetch_skippable_items to return our test data
122
+ mock.patch(
123
+ "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_skippable_items",
124
+ return_value=itr_data
125
+ ).start()
126
+
121
127
# Set ITR data when CIVisibility is enabled
122
128
import ddtrace.internal.ci_visibility.recorder
123
129
CIVisibility = ddtrace.internal.ci_visibility.recorder.CIVisibility
@@ -173,6 +179,129 @@ def patched_enable(cls, *args, **kwargs):
173
179
# Verify number of skipped tests in session
174
180
assert session_span .get_metric ("test.itr.tests_skipping.count" ) == 2
175
181
182
+ def test_xdist_suite_mode_skipped_suites (self ):
183
+ """Test that suite-level ITR skipping works correctly in xdist and counts suites, not individual tests."""
184
+
185
+ itr_skipping_sitecustomize = """
186
+ # sitecustomize.py - ITR setup for xdist worker nodes
187
+ from unittest import mock
188
+
189
+ # Import required modules
190
+ from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings
191
+ from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings
192
+ from ddtrace.internal.ci_visibility._api_client import TestManagementSettings
193
+ from ddtrace.internal.ci_visibility._api_client import ITRData
194
+ from ddtrace.ext.test_visibility._test_visibility_base import TestSuiteId, TestModuleId, TestId
195
+
196
+ # Create ITR settings and data
197
+ itr_settings = TestVisibilityAPISettings(
198
+ coverage_enabled=False, skipping_enabled=True, require_git=False, itr_enabled=True,
199
+ flaky_test_retries_enabled=False, known_tests_enabled=False,
200
+ early_flake_detection=EarlyFlakeDetectionSettings(), test_management=TestManagementSettings()
201
+ )
202
+
203
+ # Create skippable suites for suite-level skipping
204
+ skippable_suites = {
205
+ TestSuiteId(TestModuleId(""), "test_scope1.py"),
206
+ TestSuiteId(TestModuleId(""), "test_scope2.py")
207
+ }
208
+ itr_data = ITRData(correlation_id="12345678-1234-1234-1234-123456789012", skippable_items=skippable_suites)
209
+
210
+ # Mock API calls to return our settings
211
+ mock.patch(
212
+ "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_settings",
213
+ return_value=itr_settings
214
+ ).start()
215
+
216
+ mock.patch(
217
+ "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features",
218
+ return_value=itr_settings
219
+ ).start()
220
+
221
+ # Mock fetch_skippable_items to return our test data
222
+ mock.patch(
223
+ "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_skippable_items",
224
+ return_value=itr_data
225
+ ).start()
226
+
227
+ # Set ITR data when CIVisibility is enabled
228
+ import ddtrace.internal.ci_visibility.recorder
229
+ CIVisibility = ddtrace.internal.ci_visibility.recorder.CIVisibility
230
+ original_enable = CIVisibility.enable
231
+
232
+ def patched_enable(cls, *args, **kwargs):
233
+ result = original_enable(*args, **kwargs)
234
+ if cls._instance:
235
+ cls._instance._itr_data = itr_data
236
+ return result
237
+
238
+ CIVisibility.enable = classmethod(patched_enable)
239
+ """
240
+
241
+ # Create test files
242
+ self .testdir .makepyfile (sitecustomize = itr_skipping_sitecustomize )
243
+ self .testdir .makepyfile (
244
+ test_scope1 = """
245
+ import pytest
246
+
247
+ class TestScope1:
248
+ def test_scope1_method1(self):
249
+ assert True
250
+
251
+ def test_scope1_method2(self):
252
+ assert True
253
+ """ ,
254
+ test_scope2 = """
255
+ import pytest
256
+
257
+ class TestScope2:
258
+ def test_scope2_method1(self):
259
+ assert True
260
+ """ ,
261
+ )
262
+ self .testdir .chdir ()
263
+
264
+ itr_settings = TestVisibilityAPISettings (
265
+ coverage_enabled = False ,
266
+ skipping_enabled = True ,
267
+ require_git = False ,
268
+ itr_enabled = True ,
269
+ flaky_test_retries_enabled = False ,
270
+ known_tests_enabled = False ,
271
+ early_flake_detection = EarlyFlakeDetectionSettings (),
272
+ test_management = TestManagementSettings (),
273
+ )
274
+
275
+ with mock .patch (
276
+ "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features" , return_value = itr_settings
277
+ ), mock .patch (
278
+ "ddtrace.internal.ci_visibility.recorder.CIVisibility.test_skipping_enabled" ,
279
+ return_value = True ,
280
+ ):
281
+ # Run with xdist using loadscope mode (suite-level skipping)
282
+ rec = self .inline_run (
283
+ "--ddtrace" ,
284
+ "-n" ,
285
+ "2" ,
286
+ "--dist=loadscope" ,
287
+ "-s" ,
288
+ "-vvv" ,
289
+ extra_env = {
290
+ "DD_CIVISIBILITY_AGENTLESS_ENABLED" : "1" ,
291
+ "DD_API_KEY" : "foobar.baz" ,
292
+ "DD_INSTRUMENTATION_TELEMETRY_ENABLED" : "0" ,
293
+ },
294
+ )
295
+ assert rec .ret == 0 # All tests skipped, so exit code is 0
296
+
297
+ # Assert on session span metrics - key assertion for suite-level skipping
298
+ spans = self .pop_spans ()
299
+ session_span = [span for span in spans if span .get_tag ("type" ) == "test_session_end" ][0 ]
300
+ assert session_span .get_tag ("test.itr.tests_skipping.enabled" ) == "true"
301
+ assert session_span .get_tag ("test.itr.tests_skipping.type" ) == "suite" # loadscope uses suite-level skipping
302
+ # Verify number of skipped SUITES in session (should be 2 suites, not 3 tests)
303
+ assert session_span .get_metric ("test.itr.tests_skipping.count" ) == 2
304
+
176
305
def test_pytest_xdist_itr_skips_tests_at_test_level_without_loadscope (self ):
177
306
"""Test that ITR tags are correctly aggregated from xdist workers."""
178
307
# Create a simplified sitecustomize with just the essential ITR setup
@@ -210,6 +339,12 @@ def test_pytest_xdist_itr_skips_tests_at_test_level_without_loadscope(self):
210
339
return_value=itr_settings
211
340
).start()
212
341
342
+ # Mock fetch_skippable_items to return our test data
343
+ mock.patch(
344
+ "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_skippable_items",
345
+ return_value=itr_data
346
+ ).start()
347
+
213
348
# Set ITR data when CIVisibility is enabled
214
349
import ddtrace.internal.ci_visibility.recorder
215
350
CIVisibility = ddtrace.internal.ci_visibility.recorder.CIVisibility
@@ -300,6 +435,12 @@ def test_pytest_xdist_itr_skips_tests_at_suite_level_with_loadscope(self):
300
435
return_value=itr_settings
301
436
).start()
302
437
438
+ # Mock fetch_skippable_items to return our test data
439
+ mock.patch(
440
+ "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_skippable_items",
441
+ return_value=itr_data
442
+ ).start()
443
+
303
444
# Set ITR data when CIVisibility is enabled
304
445
import ddtrace.internal.ci_visibility.recorder
305
446
CIVisibility = ddtrace.internal.ci_visibility.recorder.CIVisibility
@@ -460,16 +601,16 @@ def test_handle_itr_should_skip_counts_skipped_tests_in_worker(self):
460
601
test_id = TestId (TestSuiteId (TestModuleId ("test_module" ), "test_suite" ), "test_name" )
461
602
462
603
mock_service = mock .MagicMock ()
463
- mock_service ._suite_skipping_mode = True
604
+ mock_service ._suite_skipping_mode = False # Use test-level skipping for worker count tests
464
605
465
606
with mock .patch (
466
607
"ddtrace.internal.test_visibility.api.InternalTestSession.is_test_skipping_enabled" , return_value = True
467
608
), mock .patch (
468
- "ddtrace.internal.test_visibility.api.InternalTestSuite .is_itr_unskippable" , return_value = False
609
+ "ddtrace.internal.test_visibility.api.InternalTest .is_itr_unskippable" , return_value = False
469
610
), mock .patch (
470
611
"ddtrace.internal.test_visibility.api.InternalTest.is_attempt_to_fix" , return_value = False
471
612
), mock .patch (
472
- "ddtrace.internal.test_visibility.api.InternalTestSuite .is_itr_skippable" , return_value = True
613
+ "ddtrace.internal.test_visibility.api.InternalTest .is_itr_skippable" , return_value = True
473
614
), mock .patch (
474
615
"ddtrace.internal.test_visibility.api.InternalTest.mark_itr_skipped"
475
616
), mock .patch (
@@ -491,16 +632,16 @@ def test_handle_itr_should_skip_increments_existing_worker_count(self):
491
632
test_id = TestId (TestSuiteId (TestModuleId ("test_module" ), "test_suite" ), "test_name" )
492
633
493
634
mock_service = mock .MagicMock ()
494
- mock_service ._suite_skipping_mode = True
635
+ mock_service ._suite_skipping_mode = False # Use test-level skipping for worker count tests
495
636
496
637
with mock .patch (
497
638
"ddtrace.internal.test_visibility.api.InternalTestSession.is_test_skipping_enabled" , return_value = True
498
639
), mock .patch (
499
- "ddtrace.internal.test_visibility.api.InternalTestSuite .is_itr_unskippable" , return_value = False
640
+ "ddtrace.internal.test_visibility.api.InternalTest .is_itr_unskippable" , return_value = False
500
641
), mock .patch (
501
642
"ddtrace.internal.test_visibility.api.InternalTest.is_attempt_to_fix" , return_value = False
502
643
), mock .patch (
503
- "ddtrace.internal.test_visibility.api.InternalTestSuite .is_itr_skippable" , return_value = True
644
+ "ddtrace.internal.test_visibility.api.InternalTest .is_itr_skippable" , return_value = True
504
645
), mock .patch (
505
646
"ddtrace.internal.test_visibility.api.InternalTest.mark_itr_skipped"
506
647
), mock .patch (
@@ -1690,6 +1831,11 @@ def test_func2():
1690
1831
1691
1832
# The ITR skipping type should be suite due to explicit env var override
1692
1833
assert session_span .get_tag ("test.itr.tests_skipping.type" ) == "suite"
1834
+ expected_suite_count = 0 # No suites skipped
1835
+ actual_count = session_span .get_metric ("test.itr.tests_skipping.count" )
1836
+ assert (
1837
+ actual_count == expected_suite_count
1838
+ ), f"Expected { expected_suite_count } suites skipped but got { actual_count } "
1693
1839
1694
1840
def test_explicit_env_var_overrides_xdist_test_mode (self ):
1695
1841
"""Test that explicit _DD_CIVISIBILITY_ITR_SUITE_MODE=False overrides xdist suite-level detection."""
@@ -1742,6 +1888,12 @@ def patched_enable(cls, *args, **kwargs):
1742
1888
return_value=itr_settings
1743
1889
).start()
1744
1890
1891
+ # Mock fetch_skippable_items to return our test data
1892
+ mock.patch(
1893
+ "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_skippable_items",
1894
+ return_value=itr_data
1895
+ ).start()
1896
+
1745
1897
CIVisibility.enable = classmethod(patched_enable)
1746
1898
1747
1899
"""
0 commit comments