Split Session._collection_node_cache to 3 mutually exclusive parts

Previously, this cache was used with 3 different and mutually exclusive
key-type -> value-type combinations. Mypy can't properly type this. It's
also quite confusing.

Split to 3 different dicts instead.
This commit is contained in:
Ran Benita 2020-01-24 11:43:14 +02:00
parent a76bc64c54
commit f3967333a1
1 changed files with 28 additions and 14 deletions

View File

@ -8,6 +8,7 @@ import sys
from typing import Dict from typing import Dict
from typing import FrozenSet from typing import FrozenSet
from typing import List from typing import List
from typing import Sequence
from typing import Tuple from typing import Tuple
import attr import attr
@ -21,13 +22,15 @@ from _pytest.config import directory_arg
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config import UsageError from _pytest.config import UsageError
from _pytest.fixtures import FixtureManager from _pytest.fixtures import FixtureManager
from _pytest.nodes import Node
from _pytest.outcomes import exit from _pytest.outcomes import exit
from _pytest.reports import CollectReport
from _pytest.runner import collect_one_node from _pytest.runner import collect_one_node
from _pytest.runner import SetupState from _pytest.runner import SetupState
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Type
from _pytest.python import Package from _pytest.python import Package
@ -407,7 +410,16 @@ class Session(nodes.FSCollector):
self._initialpaths = frozenset() # type: FrozenSet[py.path.local] self._initialpaths = frozenset() # type: FrozenSet[py.path.local]
# Keep track of any collected nodes in here, so we don't duplicate fixtures # Keep track of any collected nodes in here, so we don't duplicate fixtures
self._collection_node_cache = {} # type: Dict[str, List[Node]] self._collection_node_cache1 = (
{}
) # type: Dict[py.path.local, Sequence[nodes.Collector]]
self._collection_node_cache2 = (
{}
) # type: Dict[Tuple[Type[nodes.Collector], py.path.local], nodes.Collector]
self._collection_node_cache3 = (
{}
) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport]
# Dirnames of pkgs with dunder-init files. # Dirnames of pkgs with dunder-init files.
self._collection_pkg_roots = {} # type: Dict[py.path.local, Package] self._collection_pkg_roots = {} # type: Dict[py.path.local, Package]
@ -525,7 +537,9 @@ class Session(nodes.FSCollector):
self._notfound.append((report_arg, sys.exc_info()[1])) self._notfound.append((report_arg, sys.exc_info()[1]))
self.trace.root.indent -= 1 self.trace.root.indent -= 1
self._collection_node_cache.clear() self._collection_node_cache1.clear()
self._collection_node_cache2.clear()
self._collection_node_cache3.clear()
self._collection_pkg_roots.clear() self._collection_pkg_roots.clear()
def _collect(self, argpath, names): def _collect(self, argpath, names):
@ -543,13 +557,13 @@ class Session(nodes.FSCollector):
if parent.isdir(): if parent.isdir():
pkginit = parent.join("__init__.py") pkginit = parent.join("__init__.py")
if pkginit.isfile(): if pkginit.isfile():
if pkginit not in self._collection_node_cache: if pkginit not in self._collection_node_cache1:
col = self._collectfile(pkginit, handle_dupes=False) col = self._collectfile(pkginit, handle_dupes=False)
if col: if col:
if isinstance(col[0], Package): if isinstance(col[0], Package):
self._collection_pkg_roots[parent] = col[0] self._collection_pkg_roots[parent] = col[0]
# always store a list in the cache, matchnodes expects it # always store a list in the cache, matchnodes expects it
self._collection_node_cache[col[0].fspath] = [col[0]] self._collection_node_cache1[col[0].fspath] = [col[0]]
# If it's a directory argument, recurse and look for any Subpackages. # If it's a directory argument, recurse and look for any Subpackages.
# Let the Package collector deal with subnodes, don't collect here. # Let the Package collector deal with subnodes, don't collect here.
@ -576,21 +590,21 @@ class Session(nodes.FSCollector):
for x in self._collectfile(path): for x in self._collectfile(path):
key = (type(x), x.fspath) key = (type(x), x.fspath)
if key in self._collection_node_cache: if key in self._collection_node_cache2:
yield self._collection_node_cache[key] yield self._collection_node_cache2[key]
else: else:
self._collection_node_cache[key] = x self._collection_node_cache2[key] = x
yield x yield x
else: else:
assert argpath.check(file=1) assert argpath.check(file=1)
if argpath in self._collection_node_cache: if argpath in self._collection_node_cache1:
col = self._collection_node_cache[argpath] col = self._collection_node_cache1[argpath]
else: else:
collect_root = self._collection_pkg_roots.get(argpath.dirname, self) collect_root = self._collection_pkg_roots.get(argpath.dirname, self)
col = collect_root._collectfile(argpath, handle_dupes=False) col = collect_root._collectfile(argpath, handle_dupes=False)
if col: if col:
self._collection_node_cache[argpath] = col self._collection_node_cache1[argpath] = col
m = self.matchnodes(col, names) m = self.matchnodes(col, names)
# If __init__.py was the only file requested, then the matched node will be # If __init__.py was the only file requested, then the matched node will be
# the corresponding Package, and the first yielded item will be the __init__ # the corresponding Package, and the first yielded item will be the __init__
@ -703,11 +717,11 @@ class Session(nodes.FSCollector):
continue continue
assert isinstance(node, nodes.Collector) assert isinstance(node, nodes.Collector)
key = (type(node), node.nodeid) key = (type(node), node.nodeid)
if key in self._collection_node_cache: if key in self._collection_node_cache3:
rep = self._collection_node_cache[key] rep = self._collection_node_cache3[key]
else: else:
rep = collect_one_node(node) rep = collect_one_node(node)
self._collection_node_cache[key] = rep self._collection_node_cache3[key] = rep
if rep.passed: if rep.passed:
has_matched = False has_matched = False
for x in rep.result: for x in rep.result: