Skip to content

SCANPY-248 Auto detect test code#318

Merged
guillaume-dequenne merged 6 commits into
masterfrom
test-code
May 13, 2026
Merged

SCANPY-248 Auto detect test code#318
guillaume-dequenne merged 6 commits into
masterfrom
test-code

Conversation

@guillaume-dequenne
Copy link
Copy Markdown
Contributor

No description provided.

@hashicorp-vault-sonar-prod hashicorp-vault-sonar-prod Bot changed the title Auto detect test code SCANPY-248 Auto detect test code May 8, 2026
@hashicorp-vault-sonar-prod
Copy link
Copy Markdown

hashicorp-vault-sonar-prod Bot commented May 8, 2026

SCANPY-248

@sonar-review-alpha
Copy link
Copy Markdown

sonar-review-alpha Bot commented May 8, 2026

Summary

Adds automatic detection of test file paths from Python tooling configuration.

When sonar.tests is not explicitly set, the scanner now infers test directories from common sources: pyproject.toml (pytest config), pytest.ini, tox.ini, setup.cfg, or filesystem conventions (directories named "tests", "test", "testing"). This reduces reliance on sonar-python's heuristic that silences issues on test-like files.

New CLI flag --sonar-python-test-file-heuristic-disabled lets users disable this heuristic to treat all files as main code regardless of their path. If tooling declares testpaths but all paths are invalid (don't exist), the heuristic is automatically disabled since user intent was expressed.

What reviewers should know

Core Logic: Start with test_paths_loader.py — this is the new auto-detection module. It tries loaders in order (pyproject.toml → pytest.ini → tox.ini → setup.cfg → filesystem). The return value signals both what tests were found AND whether to disable the heuristic if declared paths were invalid.

Integration Point: configuration_loader.py now calls test_paths_loader.load() when sonar.tests is absent and the heuristic isn't disabled. The heuristic-disabling logic checks for an explicit CLI/env/config value before auto-detecting.

Tests: Extensive unit tests in test_test_paths_loader.py (923 lines) use pyfakefs to test all config formats and edge cases. test_auto_detect_tests.py is an integration test against real project structures.

Config Chain: New property SONAR_PYTHON_TEST_FILE_HEURISTIC_DISABLED was added to properties.py and wired through CLI args. Check the property's cli_getter to see how it handles both --sonar-python-test-file-heuristic-disabled and -Dsonar.python.testFileHeuristic.disabled variants.

Watch For: The distinction between "found nothing" (return False, fall through) and "declared but all invalid" (return True, set disable flag). Also check Windows path handling in _existing_paths() — it handles both absolute paths and drive-relative paths.


  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@Seppli11 Seppli11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, the cahnge looks good. It's really cool to see that we're finally parsing settings from the pyproject.toml file and co. (Hopefully the python version is next 🤞 )

One thing I'm not sure about is if it makes sense to also have a heuristic based on the folders in the scanner. It seems to create quite a few strange places where, IMO, the outcome isn't that straight forward.

The other, more nitpicky thing, python_project_loader doesn't seem like an appropriate name for this module, given how it is used and how closely tight it usage is to the present of the test property.

Let me know if you want to discuss anything

# Running python_project_loader unconditionally would emit confusing warnings about
# pytest config even when the result would be discarded.
if SONAR_TESTS not in resolved_properties:
resolved_properties.update(python_project_loader.load(base_dir))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a more holistic view, applying the properties from the python_project_loader only if sonar.tests is not set doesn't really make sense to me. If we for example extend the python_project_loader to also detect the python version, things become weird.

Maybe it could make sense renaming the module to python_project_test_source_loader or similar (maybe you find a catchier name 😅 )

return _load_from_ini_file(base_dir, "setup.cfg", _SETUP_CFG_PYTEST_SECTION)


def _load_from_filesystem(base_dir: pathlib.Path) -> Optional[str]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to add this heuristic here as well, given that sonar-python itself has a more extensive set of heuristics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the heuristic from sonar-python is a bit of a crutch in the sense that it will only disable rules and still report metrics based on the FileType, because we can't contradict the scanner for the file type classification.
This will reduce noise but ultimately still produce an "inconsistent" analysis.

Having it here makes the analysis more consistent because the rule execution is in sync with the metrics reporting. It's an argument to use pysonar instead of the generic sonar-scanner-cli because here, the manual setup of sonar.tests is truly optional (assuming what is inferred matches the user expectations).

Copy link
Copy Markdown
Contributor

@Seppli11 Seppli11 May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. Maybe it would make sense to only fall back to the heuristic if the user hasn't set anything, rather than we haven't found a valid test folder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a way I'm wondering if analysis coming from pysonar shouldn't simply disable the heuristic by default?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe that would make the most sense. It would certainly simplify the flow chart of how the heuristics apply.
And, even in the case where they don't specify anything in their respective configuration files, the file based heuristic should still pick up most cases.

self._wait_for_ce_completion(workdir)
return process

def _wait_for_ce_completion(self, workdir: pathlib.Path) -> None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does a lot of automatic checks and fallbacks. Is this really necessary for a test?

Comment thread tests/its/utils/sonarqube_client.py Outdated
resp.raise_for_status()
return resp.json()

def get_latest_ce_task(self) -> Optional[dict]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be never called. Same applies to search_projects and get_project_measures

testpaths = []
""",
)
result = python_project_loader.load(Path("."))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the heuristic, this could lead to a weird behavior where the pyrpoject.tol loader returned an empty set, while the file heuristic loader found a folder. This would now override what the user set

self.fs.create_dir("tests")
# nonexistent/ is not on disk — only tests/ should be returned
result = python_project_loader.load(Path("."))
self.assertEqual(result[SONAR_TESTS], "tests")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar issue could appear here, where for whatever reason, all the user specified tests are not valid paths. If the heuristic picks them up, we're overriding what the user explicitly told us.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made changes so that if we detect a legitimate attempt to set the test, even if invalid, we disable the test detection heuristic downstream.

Comment thread tests/unit/test_test_paths_loader.py Outdated
def test_load_from_pytest_ini(self):
self.fs.create_file(
"pytest.ini",
contents="[pytest]\ntestpaths = tests integration\n",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: multiline strings would be nicer

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as resolved.

@guillaume-dequenne guillaume-dequenne force-pushed the test-code branch 2 times, most recently from b1b6bf8 to 29971b5 Compare May 13, 2026 11:10
sonar-review-alpha[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@Seppli11 Seppli11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I think like this, it makes a lot of sense and seems IMO intuitive. Thanks for implementing this 😁

One thing, it would probably make sense to add the new parameter as a cli option, as well as running the generate_cli_documentation.py tool to regenerate the documentation.

SONAR_WORKING_DIRECTORY: Key = "sonar.working.directory"
SONAR_SCM_FORCE_RELOAD_ALL: Key = "sonar.scm.forceReloadAll"
SONAR_MODULES: Key = "sonar.modules"
SONAR_PYTHON_TEST_FILE_HEURISTIC_DISABLED: Key = "sonar.python.testFileHeuristic.disabled"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option should also be added to the configuration.cli module as an option, potentially as a switch

sonar-review-alpha[bot]

This comment was marked as resolved.

@sonarqube-next
Copy link
Copy Markdown

Copy link
Copy Markdown

@sonar-review-alpha sonar-review-alpha Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The or-based CLI getter bug for SONAR_PYTHON_TEST_FILE_HEURISTIC_DISABLED (previously flagged) is still present — False from --no-sonar-python-test-file-heuristic-disabled is silently discarded. That needs fixing before merge.

🗣️ Give feedback

@guillaume-dequenne guillaume-dequenne merged commit dbea81c into master May 13, 2026
20 checks passed
@guillaume-dequenne guillaume-dequenne deleted the test-code branch May 13, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants