Skip to content

[Bug]: tests_for returns 0 for Python — bare-name fallback compares file paths against dotted module names #903

Description

@rsat

Version: 2.3.8 (fresh build, not an upgraded graph)
Language: Python (Odoo 18 codebase, ~9.9k files)

Symptom

tests_for returns 0 results for symbols that are covered by tests, and whose
TESTED_BY edge is present in the graph. callers_of on the same target
returns the test, flagged is_test=True.

$ code-review-graph query tests_for "<abs>/wizard_update_invoice_supplierinfo_line.py::WizardUpdateInvoiceSupplierinfoLine._prepare_supplierinfo_update"
  status: ok | result_count: 0

$ code-review-graph query callers_of "<same target>"
  status: ok | result_count: 2
    test_ninguna_clave_del_core_se_pierde_en_silencio  tests/test_wizard_steps.py:291  is_test=True
    update_supplierinfo                                wizard_update_invoice_supplierinfo.py:45

The information is in the graph; this query does not reach it. Silent false
negative — it reports covered code as uncovered.

Root cause

TESTED_BY edges are stored with a bare source_qualified in 64.5% of
cases (85,475 of 132,484 in this repo):

source_qualified: _prepare_supplierinfo_update
target_qualified: <abs>/tests/test_wizard_steps.py::TestWizardSteps.test_ninguna_clave...

get_transitive_tests looks up by fully-qualified name, so the direct lookup
misses. The bare-name fallback that follows is gated by
_select_evidence_backed_candidate (graph.py:720):

supported = [
    qualified
    for qualified, candidate_file in candidates
    if candidate_file == context_file or candidate_file in imported_files
]
return supported[0] if len(supported) == 1 else None

candidate_file is an absolute file path. imported_files is built in
_candidate_for_context from IMPORTS_FROM.target_qualified, which for Python
holds dotted module names:

odoo
odoo.exceptions
odoo.addons.account.tests.common
odoo.addons.account_invoice_supplierinfo_update.wizard.wizard_update_...

Two different namespaces, compared with in. The membership test can never
succeed, supported is empty, None is returned, and every edge that depends
on the fallback is dropped by the continue.

In this repo 78.3% of IMPORTS_FROM edges (22,546 of 28,780) carry dotted
module targets; only 21.7% are file paths.

Reproduction

Any Python project where the test does not import the production symbol
directly (framework-mediated access — Odoo's self.env['model'], Django's app
registry, pytest fixtures). 2,712 symbols in this repo have TESTED_BY edges
reachable only through the bare-name fallback; all of them report 0.

Not an upgrade artifact: reproduced after a full build on 2.3.8
(9,891 files, 57,706 nodes, 547,277 edges). postprocess alone does not
change the bare-edge count either (85,438 before and after).

Related

Suggested direction

Normalize both sides before comparing: resolve dotted module targets to the
file paths of the nodes they refer to when building imported_files (the
graph already indexes those files), or store IMPORTS_FROM targets
consistently as paths. The C# fix in #799 may be directly portable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions