diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index a6b495a318e..9f07df08b3c 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -33,7 +33,7 @@ jobs: run: | git fetch origin ${{ github.event.pull_request.base.sha }} export PATHS=$(git diff --name-only HEAD ${{ github.event.pull_request.base.sha }}) - python -c "import os,sys,fnmatch;sys.exit(not bool([_ for pattern in {'ddtrace/*', 'setup*', 'pyproject.toml', '.github/workflows/test_frameworks.yml', 'tests/debugging/exploration/*'} for _ in fnmatch.filter(os.environ['PATHS'].splitlines(), pattern)]))" + python -c "import os,sys,fnmatch;sys.exit(not bool([_ for pattern in {'ddtrace/*', 'setup*', 'pyproject.toml', '.github/workflows/test_frameworks.yml'} for _ in fnmatch.filter(os.environ['PATHS'].splitlines(), pattern)]))" continue-on-error: true bottle-testsuite: @@ -65,7 +65,6 @@ jobs: DD_APPSEC_ENABLED: ${{ matrix.appsec }} DD_TESTING_RAISE: true CMAKE_BUILD_PARALLEL_LEVEL: 12 - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt DD_BYTECODE_INJECTION_OUTPUT_FILE: bytecode-injection.txt defaults: @@ -107,10 +106,7 @@ jobs: if: needs.needs-run.outputs.outcome == 'success' # Disable all test_simple tests because they check for # log output and it contains phony error messages. - run: PYTHONPATH=../ddtrace/tests/debugging/exploration/ ddtrace-run pytest test --continue-on-collection-errors -v -k 'not test_simple' - - name: Debugger exploration result - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt + run: ddtrace-run pytest test --continue-on-collection-errors -v -k 'not test_simple' - name: Run Bytecode injection tests if: needs.needs-run.outputs.outcome == 'success' # Disable all test_simple tests because they check for @@ -124,34 +120,16 @@ jobs: strategy: matrix: include: - #- suffix: DI profiler - # expl_profiler: 1 - # expl_coverage: 0 - # profiling: 1 - # iast: 0 - # appsec: 0 - #- suffix: DI coverage - # expl_profiler: 0 - # expl_coverage: 1 - # profiling: 1 - # iast: 0 - # appsec: 0 # Disabled while the bug is investigated: APPSEC-53222 # - suffix: IAST - # expl_profiler: 0 - # expl_coverage: 0 # profiling: 0 # iast: 1 # appsec: 0 - suffix: APPSEC - expl_profiler: 0 - expl_coverage: 0 profiling: 0 iast: 0 appsec: 1 - suffix: Tracer only - expl_profiler: 0 - expl_coverage: 0 profiling: 0 iast: 0 appsec: 0 @@ -166,13 +144,6 @@ jobs: DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING: disabled # To avoid a couple failures due to the extra query DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED: false # To avoid a couple failures due to the extra query DD_TESTING_RAISE: true - DD_DEBUGGER_EXPL_ENCODE: 0 # Disabled to speed up - DD_DEBUGGER_EXPL_PROFILER_ENABLED: ${{ matrix.expl_profiler }} - DD_DEBUGGER_EXPL_PROFILER_DELETE_FUNCTION_PROBES: 1 # Delete to speed up - DD_DEBUGGER_EXPL_COVERAGE_ENABLED: ${{ matrix.expl_coverage }} - DD_DEBUGGER_EXPL_COVERAGE_DELETE_LINE_PROBES: 1 # Delete to speed up - DD_DEBUGGER_EXPL_CONSERVATIVE: 1 - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt DD_BYTECODE_INJECTION_OUTPUT_FILE: bytecode-injection.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: @@ -237,11 +208,7 @@ jobs: - name: Run tests if: needs.needs-run.outputs.outcome == 'success' # django.tests.requests module interferes with requests library patching in the tracer -> disable requests patch - run: PYTHONPATH=../ddtrace/tests/debugging/exploration/:. DD_PATCH_MODULES=unittest:no DD_TRACE_REQUESTS_ENABLED=0 ddtrace-run tests/runtests.py --parallel 1 - - - name: Debugger exploration results - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt + run: PYTHONPATH=. DD_PATCH_MODULES=unittest:no DD_TRACE_REQUESTS_ENABLED=0 ddtrace-run tests/runtests.py --parallel 1 - name: Run Bytecode injection tests if: needs.needs-run.outputs.outcome == 'success' @@ -279,8 +246,7 @@ jobs: DD_PROFILING_ENABLED: ${{ matrix.profiling }} DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} - PYTHONPATH: ../ddtrace/tests/debugging/exploration/:. - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt + PYTHONPATH: . CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -318,9 +284,6 @@ jobs: - name: Run tests if: needs.needs-run.outputs.outcome == 'success' run: ddtrace-run pytest graphene - - name: Debugger exploration results - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt fastapi-testsuite-0_92: strategy: @@ -351,7 +314,6 @@ jobs: DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} CMAKE_BUILD_PARALLEL_LEVEL: 12 - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt DD_BYTECODE_INJECTION_OUTPUT_FILE: bytecode-injection.txt defaults: run: @@ -388,10 +350,7 @@ jobs: - name: Test if: needs.needs-run.outputs.outcome == 'success' # https://github.com/tiangolo/fastapi/pull/10876 - run: PYTHONPATH=../ddtrace/tests/debugging/exploration/ ddtrace-run pytest -p no:warnings tests -k 'not test_warn_duplicate_operation_id' - - name: Debugger exploration results - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt + run: ddtrace-run pytest -p no:warnings tests -k 'not test_warn_duplicate_operation_id' - name: Bytecode injection Test if: needs.needs-run.outputs.outcome == 'success' # https://github.com/tiangolo/fastapi/pull/10876 @@ -429,7 +388,6 @@ jobs: DD_PROFILING_ENABLED: ${{ matrix.profiling }} DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} - PYTHONPATH: ../ddtrace/tests/debugging/exploration/ CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -519,8 +477,6 @@ jobs: env: # Disabled distributed tracing since there are a lot of tests that assert on headers DD_HTTPX_DISTRIBUTED_TRACING: "false" - # Debugger exploration testing does not work in CI - # PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} # test_pool_timeout raises RuntimeError: The connection pool was closed while 1 HTTP requests/responses were still in-flight @@ -555,7 +511,6 @@ jobs: DD_PROFILING_ENABLED: ${{ matrix.profiling }} DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} - PYTHONPATH: ../ddtrace/tests/debugging/exploration/ CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -622,8 +577,6 @@ jobs: DD_PROFILING_ENABLED: ${{ matrix.profiling }} DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} - PYTHONPATH: ../ddtrace/tests/debugging/exploration/ - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -658,9 +611,6 @@ jobs: - name: Run tests if: needs.needs-run.outputs.outcome == 'success' run: pytest -W ignore --ddtrace-patch-all tests -k 'not test_request_headers and not test_subdomain_route and not test_websocket_headers and not test_staticfiles_with_invalid_dir_permissions_returns_401 and not test_contextvars[asyncio-CustomMiddlewareUsingBaseHTTPMiddleware]' - - name: Debugger exploration results - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt requests-testsuite-2_26_0: strategy: @@ -691,7 +641,6 @@ jobs: DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} CMAKE_BUILD_PARALLEL_LEVEL: 12 - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt defaults: run: working-directory: requests @@ -726,10 +675,7 @@ jobs: run: pip install --upgrade pytest==5.4.3 - name: Run tests if: needs.needs-run.outputs.outcome == 'success' - run: PYTHONPATH=../ddtrace/tests/debugging/exploration/ ddtrace-run pytest -p no:warnings tests - - name: Debugger exploration results - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt + run: ddtrace-run pytest -p no:warnings tests asyncpg-testsuite: # https://github.com/MagicStack/asyncpg/blob/v0.25.0/.github/workflows/tests.yml#L125 @@ -816,7 +762,6 @@ jobs: DD_TESTING_RAISE: true DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} - # PYTHONPATH: ../ddtrace/tests/debugging/exploration/ CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -874,8 +819,6 @@ jobs: DD_PROFILING_ENABLED: ${{ matrix.profiling }} DD_IAST_ENABLED: ${{ matrix.iast }} DD_APPSEC_ENABLED: ${{ matrix.appsec }} - PYTHONPATH: ../ddtrace/tests/debugging/exploration/ - DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -924,6 +867,3 @@ jobs: - name: Run deadlock tests if: needs.needs-run.outputs.outcome == 'success' run: ddtrace-run ./tests/gh-deadlocks.sh python39 - - name: Debugger exploration results - if: needs.needs-run.outputs.outcome == 'success' - run: cat debugger-expl.txt diff --git a/.gitlab/templates/debugging/exploration.yml b/.gitlab/templates/debugging/exploration.yml new file mode 100644 index 00000000000..ff84efe505a --- /dev/null +++ b/.gitlab/templates/debugging/exploration.yml @@ -0,0 +1,25 @@ +"debugging::exploration::boto3": + stage: exploration + extends: .cached_testrunner + timeout: 30m + variables: + DD_DEBUGGER_EXPL_INCLUDE: "boto3" + DD_DEBUGGER_EXPL_OUTPUT_FILE: "${{CI_PROJECT_DIR}}/debugger-expl.txt" + DD_DEBUGGER_EXPL_STATUS_MESSAGES: 1 + PYTEST_PLUGINS: "exploration" + PYTHONPATH: "${{CI_PROJECT_DIR}}/tests/debugging/exploration/pytest" + parallel: + matrix: + - PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13"] + BOTO3_TAG: 1.38.44 + script: | + python${{PYTHON_VERSION}} -m pip install -e . + git clone --depth 1 --branch ${{BOTO3_TAG}} https://github.com/boto/boto3.git + cd boto3 + python${{PYTHON_VERSION}} scripts/ci/install + python${{PYTHON_VERSION}} scripts/ci/run-tests --test-runner 'pytest -svv -W error -W "ignore::dateutil.parser._parser.UnknownTimezoneWarning" -W "ignore::DeprecationWarning"' + cat ${{DD_DEBUGGER_EXPL_OUTPUT_FILE}} + needs: [] + artifacts: + paths: + - ${{DD_DEBUGGER_EXPL_OUTPUT_FILE}} diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 7c89ea1a4c6..aea13564505 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -2,6 +2,7 @@ stages: - setup - riot - hatch + - exploration variables: RIOT_RUN_CMD: riot -P -v run --exitfirst --pass-env -s diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index e6a11c0ba72..929efc443f9 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -265,6 +265,16 @@ def gen_build_base_venvs() -> None: f.write(template("build-base-venvs")) +def gen_debugger_exploration() -> None: + """Generate the cached testrunner job. + + We need to generate this dynamically from a template because it depends + on the cached testrunner job, which is also generated dynamically. + """ + with TESTS_GEN.open("a") as f: + f.write(template("debugging/exploration")) + + # ----------------------------------------------------------------------------- # The code below is the boilerplate that makes the script work. There is diff --git a/tests/debugging/exploration/_config.py b/tests/debugging/exploration/_config.py index a7f54b885dc..79b197b466c 100644 --- a/tests/debugging/exploration/_config.py +++ b/tests/debugging/exploration/_config.py @@ -29,7 +29,7 @@ class ExplorationConfig(DDConfig): encode = DDConfig.v( bool, "dd.debugger.expl.encode", - default=True, + default=False, help="Whether to encode the snapshots", ) @@ -40,7 +40,7 @@ class ExplorationConfig(DDConfig): help="Whether to print exploration debugger status messages", ) - include = DDConfig.v( + includes = DDConfig.v( list, "dd.debugger.expl.include", parser=lambda v: [path.split(".") for path in v.split(",")], @@ -58,7 +58,7 @@ class ExplorationConfig(DDConfig): conservative = DDConfig.v( bool, "dd.debugger.expl.conservative", - default=False, + default=True, help="Use extremely low capture limits to reduce overhead", ) diff --git a/tests/debugging/exploration/_coverage.py b/tests/debugging/exploration/_coverage.py index 74e0dee2b14..2cedf0ed7be 100644 --- a/tests/debugging/exploration/_coverage.py +++ b/tests/debugging/exploration/_coverage.py @@ -1,6 +1,5 @@ from collections import defaultdict from pathlib import Path -import sys from types import ModuleType import typing as t @@ -12,7 +11,6 @@ from debugging.utils import create_snapshot_line_probe from output import log from utils import COLS -from utils import CWD from ddtrace.debugging._function.discovery import FunctionDiscovery from ddtrace.debugging._probe.model import LogLineProbe @@ -22,27 +20,38 @@ # Track all the covered modules and its lines. Indexed by module origin. -_tracked_modules: t.Dict[str, t.Tuple[ModuleType, t.Set[int]]] = {} +_tracked_modules: t.Dict[Path, t.Tuple[ModuleType, t.Set[int]]] = {} class LineCollector(ModuleCollector): def on_collect(self, discovery: FunctionDiscovery) -> None: o = origin(discovery._module) + if o is None: + status("[coverage] cannot collect lines from %s, no origin found" % discovery._module.__name__) + return + status("[coverage] collecting lines from %s" % o) _tracked_modules[o] = (discovery._module, {_ for _ in discovery.keys()}) - LineCoverage.add_probes( - [ - create_snapshot_line_probe( - probe_id="@".join([str(hash(f)), str(line)]), - source_file=origin(sys.modules[f.__module__]), - line=line, - rate=0.0, - limits=expl_config.limits, + probes = [] + for line, fcps in discovery.items(): + for fcp in fcps: + try: + fcp.resolve() + except ValueError: + # This function-code pair is not from a function, e.g. a + # class. + continue + probe_id = f"{o}:{line}" + probes.append( + create_snapshot_line_probe( + probe_id=probe_id, + source_file=o, + line=line, + rate=0.0, + limits=expl_config.limits, + ) ) - for line, functions in discovery.items() - for f in functions - ] - ) + LineCoverage.add_probes(probes) class LineCoverage(ExplorationDebugger): @@ -55,7 +64,7 @@ def report_coverage(cls) -> None: seen_lines_map[t.cast(LogLineProbe, probe).resolved_source_file].add(probe.line) try: - w = max(len(str(o.relative_to(CWD))) for o in _tracked_modules) + w = max(len(str(o)) for o in _tracked_modules) except ValueError: w = int(COLS * 0.75) log(("{:=^%ds}" % COLS).format(" Line coverage ")) @@ -72,7 +81,7 @@ def report_coverage(cls) -> None: total_covered += len(seen_lines) log( ("{:<%d} {:>5} {: 6.0f}%%" % w).format( - str(o.relative_to(CWD)), + str(o), len(lines), len(seen_lines) * 100.0 / len(lines) if lines else 0, ) diff --git a/tests/debugging/exploration/_profiler.py b/tests/debugging/exploration/_profiler.py index e1cd061028c..59d42637fb4 100644 --- a/tests/debugging/exploration/_profiler.py +++ b/tests/debugging/exploration/_profiler.py @@ -25,18 +25,28 @@ class FunctionCollector(ModuleCollector): def on_collect(self, discovery: FunctionDiscovery) -> None: module = discovery._module status("[profiler] Collecting functions from %s" % module.__name__) - for fname, f in discovery._fullname_index.items(): - if origin(module) != Path(f.__code__.co_filename).resolve(): + try: # Python < 3.11 + fcps = [fcp for fcps in discovery._name_index.values() for fcp in fcps] + except AttributeError: # Python >= 3.11 + fcps = list(discovery._fullname_index.values()) + + for fcp in fcps: + try: + f = fcp.resolve() + except ValueError: + # This function-code pair is not from a function, e.g. a class. + continue + if (o := origin(module)) != Path(f.__code__.co_filename).resolve(): # Do not wrap functions that do not belong to the module. We # will have a chance to wrap them when we discover the module # they belong to. continue - _tracked_funcs[fname] = 0 + _tracked_funcs[f"{module.__name__}.{f.__qualname__}"] = 0 DeterministicProfiler.add_probe( create_snapshot_function_probe( - probe_id=str(hash(f)), + probe_id=f"{o}:{f.__code__.co_firstlineno}", module=module.__name__, - func_qname=fname.replace(module.__name__, "").lstrip("."), + func_qname=f.__qualname__, rate=float("inf"), limits=expl_config.limits, ) @@ -49,7 +59,7 @@ class DeterministicProfiler(ExplorationDebugger): @classmethod def report_func_calls(cls) -> None: for probe in (_ for _ in cls.get_triggered_probes() if isinstance(_, FunctionLocationMixin)): - _tracked_funcs[".".join([probe.module, probe.func_qname])] += 1 + _tracked_funcs[f"{probe.module}.{probe.func_qname}"] += 1 log(("{:=^%ds}" % COLS).format(" Function coverage ")) log("") calls = sorted([(v, k) for k, v in _tracked_funcs.items()], reverse=True) diff --git a/tests/debugging/exploration/debugger.py b/tests/debugging/exploration/debugger.py index 1c96cf1703f..293fa8c6e4b 100644 --- a/tests/debugging/exploration/debugger.py +++ b/tests/debugging/exploration/debugger.py @@ -1,4 +1,6 @@ import atexit +import linecache +import os import sys from types import ModuleType import typing as t @@ -45,7 +47,7 @@ def on_collect(self, discovery: FunctionDiscovery) -> None: def _on_new_module(self, module): try: if not is_ddtrace(module): - if config.include: + if config.includes: if not is_included(module, config): return elif not from_editable_install(module, config): @@ -55,7 +57,7 @@ def _on_new_module(self, module): return try: - return self.on_collect(FunctionDiscovery(module)) + return self.on_collect(FunctionDiscovery.from_module(module)) except Exception as e: status("Error collecting functions from %s: %s" % (module.__name__, e)) raise e @@ -203,6 +205,8 @@ def enable(cls) -> None: # not being managed by the product manager. atexit.register(cls.disable) + cls.__watchdog__.install() + @classmethod def disable(cls, join: bool = True) -> None: registry = cls._instance._probe_registry @@ -218,37 +222,63 @@ def disable(cls, join: bool = True) -> None: cls.on_disable() - snapshots = cls.get_snapshots() - if snapshots and snapshots[-1] is not None: - import json - from pprint import pprint - - pprint(json.loads(snapshots[-1].decode()), stream=config.output_stream) + cls.__watchdog__.uninstall() + + failed = False + if not nprobes: + failed = True + log(f"Exploration testing failed: no probes were installed for {cls.__name__}") + elif nokprobes != nprobes: + failed = True + log( + "Exploration testing failed: " + f"only {nokprobes} out of {nprobes} probes were installed for {cls.__name__}" + ) + for e in cls.get_failed_probes(): + probe_id = e.probe.probe_id + file, _, line = probe_id.partition(":") + log(f" - {e.error_type}: {e.message}, in {probe_id}") + log(f" >>> {linecache.getline(file, int(line))}") super(ExplorationDebugger, cls).disable(join=join) + if failed: + os._exit(2) + @classmethod def get_snapshots(cls) -> t.List[t.Optional[bytes]]: if cls._instance is None: - return None + raise RuntimeError("ExplorationDebugger is not enabled") return cls._instance.__uploader__.get_collector().snapshots @classmethod def get_triggered_probes(cls) -> t.List[Probe]: if cls._instance is None: - return None + raise RuntimeError("ExplorationDebugger is not enabled") return cls._instance.__uploader__.get_collector().probes + @classmethod + def get_failed_probes(cls) -> t.List[Probe]: + if cls._instance is None: + raise RuntimeError("ExplorationDebugger is not enabled") + return [e for e in cls._instance._probe_registry.values() if e.error_type is not None] + @classmethod def add_probe(cls, probe: Probe) -> None: + if cls._instance is None: + raise RuntimeError("ExplorationDebugger is not enabled") cls._instance._on_configuration(ProbePollerEvent.NEW_PROBES, [probe]) @classmethod def add_probes(cls, probes: t.List[Probe]) -> None: + if cls._instance is None: + raise RuntimeError("ExplorationDebugger is not enabled") cls._instance._on_configuration(ProbePollerEvent.NEW_PROBES, probes) @classmethod def delete_probe(cls, probe: Probe) -> None: + if cls._instance is None: + raise RuntimeError("ExplorationDebugger is not enabled") cls._instance._on_configuration(ProbePollerEvent.DELETED_PROBES, [probe]) diff --git a/tests/debugging/exploration/pytest/exploration.py b/tests/debugging/exploration/pytest/exploration.py new file mode 100644 index 00000000000..912d98f5e78 --- /dev/null +++ b/tests/debugging/exploration/pytest/exploration.py @@ -0,0 +1,8 @@ +from pathlib import Path +import sys + + +def pytest_configure(config): + # Make the exploration module available for import + sys.path.append(str(Path(__file__).parents[1])) + import preload # noqa: F401 diff --git a/tests/debugging/exploration/test_bootstrap.py b/tests/debugging/exploration/test_bootstrap.py index 33382693210..ed404bc0b7a 100644 --- a/tests/debugging/exploration/test_bootstrap.py +++ b/tests/debugging/exploration/test_bootstrap.py @@ -4,25 +4,7 @@ import pytest -OUT = """Enabling debugging exploration testing -===================== DeterministicProfiler: probes stats ====================== - -Installed probes: 0/0 - -============================== Function coverage =============================== - -No functions called -========================== LineCoverage: probes stats ========================== - -Installed probes: 0/0 - -================================ Line coverage ================================= - -Source Lines Covered -========================================================================== -No lines found -""" - +NEEDLE = "Enabling debugging exploration testing" EXPL_FOLDER = Path(__file__).parent.resolve() @@ -34,14 +16,7 @@ def expl_env(**kwargs): } -def test_exploration_smoke(): - import sys - - sys.path.insert(0, str(EXPL_FOLDER)) - import tests.debugging.exploration.preload # noqa: F401 - - -@pytest.mark.subprocess(env=expl_env(), out=OUT) +@pytest.mark.subprocess(env=expl_env(), out=lambda _: NEEDLE in _, status=2) def test_exploration_bootstrap(): # We test that we get the expected output from the exploration debuggers # and no errors when running the sitecustomize.py script. @@ -53,7 +28,7 @@ def check_output_file(o): output_file = Path("expl.txt") try: - assert output_file.read_text() == OUT + assert NEEDLE in output_file.read_text() return True finally: if output_file.exists(): @@ -63,6 +38,7 @@ def check_output_file(o): @pytest.mark.subprocess( env=expl_env(DD_DEBUGGER_EXPL_OUTPUT_FILE="expl.txt"), out=check_output_file, + status=2, ) def test_exploration_file_output(): from pathlib import Path diff --git a/tests/debugging/exploration/utils.py b/tests/debugging/exploration/utils.py index 57be18133aa..c7f96120ecd 100644 --- a/tests/debugging/exploration/utils.py +++ b/tests/debugging/exploration/utils.py @@ -1,6 +1,8 @@ import os from types import ModuleType +from _config import ExplorationConfig + from ddtrace.internal.compat import Path from ddtrace.internal.module import origin @@ -33,9 +35,9 @@ def from_editable_install(module: ModuleType, config) -> bool: ) -def is_included(module: ModuleType, config) -> bool: +def is_included(module: ModuleType, config: ExplorationConfig) -> bool: segments = module.__name__.split(".") - for i in config.include: + for i in config.includes: if i == segments[: len(i)]: return True return False