8
8
9
9
from ._version import __version__ # noqa: F401
10
10
11
+ # replace by Enum class after Python 2 support is gone
12
+ CLASS = 1
13
+ MODULE = 2
14
+ SESSION = 3
15
+
11
16
orders_map = {
12
17
"first" : 0 ,
13
18
"second" : 1 ,
@@ -50,7 +55,7 @@ def pytest_configure(config):
50
55
# manually replace it.
51
56
# Python 2.7 didn"t allow arbitrary attributes on methods, so we have
52
57
# to keep the function as a function and then add it to the class as a
53
- # pseudomethod . Since the class is purely for structuring and `self`
58
+ # pseudo method . Since the class is purely for structuring and `self`
54
59
# is never referenced, this seems reasonable.
55
60
OrderingPlugin .pytest_collection_modifyitems = pytest .hookimpl (
56
61
function = modify_items , tryfirst = True )
@@ -71,7 +76,14 @@ def pytest_addoption(parser):
71
76
group .addoption ("--order-scope" , action = "store" ,
72
77
dest = "order_scope" ,
73
78
help = "Defines the scope used for ordering. Possible values"
74
- "are 'session' (default), 'module', and 'class'" )
79
+ "are 'session' (default), 'module', and 'class'."
80
+ "Ordering is only done inside a scope." )
81
+ group .addoption ("--order-group-scope" , action = "store" ,
82
+ dest = "order_group_scope" ,
83
+ help = "Defines the scope used for order groups. Possible "
84
+ "values are 'session' (default), 'module', "
85
+ "and 'class'. Ordering is first done inside a group, "
86
+ "then between groups." )
75
87
group .addoption ("--sparse-ordering" , action = "store_true" ,
76
88
dest = "sparse_ordering" ,
77
89
help = "If there are gaps between ordinals they are filled "
@@ -94,21 +106,40 @@ class OrderingPlugin(object):
94
106
class Settings :
95
107
sparse_ordering = False
96
108
order_dependencies = False
97
- scope = "session"
109
+ scope = SESSION
110
+ group_scope = SESSION
111
+
112
+ valid_scopes = {
113
+ "class" : CLASS ,
114
+ "module" : MODULE ,
115
+ "session" : SESSION
116
+ }
98
117
99
118
@classmethod
100
119
def initialize (cls , config ):
101
120
cls .sparse_ordering = config .getoption ("sparse_ordering" )
102
121
cls .order_dependencies = config .getoption ("order_dependencies" )
103
122
scope = config .getoption ("order_scope" )
104
- if scope in ( "session" , "module" , "class" ) :
105
- cls .scope = scope
123
+ if scope in cls . valid_scopes :
124
+ cls .scope = cls . valid_scopes [ scope ]
106
125
else :
107
126
if scope is not None :
108
127
warn ("Unknown order scope '{}', ignoring it. "
109
128
"Valid scopes are 'session', 'module' and 'class'."
110
129
.format (scope ))
111
- cls .scope = "session"
130
+ cls .scope = SESSION
131
+ group_scope = config .getoption ("order_group_scope" )
132
+ if group_scope in cls .valid_scopes :
133
+ cls .group_scope = cls .valid_scopes [group_scope ]
134
+ else :
135
+ if group_scope is not None :
136
+ warn ("Unknown order group scope '{}', ignoring it. "
137
+ "Valid scopes are 'session', 'module' and 'class'."
138
+ .format (group_scope ))
139
+ cls .group_scope = cls .scope
140
+ if cls .group_scope > cls .scope :
141
+ warn ("Group scope is larger than order scope, ignoring it." )
142
+ cls .group_scope = cls .scope
112
143
113
144
114
145
def full_name (item , name = None ):
@@ -212,6 +243,64 @@ def insert_after(name, items, sort):
212
243
return False
213
244
214
245
246
+ def sorted_groups (groups ):
247
+ start_groups = []
248
+ middle_groups = []
249
+ end_groups = []
250
+ # TODO: handle relative markers
251
+ for group in groups :
252
+ if group [0 ] is None :
253
+ middle_groups .append (group [1 ])
254
+ elif group [0 ] >= 0 :
255
+ start_groups .append (group )
256
+ else :
257
+ end_groups .append (group )
258
+
259
+ start_groups = sorted (start_groups )
260
+ end_groups = sorted (end_groups )
261
+ groups_sorted = [group [1 ] for group in start_groups ]
262
+ groups_sorted .extend (middle_groups )
263
+ groups_sorted .extend ([group [1 ] for group in end_groups ])
264
+ if start_groups :
265
+ group_order = start_groups [0 ][0 ]
266
+ elif end_groups :
267
+ group_order = end_groups [- 1 ][0 ]
268
+ else :
269
+ group_order = None
270
+ return group_order , groups_sorted
271
+
272
+
273
+ def modify_item_groups (items ):
274
+ if Settings .group_scope < Settings .scope :
275
+ sorted_list = []
276
+ if Settings .scope == SESSION :
277
+ module_items = module_item_groups (items )
278
+ module_groups = []
279
+ if Settings .group_scope == CLASS :
280
+ for module_item in module_items .values ():
281
+ class_items = class_item_groups (module_item )
282
+ class_groups = [do_modify_items (item ) for item in
283
+ class_items .values ()]
284
+ module_group = []
285
+ group_order , class_groups = sorted_groups (class_groups )
286
+ for group in class_groups :
287
+ module_group .extend (group )
288
+ module_groups .append ((group_order , module_group ))
289
+ else :
290
+ module_groups = [do_modify_items (item ) for item in
291
+ module_items .values ()]
292
+ for group in sorted_groups (module_groups )[1 ]:
293
+ sorted_list .extend (group )
294
+ else : # module scope / class group scope
295
+ class_items = class_item_groups (items )
296
+ class_groups = [do_modify_items (item ) for item in
297
+ class_items .values ()]
298
+ for group in sorted_groups (class_groups )[1 ]:
299
+ sorted_list .extend (group )
300
+ return sorted_list
301
+ return do_modify_items (items )[1 ]
302
+
303
+
215
304
def do_modify_items (items ):
216
305
before_item = {}
217
306
after_item = {}
@@ -272,7 +361,14 @@ def do_modify_items(items):
272
361
sys .stdout .flush ()
273
362
print ("enqueue them behind the others" )
274
363
275
- return sorted_list
364
+ if start_item :
365
+ group_order = start_item [0 ][0 ]
366
+ elif end_item :
367
+ group_order = end_item [- 1 ][0 ]
368
+ else :
369
+ group_order = None
370
+
371
+ return group_order , sorted_list
276
372
277
373
278
374
def sort_numbered_items (start_item , end_item , unordered_list ):
@@ -300,26 +396,36 @@ def sort_numbered_items(start_item, end_item, unordered_list):
300
396
301
397
def modify_items (session , config , items ):
302
398
Settings .initialize (config )
303
- if Settings .scope == "session" :
304
- sorted_list = do_modify_items (items )
305
- elif Settings .scope == "module" :
306
- module_items = OrderedDict ()
307
- for item in items :
308
- module_path = item .nodeid [:item .nodeid .index ("::" )]
309
- module_items .setdefault (module_path , []).append (item )
399
+ if Settings .scope == SESSION :
400
+ sorted_list = modify_item_groups (items )
401
+ elif Settings .scope == MODULE :
402
+ module_items = module_item_groups (items )
310
403
sorted_list = []
311
404
for module_item_list in module_items .values ():
312
- sorted_list .extend (do_modify_items (module_item_list ))
405
+ sorted_list .extend (modify_item_groups (module_item_list ))
313
406
else : # class scope
314
- class_items = OrderedDict ()
315
- for item in items :
316
- delim_index = item .nodeid .index ("::" )
317
- if "::" in item .nodeid [delim_index + 2 :]:
318
- delim_index = item .nodeid .index ("::" , delim_index + 2 )
319
- class_path = item .nodeid [:delim_index ]
320
- class_items .setdefault (class_path , []).append (item )
407
+ class_items = class_item_groups (items )
321
408
sorted_list = []
322
409
for class_item_list in class_items .values ():
323
- sorted_list .extend (do_modify_items (class_item_list ))
410
+ sorted_list .extend (modify_item_groups (class_item_list ))
324
411
325
412
items [:] = sorted_list
413
+
414
+
415
+ def module_item_groups (items ):
416
+ module_items = OrderedDict ()
417
+ for item in items :
418
+ module_path = item .nodeid [:item .nodeid .index ("::" )]
419
+ module_items .setdefault (module_path , []).append (item )
420
+ return module_items
421
+
422
+
423
+ def class_item_groups (items ):
424
+ class_items = OrderedDict ()
425
+ for item in items :
426
+ delim_index = item .nodeid .index ("::" )
427
+ if "::" in item .nodeid [delim_index + 2 :]:
428
+ delim_index = item .nodeid .index ("::" , delim_index + 2 )
429
+ class_path = item .nodeid [:delim_index ]
430
+ class_items .setdefault (class_path , []).append (item )
431
+ return class_items
0 commit comments