This repository was archived by the owner on Feb 8, 2019. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathnose_randomly.py
206 lines (164 loc) · 6.6 KB
/
nose_randomly.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# -*- coding: utf-8 -*-
from __future__ import division, print_function, unicode_literals
import random
import sys
import time
from nose.plugins import Plugin
from nose.suite import ContextList
# factory-boy
try:
from factory.fuzzy import set_random_state as factory_set_random_state
have_factory_boy = True
except ImportError:
have_factory_boy = False
# fake-factory
try:
from faker.generator import random as faker_random
have_faker = True
except ImportError:
have_faker = False
try:
from numpy import random as np_random
have_numpy = True
except ImportError:
have_numpy = False
# Compat
if sys.version_info[0] == 2: # Python 2
map_return_type = list
else:
map_return_type = map
__version__ = '1.2.6'
class RandomlyPlugin(Plugin):
name = str('randomly')
score = 10000 # Ensure randomly's logic is executed first
def options(self, parser, env):
"""Register commandline options.
"""
super(RandomlyPlugin, self).options(parser, env)
parser.add_option(
str('--randomly-seed'), action='store', dest='seed',
default=int(time.time()), type=int,
help="""Set the seed that nose-randomly uses. Default behaviour:
use time.time()"""
)
parser.add_option(
str('--randomly-dont-shuffle-modules'), action='store_false',
dest='shuffle_modules', default=True,
help="Stop nose-randomly from shuffling the tests inside modules"
)
parser.add_option(
str('--randomly-dont-shuffle-cases'), action='store_false',
dest='shuffle_cases', default=True,
help="""Stop nose-randomly from shuffling the tests inside TestCase
classes"""
)
parser.add_option(
str('--randomly-dont-reset-seed'), action='store_false',
dest='reset_seed', default=True,
help="""Stop nose-randomly from resetting random.seed() at the
start of every test context (TestCase) and test."""
)
def configure(self, options, conf):
"""
Configure plugin.
"""
super(RandomlyPlugin, self).configure(options, conf)
if not self.enabled:
return
self.options = options
def setOutputStream(self, stream):
if not self.enabled:
return
self.output_stream = stream
print(
"Using --randomly-seed={seed}".format(seed=self.options.seed),
file=self.output_stream
)
def startContext(self, context):
self.reset_random_seed()
def startTest(self, test):
self.reset_random_seed()
def reset_random_seed(self):
if not self.enabled:
return
if self.options.reset_seed:
random.setstate(self.random_state)
if have_factory_boy:
factory_set_random_state(self.random_state)
if have_faker:
faker_random.setstate(self.random_state)
if have_numpy:
np_random.set_state(self.random_state_numpy)
@property
def random_state(self):
if not hasattr(self, '_random_state'):
random.seed(self.options.seed)
self._random_state = random.getstate()
return self._random_state
@property
def random_state_numpy(self):
# numpy uses its own random state implementation.
if not have_numpy:
raise RuntimeError('numpy not installed')
if not hasattr(self, '_random_state_numpy'):
np_random.seed(self.options.seed)
self._random_state_numpy = np_random.get_state()
return self._random_state_numpy
def prepareTestLoader(self, loader):
"""
Randomize the order of tests loaded from modules and from classes.
This is a hack. We take the class of the existing, passed in loader
(normally nose.loader.Loader) and subclass it to monkey-patch in
shuffle calls for module and case loading when they requested, and then
mutate the existing test loader's class to this new subclass.
This is somewhat horrible, but nose's plugin infrastructure isn't so
flexible - there is no way to wrap just the loader without risking
interfering with other plugins (if you return anything, no other plugin
may do anything to the loader).
"""
if not self.enabled:
return
options = self.options
class ShuffledLoader(loader.__class__):
def loadTestsFromModule(self, *args, **kwargs):
"""
Temporarily wrap self.suiteClass with a function that shuffles
any ContextList instances that the super() call will pass it.
"""
if options.shuffle_modules:
orig_suiteClass = self.suiteClass
def hackSuiteClass(tests, **kwargs):
if isinstance(tests, ContextList):
random.seed(options.seed)
random.shuffle(tests.tests)
return orig_suiteClass(tests, **kwargs)
self.suiteClass = hackSuiteClass
suite = super(ShuffledLoader, self).loadTestsFromModule(
*args, **kwargs)
if options.shuffle_modules:
self.suiteClass = orig_suiteClass
return suite
def loadTestsFromTestCase(self, testCaseClass):
"""
Temporarily wrap self.suiteClass with a function that shuffles
any list of tests that the super() call will pass it.
"""
if options.shuffle_cases:
orig_suiteClass = self.suiteClass
def hackSuiteClass(tests, **kwargs):
if isinstance(tests, map_return_type):
tests = list(tests)
random.seed(options.seed)
random.shuffle(tests)
return orig_suiteClass(tests, **kwargs)
self.suiteClass = hackSuiteClass
suite = super(ShuffledLoader, self).loadTestsFromTestCase(
testCaseClass)
if options.shuffle_cases:
self.suiteClass = orig_suiteClass
return suite
# Directly mutate the class of loader... eww
loader.__class__ = ShuffledLoader
# Tell the plugin infrastructure we did nothing so 'loader', as mutated
# above, continues to be used
return None