44import sys
55import traceback
66
7+ import operator
8+
79
810def pytest_addoption (parser ):
911 group = parser .getgroup ('random-order' )
@@ -35,7 +37,7 @@ def pytest_report_header(config):
3537}
3638
3739
38- def _shuffle_items (items , key = None , preserve_bucket_order = False ):
40+ def _shuffle_items (items , key = None , disable = None , preserve_bucket_order = False ):
3941 """
4042 Shuffles `items`, a list, in place.
4143
@@ -47,26 +49,46 @@ def _shuffle_items(items, key=None, preserve_bucket_order=False):
4749 Bucket defines the boundaries across which tests will not
4850 be reordered.
4951
52+ If `disable` is function and returns True for ALL items
53+ in a bucket, items in this bucket will remain in their original order.
54+
5055 `preserve_bucket_order` is only customisable for testing purposes.
5156 There is no use case for predefined bucket order, is there?
5257 """
5358
5459 # If `key` is falsey, shuffle is global.
55- if not key :
60+ if not key and not disable :
5661 random .shuffle (items )
5762 return
5863
64+ # Use (key(x), disable(x)) as the key because
65+ # when we have a bucket type like package over a disabled module, we must
66+ # not shuffle the disabled module items.
67+ def full_key (x ):
68+ if key and disable :
69+ return key (x ), disable (x )
70+ elif disable :
71+ return disable (x )
72+ else :
73+ return key (x )
74+
5975 buckets = []
6076 this_key = '__not_initialised__'
6177 for item in items :
6278 prev_key = this_key
63- this_key = key (item )
79+ this_key = full_key (item )
6480 if this_key != prev_key :
6581 buckets .append ([])
6682 buckets [- 1 ].append (item )
6783
68- # Shuffle within bucket
84+ # Shuffle within bucket unless disable(item) evaluates to True for
85+ # the first item in the bucket.
86+ # This assumes that whoever supplied disable function knows this requirement.
87+ # Fixation of individual items in an otherwise shuffled bucket
88+ # is not supported.
6989 for bucket in buckets :
90+ if callable (disable ) and disable (bucket [0 ]):
91+ continue
7092 random .shuffle (bucket )
7193
7294 # Shuffle buckets
@@ -85,13 +107,28 @@ def _get_set_of_item_ids(items):
85107 return s
86108
87109
110+ _is_random_order_disabled = operator .attrgetter ('pytest.mark.random_order_disabled' )
111+
112+
113+ def _disable (item ):
114+ try :
115+ if _is_random_order_disabled (item .module ):
116+ # It is not enough to return just True because in case the shuffling
117+ # is disabled on module, we must preserve the module unchanged
118+ # even when the bucket type for this test run is say package or global.
119+ return item .module .__name__
120+ except AttributeError :
121+ return False
122+
123+
88124def pytest_collection_modifyitems (session , config , items ):
89125 failure = None
126+
90127 item_ids = _get_set_of_item_ids (items )
91128
92129 try :
93- shuffle_mode = config .getoption ('random_order_bucket' )
94- _shuffle_items (items , key = _random_order_item_keys [shuffle_mode ] )
130+ bucket_type = config .getoption ('random_order_bucket' )
131+ _shuffle_items (items , key = _random_order_item_keys [bucket_type ], disable = _disable )
95132
96133 except Exception as e :
97134 # If the number of items is still the same, we assume that we haven't messed up too hard
0 commit comments