2
2
from __future__ import absolute_import , division , print_function
3
3
4
4
import inspect
5
+ import warnings
5
6
from collections import namedtuple
6
7
from operator import attrgetter
7
8
from .compat import imap
9
+ from .deprecated import MARK_INFO_ATTRIBUTE
8
10
11
+ def alias (name , warning = None ):
12
+ getter = attrgetter (name )
9
13
10
- def alias (name ):
11
- return property (attrgetter (name ), doc = 'alias for ' + name )
14
+ def warned (self ):
15
+ warnings .warn (warning , stacklevel = 2 )
16
+ return getter (self )
17
+
18
+ return property (getter if warning is None else warned , doc = 'alias for ' + name )
12
19
13
20
14
21
class ParameterSet (namedtuple ('ParameterSet' , 'values, marks, id' )):
@@ -329,31 +336,51 @@ def __call__(self, *args, **kwargs):
329
336
is_class = inspect .isclass (func )
330
337
if len (args ) == 1 and (istestfunc (func ) or is_class ):
331
338
if is_class :
332
- if hasattr (func , 'pytestmark' ):
333
- mark_list = func .pytestmark
334
- if not isinstance (mark_list , list ):
335
- mark_list = [mark_list ]
336
- # always work on a copy to avoid updating pytestmark
337
- # from a superclass by accident
338
- mark_list = mark_list + [self ]
339
- func .pytestmark = mark_list
340
- else :
341
- func .pytestmark = [self ]
339
+ store_mark (func , self .mark )
342
340
else :
343
- holder = getattr (func , self .name , None )
344
- if holder is None :
345
- holder = MarkInfo (self .mark )
346
- setattr (func , self .name , holder )
347
- else :
348
- holder .add_mark (self .mark )
341
+ store_legacy_markinfo (func , self .mark )
342
+ store_mark (func , self .mark )
349
343
return func
350
344
351
345
mark = Mark (self .name , args , kwargs )
352
346
return self .__class__ (self .mark .combined_with (mark ))
353
347
348
+ def get_unpacked_marks (obj ):
349
+ """
350
+ obtain the unpacked marks that are stored on a object
351
+ """
352
+ mark_list = getattr (obj , 'pytestmark' , [])
354
353
354
+ if not isinstance (mark_list , list ):
355
+ mark_list = [mark_list ]
356
+ return [
357
+ getattr (mark , 'mark' , mark ) # unpack MarkDecorator
358
+ for mark in mark_list
359
+ ]
355
360
356
361
362
+ def store_mark (obj , mark ):
363
+ """store a Mark on a object
364
+ this is used to implement the Mark declarations/decorators correctly
365
+ """
366
+ assert isinstance (mark , Mark ), mark
367
+ # always reassign name to avoid updating pytestmark
368
+ # in a referene that was only borrowed
369
+ obj .pytestmark = get_unpacked_marks (obj ) + [mark ]
370
+
371
+
372
+ def store_legacy_markinfo (func , mark ):
373
+ """create the legacy MarkInfo objects and put them onto the function
374
+ """
375
+ if not isinstance (mark , Mark ):
376
+ raise TypeError ("got {mark!r} instead of a Mark" .format (mark = mark ))
377
+ holder = getattr (func , mark .name , None )
378
+ if holder is None :
379
+ holder = MarkInfo (mark )
380
+ setattr (func , mark .name , holder )
381
+ else :
382
+ holder .add_mark (mark )
383
+
357
384
358
385
class Mark (namedtuple ('Mark' , 'name, args, kwargs' )):
359
386
@@ -371,9 +398,9 @@ def __init__(self, mark):
371
398
self .combined = mark
372
399
self ._marks = [mark ]
373
400
374
- name = alias ('combined.name' )
375
- args = alias ('combined.args' )
376
- kwargs = alias ('combined.kwargs' )
401
+ name = alias ('combined.name' , warning = MARK_INFO_ATTRIBUTE )
402
+ args = alias ('combined.args' , warning = MARK_INFO_ATTRIBUTE )
403
+ kwargs = alias ('combined.kwargs' , warning = MARK_INFO_ATTRIBUTE )
377
404
378
405
def __repr__ (self ):
379
406
return "<MarkInfo {0!r}>" .format (self .combined )
@@ -389,3 +416,30 @@ def __iter__(self):
389
416
390
417
391
418
MARK_GEN = MarkGenerator ()
419
+
420
+
421
+ def _marked (func , mark ):
422
+ """ Returns True if :func: is already marked with :mark:, False otherwise.
423
+ This can happen if marker is applied to class and the test file is
424
+ invoked more than once.
425
+ """
426
+ try :
427
+ func_mark = getattr (func , mark .name )
428
+ except AttributeError :
429
+ return False
430
+ return mark .args == func_mark .args and mark .kwargs == func_mark .kwargs
431
+
432
+
433
+ def transfer_markers (funcobj , cls , mod ):
434
+ """
435
+ this function transfers class level markers and module level markers
436
+ into function level markinfo objects
437
+
438
+ this is the main reason why marks are so broken
439
+ the resolution will involve phasing out function level MarkInfo objects
440
+
441
+ """
442
+ for obj in (cls , mod ):
443
+ for mark in get_unpacked_marks (obj ):
444
+ if not _marked (funcobj , mark ):
445
+ store_legacy_markinfo (funcobj , mark )
0 commit comments