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:
parent
9f035ca030
commit
8a9c501030
2 changed files with 30 additions and 18 deletions
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue