spec.py: fix __getitem__ looking outside of dag (#45090)

`Spec.__getitem__` queries dependent edges, which almost always point to
nodes outside the sub-dag considered. It should only ever look at edges
being traversed.
This commit is contained in:
Harmen Stoppels 2024-07-08 14:53:51 +02:00 committed by Harmen Stoppels
parent 9f035ca030
commit 8a9c501030
2 changed files with 30 additions and 18 deletions

View file

@ -4164,29 +4164,21 @@ def __getitem__(self, name: str):
csv = query_parameters.pop().strip()
query_parameters = re.split(r"\s*,\s*", csv)
# In some cases a package appears multiple times in the same DAG for *distinct*
# specs. For example, a build-type dependency may itself depend on a package
# the current spec depends on, but their specs may differ. Therefore we iterate
# in an order here that prioritizes the build, test and runtime dependencies;
# only when we don't find the package do we consider the full DAG.
order = lambda: itertools.chain(
self.traverse(deptype="link"),
self.dependencies(deptype=dt.BUILD | dt.RUN | dt.TEST),
self.traverse(), # fall back to a full search
self.traverse_edges(deptype=dt.LINK, order="breadth", cover="edges"),
self.edges_to_dependencies(depflag=dt.BUILD | dt.RUN | dt.TEST),
self.traverse_edges(deptype=dt.ALL, order="breadth", cover="edges"),
)
# Consider runtime dependencies and direct build/test deps before transitive dependencies,
# and prefer matches closest to the root.
try:
child: Spec = next(
itertools.chain(
# Regular specs
(x for x in order() if x.name == name),
(
x
for x in order()
if (not x.virtual)
and any(name in edge.virtuals for edge in x.edges_from_dependents())
),
(x for x in order() if (not x.virtual) and x.package.provides(name)),
e.spec
for e in itertools.chain(
(e for e in order() if e.spec.name == name or name in e.virtuals),
# for historical reasons
(e for e in order() if e.spec.concrete and e.spec.package.provides(name)),
)
)
except StopIteration:

View file

@ -1105,3 +1105,23 @@ def test_indexing_prefers_direct_or_transitive_link_deps():
# Ensure that the full DAG is still searched
assert root["a2"]
def test_getitem_sticks_to_subdag():
"""Test that indexing on Spec by virtual does not traverse outside the dag, which happens in
the unlikely case someone would rewrite __getitem__ in terms of edges_from_dependents instead
of edges_to_dependencies."""
x, y, z = Spec("x"), Spec("y"), Spec("z")
x.add_dependency_edge(z, depflag=dt.LINK, virtuals=("virtual",))
y.add_dependency_edge(z, depflag=dt.LINK, virtuals=())
assert x["virtual"].name == "z"
with pytest.raises(KeyError):
y["virtual"]
def test_getitem_finds_transitive_virtual():
x, y, z = Spec("x"), Spec("y"), Spec("z")
x.add_dependency_edge(z, depflag=dt.LINK, virtuals=())
x.add_dependency_edge(y, depflag=dt.LINK, virtuals=())
y.add_dependency_edge(z, depflag=dt.LINK, virtuals=("virtual",))
assert x["virtual"].name == "z"