chore(testing): add test discovery mode for pytest#18559
Conversation
Add DD_CI_TEST_DISCOVERY_MODE_ENABLED support to the ddtrace/testing pytest plugin, matching the format produced by datadog-ci-rb. When enabled, pytest collects tests and writes them as JSON Lines to DD_CI_TEST_DISCOVERY_OUTPUT_PATH (default: ddtest/test_discovery/tests.json), then exits without running any test. Each line contains name, suite, module, parameters, and suiteSourceFile fields. Tests decorated with pytest.mark.skip or pytest.mark.skipif with a truthy non-string condition are excluded from the output. CI Visibility initialisation is suppressed when discovery mode is active. Also moves _get_test_parameters_json and _encode_test_parameter from plugin.py to utils.py so they can be imported by _discovery.py without a circular dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
…ery module Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codeowners resolved as |
Use DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED and DD_TEST_OPTIMIZATION_DISCOVERY_FILE (ddtest's canonical names) instead of the Ruby-aligned DD_CI_TEST_DISCOVERY_MODE_ENABLED / DD_CI_TEST_DISCOVERY_OUTPUT_PATH. ddtest owns the shared env var contract; individual language plugins should conform to it. Also update the default output path to match ddtest's .testoptimization/tests-discovery/tests.json. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…output The module field must match what the plugin reports at run time so the backend can correlate discovery results with test runs via FQN (module.suite.name). Using the static string "pytest" as module would collapse every test across all repos into the same namespace. nodeid_to_names already extracts the correct value (e.g. "tests.unit" for tests/unit/test_foo.py); we just needed to stop discarding it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y mode Register TestOptHooks specs during discovery so item_to_test_ref can call the same custom hook chain used at run time. This means name/suite/ module resolution stays in sync with ITR matching if the logic ever changes, rather than being a separate parallel implementation. pytest-bdd is intentionally not wired up: BddTestOptPlugin is not registered in discovery mode, so BDD tests fall back to nodeid-based names (same as before). A TODO marks where to add that support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED and DD_TEST_OPTIMIZATION_DISCOVERY_FILE to supported-configurations.json and regenerate _supported_configurations.py so the CI check passes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…est plugin - Sort discovery imports alphabetically into the ddtrace.testing block - Drop the pytest_collection_finish module-level re-export (caused F811 clash with BddTestOptPlugin.pytest_collection_finish); register the _discovery module directly as a plugin in pytest_configure instead - Fix import order and remove spurious f-string prefix in test_pytest_discovery.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pytester.monkeypatch was added in pytest 6.2; older envs (py3.9 CI) don't have it. Accept monkeypatch as a separate fixture parameter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 12ec7caea9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… skip in discovery @pytest.mark.skipif with no condition argument yields condition=None in _is_item_skipped. Previously this fell through to continue, so such tests were included in discovery output even though pytest treats missing conditions as unconditional skip (matching plugin.py's _get_skipif_condition). Align the None branch with plugin.py: condition is None → return True. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s_item_skipped pytest >= 7 raises a collection error for @pytest.mark.skipif with no condition argument, so such items never reach _is_item_skipped. Treating condition=None as an unconditional skip would diverge from pytest's own behaviour and handle a case that can't occur in practice. Restore the original: condition=None conservatively includes the test, same as a string condition that cannot be evaluated at collection time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y included Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…arg) pytest 9 accepts @pytest.mark.skipif(reason=...) without a condition and skips the test at runtime, but condition is still None at collection time. The conservative-include behaviour is correct; update the comment to reflect the actual pytest 9 behaviour rather than claiming it can't arise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED/FILE constants to ddtrace.testing.internal.constants alongside other DD_TEST_OPTIMIZATION_* constants to centralize config loading - Move get_workspace_path import to module level (no circular dependency) - Fix Windows path bug: use .as_posix() for consistent forward slashes - Open output file with explicit encoding="utf-8" - Remove unused _encode_test_parameter re-export from plugin.py; update test_plugin.py to import directly from utils.py - Add test for empty collection (output file created but empty) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What
Adds a test discovery mode to the ddtrace/testing pytest plugin. When
DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED=true, pytest collects all tests and writes them as JSON Lines to a file, then exits without running any test. This is used by ddtest during its plan phase to enumerate the test suite before deciding which tests to run or skip.Why
ddtest needs a language-agnostic way to enumerate tests for a given project. For Ruby,
datadog-ci-rbprovides this via a discovery mode. This PR adds the equivalent for pytest, producing the same JSONL format so ddtest's planner can parse both without special-casing the language.Configuration
DD_TEST_OPTIMIZATION_DISCOVERY_ENABLEDfalseDD_TEST_OPTIMIZATION_DISCOVERY_FILE.testoptimization/tests-discovery/tests.jsonOutput format
Each line is a JSON object with:
name— test namesuite— test suite (file path, dot-separated)module— module (directory, dot-separated)parameters— JSON-encoded parameters for parametrised tests, ornullsuiteSourceFile— relative path to the source fileTests decorated with
pytest.mark.skiporpytest.mark.skipifwith a truthy non-string condition are excluded from the output.Other changes
_get_test_parameters_jsonand_encode_test_parameterfromplugin.pytoutils.pyto avoid a circular import with_discovery.pyDD_TEST_OPTIMIZATION_DISCOVERY_ENABLEDandDD_TEST_OPTIMIZATION_DISCOVERY_FILEin the ddtrace settings registry