18
18
from _pytest .config import hookimpl
19
19
from _pytest .config import UsageError
20
20
from _pytest .outcomes import exit
21
- from _pytest .pathlib import parts
22
21
from _pytest .runner import collect_one_node
23
22
24
23
@@ -387,6 +386,7 @@ def __init__(self, config):
387
386
self ._initialpaths = frozenset ()
388
387
# Keep track of any collected nodes in here, so we don't duplicate fixtures
389
388
self ._node_cache = {}
389
+ self ._pkg_roots = {}
390
390
391
391
self .config .pluginmanager .register (self , name = "session" )
392
392
@@ -489,30 +489,26 @@ def _collect(self, arg):
489
489
490
490
names = self ._parsearg (arg )
491
491
argpath = names .pop (0 ).realpath ()
492
- paths = set ()
493
492
494
- root = self
495
493
# Start with a Session root, and delve to argpath item (dir or file)
496
494
# and stack all Packages found on the way.
497
495
# No point in finding packages when collecting doctests
498
496
if not self .config .option .doctestmodules :
497
+ pm = self .config .pluginmanager
499
498
for parent in argpath .parts ():
500
- pm = self .config .pluginmanager
501
499
if pm ._confcutdir and pm ._confcutdir .relto (parent ):
502
500
continue
503
501
504
502
if parent .isdir ():
505
503
pkginit = parent .join ("__init__.py" )
506
504
if pkginit .isfile ():
507
- if pkginit in self ._node_cache :
508
- root = self ._node_cache [pkginit ][0 ]
509
- else :
510
- col = root ._collectfile (pkginit )
505
+ if pkginit not in self ._node_cache :
506
+ col = self ._collectfile (pkginit , handle_dupes = False )
511
507
if col :
512
508
if isinstance (col [0 ], Package ):
513
- root = col [0 ]
509
+ self . _pkg_roots [ parent ] = col [0 ]
514
510
# always store a list in the cache, matchnodes expects it
515
- self ._node_cache [root .fspath ] = [root ]
511
+ self ._node_cache [col [ 0 ] .fspath ] = [col [ 0 ] ]
516
512
517
513
# If it's a directory argument, recurse and look for any Subpackages.
518
514
# Let the Package collector deal with subnodes, don't collect here.
@@ -535,28 +531,34 @@ def filter_(f):
535
531
):
536
532
dirpath = path .dirpath ()
537
533
if dirpath not in seen_dirs :
534
+ # Collect packages first.
538
535
seen_dirs .add (dirpath )
539
536
pkginit = dirpath .join ("__init__.py" )
540
- if pkginit .exists () and parts (pkginit .strpath ).isdisjoint (paths ):
541
- for x in root ._collectfile (pkginit ):
542
- yield x
543
- paths .add (x .fspath .dirpath ())
544
-
545
- if parts (path .strpath ).isdisjoint (paths ):
546
- for x in root ._collectfile (path ):
547
- key = (type (x ), x .fspath )
548
- if key in self ._node_cache :
549
- yield self ._node_cache [key ]
550
- else :
551
- self ._node_cache [key ] = x
537
+ if pkginit .exists ():
538
+ collect_root = self ._pkg_roots .get (dirpath , self )
539
+ for x in collect_root ._collectfile (pkginit ):
552
540
yield x
541
+ if isinstance (x , Package ):
542
+ self ._pkg_roots [dirpath ] = x
543
+ if dirpath in self ._pkg_roots :
544
+ # Do not collect packages here.
545
+ continue
546
+
547
+ for x in self ._collectfile (path ):
548
+ key = (type (x ), x .fspath )
549
+ if key in self ._node_cache :
550
+ yield self ._node_cache [key ]
551
+ else :
552
+ self ._node_cache [key ] = x
553
+ yield x
553
554
else :
554
555
assert argpath .check (file = 1 )
555
556
556
557
if argpath in self ._node_cache :
557
558
col = self ._node_cache [argpath ]
558
559
else :
559
- col = root ._collectfile (argpath )
560
+ collect_root = self ._pkg_roots .get (argpath .dirname , self )
561
+ col = collect_root ._collectfile (argpath )
560
562
if col :
561
563
self ._node_cache [argpath ] = col
562
564
m = self .matchnodes (col , names )
@@ -570,20 +572,20 @@ def filter_(f):
570
572
for y in m :
571
573
yield y
572
574
573
- def _collectfile (self , path ):
575
+ def _collectfile (self , path , handle_dupes = True ):
574
576
ihook = self .gethookproxy (path )
575
577
if not self .isinitpath (path ):
576
578
if ihook .pytest_ignore_collect (path = path , config = self .config ):
577
579
return ()
578
580
579
- # Skip duplicate paths.
580
- keepduplicates = self .config .getoption ("keepduplicates" )
581
- if not keepduplicates :
582
- duplicate_paths = self .config .pluginmanager ._duplicatepaths
583
- if path in duplicate_paths :
584
- return ()
585
- else :
586
- duplicate_paths .add (path )
581
+ if handle_dupes :
582
+ keepduplicates = self .config .getoption ("keepduplicates" )
583
+ if not keepduplicates :
584
+ duplicate_paths = self .config .pluginmanager ._duplicatepaths
585
+ if path in duplicate_paths :
586
+ return ()
587
+ else :
588
+ duplicate_paths .add (path )
587
589
588
590
return ihook .pytest_collect_file (path = path , parent = self )
589
591
0 commit comments