3030_SETUP_CFG_PYTEST_SECTION = "tool:pytest"
3131
3232
33- def load (base_dir : pathlib .Path ) -> dict [str , str ]:
33+ def load (base_dir : pathlib .Path ) -> tuple [ dict [str , str ], bool ]:
3434 """Infer sonar.tests from Python tooling configuration and filesystem conventions.
3535
36- Returns sonar.tests if a test directory can be reliably inferred; empty dict otherwise.
37- Filesystem convention fallback only runs when no config file declares a testpaths key.
36+ Returns (properties, disable_heuristic) where:
37+ - properties contains sonar.tests if a test directory was reliably inferred
38+ - disable_heuristic is True when a config file declared testpaths but all paths were
39+ invalid — the user expressed intent, so the sonar-python heuristic should not fire
3840 """
3941 for loader in [_load_from_pyproject_toml , _load_from_pytest_ini , _load_from_tox_ini , _load_from_setup_cfg ]:
4042 result = loader (base_dir )
4143 if result is None :
42- continue # file absent or no testpaths key — try next source
44+ continue # file absent, no testpaths key, or empty testpaths (no restriction) — try next
4345 if result :
44- return {SONAR_TESTS : result }
45- return {} # testpaths declared but all paths were invalid — stop chain, set nothing
46+ return {SONAR_TESTS : result }, False
47+ return {}, True # declared but all paths invalid: user expressed intent, disable heuristic
4648
47- # No config file declared a testpaths key ; fall back to filesystem conventions.
49+ # No config file gave a non-empty testpaths declaration ; fall back to filesystem conventions.
4850 filesystem_result = _load_from_filesystem (base_dir )
4951 if filesystem_result :
50- return {SONAR_TESTS : filesystem_result }
51- return {}
52+ return {SONAR_TESTS : filesystem_result }, False
53+ return {}, False
5254
5355
5456def _existing_paths (base_dir : pathlib .Path , paths : list [str ]) -> list [str ]:
@@ -102,7 +104,7 @@ def _load_from_pyproject_toml(base_dir: pathlib.Path) -> Optional[str]:
102104 testpaths = ini_options ["testpaths" ]
103105 raw = [str (p ) for p in (testpaths if isinstance (testpaths , list ) else testpaths .split ()) if str (p ).strip ()]
104106 if not raw :
105- return "" # testpaths key present but empty — stop chain
107+ return None # testpaths = [] means "no path restriction" — same as key absent, continue chain
106108 paths = _existing_paths (base_dir , raw )
107109 if paths :
108110 result = "," .join (paths )
@@ -129,7 +131,7 @@ def _load_from_ini_file(base_dir: pathlib.Path, filename: str, section: str) ->
129131 return None
130132 raw = [p for p in config [section ]["testpaths" ].split () if p ]
131133 if not raw :
132- return "" # testpaths key present but empty — stop chain
134+ return None # empty testpaths means "no path restriction" — same as key absent, continue chain
133135 paths = _existing_paths (base_dir , raw )
134136 if paths :
135137 result = "," .join (paths )
0 commit comments