##// END OF EJS Templates
plural error message
MinRK -
Show More
@@ -1,400 +1,400 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 Authors
29 Authors
30 -------
30 -------
31
31
32 - Fernando Perez <Fernando.Perez@berkeley.edu>
32 - Fernando Perez <Fernando.Perez@berkeley.edu>
33 """
33 """
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Copyright (C) 2009-2011 The IPython Development Team
36 # Copyright (C) 2009-2011 The IPython Development Team
37 #
37 #
38 # Distributed under the terms of the BSD License. The full license is in
38 # Distributed under the terms of the BSD License. The full license is in
39 # the file COPYING, distributed as part of this software.
39 # the file COPYING, distributed as part of this software.
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Imports
43 # Imports
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 # Stdlib imports
46 # Stdlib imports
47 import sys
47 import sys
48 import os
48 import os
49 import tempfile
49 import tempfile
50 import unittest
50 import unittest
51
51
52 # Third-party imports
52 # Third-party imports
53
53
54 # This is Michele Simionato's decorator module, kept verbatim.
54 # This is Michele Simionato's decorator module, kept verbatim.
55 from IPython.external.decorator import decorator
55 from IPython.external.decorator import decorator
56
56
57 # Expose the unittest-driven decorators
57 # Expose the unittest-driven decorators
58 from .ipunittest import ipdoctest, ipdocstring
58 from .ipunittest import ipdoctest, ipdocstring
59
59
60 # Grab the numpy-specific decorators which we keep in a file that we
60 # Grab the numpy-specific decorators which we keep in a file that we
61 # occasionally update from upstream: decorators.py is a copy of
61 # occasionally update from upstream: decorators.py is a copy of
62 # numpy.testing.decorators, we expose all of it here.
62 # numpy.testing.decorators, we expose all of it here.
63 from IPython.external.decorators import *
63 from IPython.external.decorators import *
64
64
65 # For onlyif_cmd_exists decorator
65 # For onlyif_cmd_exists decorator
66 from IPython.utils.process import is_cmd_found
66 from IPython.utils.process import is_cmd_found
67 from IPython.utils.py3compat import string_types
67 from IPython.utils.py3compat import string_types
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Classes and functions
70 # Classes and functions
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73 # Simple example of the basic idea
73 # Simple example of the basic idea
74 def as_unittest(func):
74 def as_unittest(func):
75 """Decorator to make a simple function into a normal test via unittest."""
75 """Decorator to make a simple function into a normal test via unittest."""
76 class Tester(unittest.TestCase):
76 class Tester(unittest.TestCase):
77 def test(self):
77 def test(self):
78 func()
78 func()
79
79
80 Tester.__name__ = func.__name__
80 Tester.__name__ = func.__name__
81
81
82 return Tester
82 return Tester
83
83
84 # Utility functions
84 # Utility functions
85
85
86 def apply_wrapper(wrapper,func):
86 def apply_wrapper(wrapper,func):
87 """Apply a wrapper to a function for decoration.
87 """Apply a wrapper to a function for decoration.
88
88
89 This mixes Michele Simionato's decorator tool with nose's make_decorator,
89 This mixes Michele Simionato's decorator tool with nose's make_decorator,
90 to apply a wrapper in a decorator so that all nose attributes, as well as
90 to apply a wrapper in a decorator so that all nose attributes, as well as
91 function signature and other properties, survive the decoration cleanly.
91 function signature and other properties, survive the decoration cleanly.
92 This will ensure that wrapped functions can still be well introspected via
92 This will ensure that wrapped functions can still be well introspected via
93 IPython, for example.
93 IPython, for example.
94 """
94 """
95 import nose.tools
95 import nose.tools
96
96
97 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
97 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
98
98
99
99
100 def make_label_dec(label,ds=None):
100 def make_label_dec(label,ds=None):
101 """Factory function to create a decorator that applies one or more labels.
101 """Factory function to create a decorator that applies one or more labels.
102
102
103 Parameters
103 Parameters
104 ----------
104 ----------
105 label : string or sequence
105 label : string or sequence
106 One or more labels that will be applied by the decorator to the functions
106 One or more labels that will be applied by the decorator to the functions
107 it decorates. Labels are attributes of the decorated function with their
107 it decorates. Labels are attributes of the decorated function with their
108 value set to True.
108 value set to True.
109
109
110 ds : string
110 ds : string
111 An optional docstring for the resulting decorator. If not given, a
111 An optional docstring for the resulting decorator. If not given, a
112 default docstring is auto-generated.
112 default docstring is auto-generated.
113
113
114 Returns
114 Returns
115 -------
115 -------
116 A decorator.
116 A decorator.
117
117
118 Examples
118 Examples
119 --------
119 --------
120
120
121 A simple labeling decorator:
121 A simple labeling decorator:
122
122
123 >>> slow = make_label_dec('slow')
123 >>> slow = make_label_dec('slow')
124 >>> slow.__doc__
124 >>> slow.__doc__
125 "Labels a test as 'slow'."
125 "Labels a test as 'slow'."
126
126
127 And one that uses multiple labels and a custom docstring:
127 And one that uses multiple labels and a custom docstring:
128
128
129 >>> rare = make_label_dec(['slow','hard'],
129 >>> rare = make_label_dec(['slow','hard'],
130 ... "Mix labels 'slow' and 'hard' for rare tests.")
130 ... "Mix labels 'slow' and 'hard' for rare tests.")
131 >>> rare.__doc__
131 >>> rare.__doc__
132 "Mix labels 'slow' and 'hard' for rare tests."
132 "Mix labels 'slow' and 'hard' for rare tests."
133
133
134 Now, let's test using this one:
134 Now, let's test using this one:
135 >>> @rare
135 >>> @rare
136 ... def f(): pass
136 ... def f(): pass
137 ...
137 ...
138 >>>
138 >>>
139 >>> f.slow
139 >>> f.slow
140 True
140 True
141 >>> f.hard
141 >>> f.hard
142 True
142 True
143 """
143 """
144
144
145 if isinstance(label, string_types):
145 if isinstance(label, string_types):
146 labels = [label]
146 labels = [label]
147 else:
147 else:
148 labels = label
148 labels = label
149
149
150 # Validate that the given label(s) are OK for use in setattr() by doing a
150 # Validate that the given label(s) are OK for use in setattr() by doing a
151 # dry run on a dummy function.
151 # dry run on a dummy function.
152 tmp = lambda : None
152 tmp = lambda : None
153 for label in labels:
153 for label in labels:
154 setattr(tmp,label,True)
154 setattr(tmp,label,True)
155
155
156 # This is the actual decorator we'll return
156 # This is the actual decorator we'll return
157 def decor(f):
157 def decor(f):
158 for label in labels:
158 for label in labels:
159 setattr(f,label,True)
159 setattr(f,label,True)
160 return f
160 return f
161
161
162 # Apply the user's docstring, or autogenerate a basic one
162 # Apply the user's docstring, or autogenerate a basic one
163 if ds is None:
163 if ds is None:
164 ds = "Labels a test as %r." % label
164 ds = "Labels a test as %r." % label
165 decor.__doc__ = ds
165 decor.__doc__ = ds
166
166
167 return decor
167 return decor
168
168
169
169
170 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
170 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
171 # preserve function metadata better and allows the skip condition to be a
171 # preserve function metadata better and allows the skip condition to be a
172 # callable.
172 # callable.
173 def skipif(skip_condition, msg=None):
173 def skipif(skip_condition, msg=None):
174 ''' Make function raise SkipTest exception if skip_condition is true
174 ''' Make function raise SkipTest exception if skip_condition is true
175
175
176 Parameters
176 Parameters
177 ----------
177 ----------
178
178
179 skip_condition : bool or callable
179 skip_condition : bool or callable
180 Flag to determine whether to skip test. If the condition is a
180 Flag to determine whether to skip test. If the condition is a
181 callable, it is used at runtime to dynamically make the decision. This
181 callable, it is used at runtime to dynamically make the decision. This
182 is useful for tests that may require costly imports, to delay the cost
182 is useful for tests that may require costly imports, to delay the cost
183 until the test suite is actually executed.
183 until the test suite is actually executed.
184 msg : string
184 msg : string
185 Message to give on raising a SkipTest exception.
185 Message to give on raising a SkipTest exception.
186
186
187 Returns
187 Returns
188 -------
188 -------
189 decorator : function
189 decorator : function
190 Decorator, which, when applied to a function, causes SkipTest
190 Decorator, which, when applied to a function, causes SkipTest
191 to be raised when the skip_condition was True, and the function
191 to be raised when the skip_condition was True, and the function
192 to be called normally otherwise.
192 to be called normally otherwise.
193
193
194 Notes
194 Notes
195 -----
195 -----
196 You will see from the code that we had to further decorate the
196 You will see from the code that we had to further decorate the
197 decorator with the nose.tools.make_decorator function in order to
197 decorator with the nose.tools.make_decorator function in order to
198 transmit function name, and various other metadata.
198 transmit function name, and various other metadata.
199 '''
199 '''
200
200
201 def skip_decorator(f):
201 def skip_decorator(f):
202 # Local import to avoid a hard nose dependency and only incur the
202 # Local import to avoid a hard nose dependency and only incur the
203 # import time overhead at actual test-time.
203 # import time overhead at actual test-time.
204 import nose
204 import nose
205
205
206 # Allow for both boolean or callable skip conditions.
206 # Allow for both boolean or callable skip conditions.
207 if callable(skip_condition):
207 if callable(skip_condition):
208 skip_val = skip_condition
208 skip_val = skip_condition
209 else:
209 else:
210 skip_val = lambda : skip_condition
210 skip_val = lambda : skip_condition
211
211
212 def get_msg(func,msg=None):
212 def get_msg(func,msg=None):
213 """Skip message with information about function being skipped."""
213 """Skip message with information about function being skipped."""
214 if msg is None: out = 'Test skipped due to test condition.'
214 if msg is None: out = 'Test skipped due to test condition.'
215 else: out = msg
215 else: out = msg
216 return "Skipping test: %s. %s" % (func.__name__,out)
216 return "Skipping test: %s. %s" % (func.__name__,out)
217
217
218 # We need to define *two* skippers because Python doesn't allow both
218 # We need to define *two* skippers because Python doesn't allow both
219 # return with value and yield inside the same function.
219 # return with value and yield inside the same function.
220 def skipper_func(*args, **kwargs):
220 def skipper_func(*args, **kwargs):
221 """Skipper for normal test functions."""
221 """Skipper for normal test functions."""
222 if skip_val():
222 if skip_val():
223 raise nose.SkipTest(get_msg(f,msg))
223 raise nose.SkipTest(get_msg(f,msg))
224 else:
224 else:
225 return f(*args, **kwargs)
225 return f(*args, **kwargs)
226
226
227 def skipper_gen(*args, **kwargs):
227 def skipper_gen(*args, **kwargs):
228 """Skipper for test generators."""
228 """Skipper for test generators."""
229 if skip_val():
229 if skip_val():
230 raise nose.SkipTest(get_msg(f,msg))
230 raise nose.SkipTest(get_msg(f,msg))
231 else:
231 else:
232 for x in f(*args, **kwargs):
232 for x in f(*args, **kwargs):
233 yield x
233 yield x
234
234
235 # Choose the right skipper to use when building the actual generator.
235 # Choose the right skipper to use when building the actual generator.
236 if nose.util.isgenerator(f):
236 if nose.util.isgenerator(f):
237 skipper = skipper_gen
237 skipper = skipper_gen
238 else:
238 else:
239 skipper = skipper_func
239 skipper = skipper_func
240
240
241 return nose.tools.make_decorator(f)(skipper)
241 return nose.tools.make_decorator(f)(skipper)
242
242
243 return skip_decorator
243 return skip_decorator
244
244
245 # A version with the condition set to true, common case just to attach a message
245 # A version with the condition set to true, common case just to attach a message
246 # to a skip decorator
246 # to a skip decorator
247 def skip(msg=None):
247 def skip(msg=None):
248 """Decorator factory - mark a test function for skipping from test suite.
248 """Decorator factory - mark a test function for skipping from test suite.
249
249
250 Parameters
250 Parameters
251 ----------
251 ----------
252 msg : string
252 msg : string
253 Optional message to be added.
253 Optional message to be added.
254
254
255 Returns
255 Returns
256 -------
256 -------
257 decorator : function
257 decorator : function
258 Decorator, which, when applied to a function, causes SkipTest
258 Decorator, which, when applied to a function, causes SkipTest
259 to be raised, with the optional message added.
259 to be raised, with the optional message added.
260 """
260 """
261
261
262 return skipif(True,msg)
262 return skipif(True,msg)
263
263
264
264
265 def onlyif(condition, msg):
265 def onlyif(condition, msg):
266 """The reverse from skipif, see skipif for details."""
266 """The reverse from skipif, see skipif for details."""
267
267
268 if callable(condition):
268 if callable(condition):
269 skip_condition = lambda : not condition()
269 skip_condition = lambda : not condition()
270 else:
270 else:
271 skip_condition = lambda : not condition
271 skip_condition = lambda : not condition
272
272
273 return skipif(skip_condition, msg)
273 return skipif(skip_condition, msg)
274
274
275 #-----------------------------------------------------------------------------
275 #-----------------------------------------------------------------------------
276 # Utility functions for decorators
276 # Utility functions for decorators
277 def module_not_available(module):
277 def module_not_available(module):
278 """Can module be imported? Returns true if module does NOT import.
278 """Can module be imported? Returns true if module does NOT import.
279
279
280 This is used to make a decorator to skip tests that require module to be
280 This is used to make a decorator to skip tests that require module to be
281 available, but delay the 'import numpy' to test execution time.
281 available, but delay the 'import numpy' to test execution time.
282 """
282 """
283 try:
283 try:
284 mod = __import__(module)
284 mod = __import__(module)
285 mod_not_avail = False
285 mod_not_avail = False
286 except ImportError:
286 except ImportError:
287 mod_not_avail = True
287 mod_not_avail = True
288
288
289 return mod_not_avail
289 return mod_not_avail
290
290
291
291
292 def decorated_dummy(dec, name):
292 def decorated_dummy(dec, name):
293 """Return a dummy function decorated with dec, with the given name.
293 """Return a dummy function decorated with dec, with the given name.
294
294
295 Examples
295 Examples
296 --------
296 --------
297 import IPython.testing.decorators as dec
297 import IPython.testing.decorators as dec
298 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
298 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
299 """
299 """
300 dummy = lambda: None
300 dummy = lambda: None
301 dummy.__name__ = name
301 dummy.__name__ = name
302 return dec(dummy)
302 return dec(dummy)
303
303
304 #-----------------------------------------------------------------------------
304 #-----------------------------------------------------------------------------
305 # Decorators for public use
305 # Decorators for public use
306
306
307 # Decorators to skip certain tests on specific platforms.
307 # Decorators to skip certain tests on specific platforms.
308 skip_win32 = skipif(sys.platform == 'win32',
308 skip_win32 = skipif(sys.platform == 'win32',
309 "This test does not run under Windows")
309 "This test does not run under Windows")
310 skip_linux = skipif(sys.platform.startswith('linux'),
310 skip_linux = skipif(sys.platform.startswith('linux'),
311 "This test does not run under Linux")
311 "This test does not run under Linux")
312 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
312 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
313
313
314
314
315 # Decorators to skip tests if not on specific platforms.
315 # Decorators to skip tests if not on specific platforms.
316 skip_if_not_win32 = skipif(sys.platform != 'win32',
316 skip_if_not_win32 = skipif(sys.platform != 'win32',
317 "This test only runs under Windows")
317 "This test only runs under Windows")
318 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
318 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
319 "This test only runs under Linux")
319 "This test only runs under Linux")
320 skip_if_not_osx = skipif(sys.platform != 'darwin',
320 skip_if_not_osx = skipif(sys.platform != 'darwin',
321 "This test only runs under OSX")
321 "This test only runs under OSX")
322
322
323
323
324 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
324 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
325 os.environ.get('DISPLAY', '') == '')
325 os.environ.get('DISPLAY', '') == '')
326 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
326 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
327
327
328 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
328 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
329
329
330 # not a decorator itself, returns a dummy function to be used as setup
330 # not a decorator itself, returns a dummy function to be used as setup
331 def skip_file_no_x11(name):
331 def skip_file_no_x11(name):
332 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
332 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
333
333
334 # Other skip decorators
334 # Other skip decorators
335
335
336 # generic skip without module
336 # generic skip without module
337 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
337 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
338
338
339 skipif_not_numpy = skip_without('numpy')
339 skipif_not_numpy = skip_without('numpy')
340
340
341 skipif_not_matplotlib = skip_without('matplotlib')
341 skipif_not_matplotlib = skip_without('matplotlib')
342
342
343 skipif_not_sympy = skip_without('sympy')
343 skipif_not_sympy = skip_without('sympy')
344
344
345 skip_known_failure = knownfailureif(True,'This test is known to fail')
345 skip_known_failure = knownfailureif(True,'This test is known to fail')
346
346
347 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
347 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
348 'This test is known to fail on Python 3.')
348 'This test is known to fail on Python 3.')
349
349
350 # A null 'decorator', useful to make more readable code that needs to pick
350 # A null 'decorator', useful to make more readable code that needs to pick
351 # between different decorators based on OS or other conditions
351 # between different decorators based on OS or other conditions
352 null_deco = lambda f: f
352 null_deco = lambda f: f
353
353
354 # Some tests only run where we can use unicode paths. Note that we can't just
354 # Some tests only run where we can use unicode paths. Note that we can't just
355 # check os.path.supports_unicode_filenames, which is always False on Linux.
355 # check os.path.supports_unicode_filenames, which is always False on Linux.
356 try:
356 try:
357 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
357 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
358 except UnicodeEncodeError:
358 except UnicodeEncodeError:
359 unicode_paths = False
359 unicode_paths = False
360 else:
360 else:
361 unicode_paths = True
361 unicode_paths = True
362 f.close()
362 f.close()
363
363
364 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
364 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
365 "where we can use unicode in filenames."))
365 "where we can use unicode in filenames."))
366
366
367
367
368 def onlyif_cmds_exist(*commands):
368 def onlyif_cmds_exist(*commands):
369 """
369 """
370 Decorator to skip test when at least one of `commands` is not found.
370 Decorator to skip test when at least one of `commands` is not found.
371 """
371 """
372 for cmd in commands:
372 for cmd in commands:
373 try:
373 try:
374 if not is_cmd_found(cmd):
374 if not is_cmd_found(cmd):
375 return skip("This test runs only if command '{0}' "
375 return skip("This test runs only if command '{0}' "
376 "is installed".format(cmd))
376 "is installed".format(cmd))
377 except ImportError as e:
377 except ImportError as e:
378 # is_cmd_found uses pywin32 on windows, which might not be available
378 # is_cmd_found uses pywin32 on windows, which might not be available
379 if sys.platform == 'win32' and 'pywin32' in str(e):
379 if sys.platform == 'win32' and 'pywin32' in str(e):
380 return skip("This test runs only if pywin32 and command '{0}' "
380 return skip("This test runs only if pywin32 and command '{0}' "
381 "is installed".format(cmd))
381 "is installed".format(cmd))
382 raise e
382 raise e
383 return null_deco
383 return null_deco
384
384
385 def onlyif_any_cmd_exists(*commands):
385 def onlyif_any_cmd_exists(*commands):
386 """
386 """
387 Decorator to skip test unless at least one of `commands` is found.
387 Decorator to skip test unless at least one of `commands` is found.
388 """
388 """
389 for cmd in commands:
389 for cmd in commands:
390 try:
390 try:
391 if is_cmd_found(cmd):
391 if is_cmd_found(cmd):
392 return null_deco
392 return null_deco
393 except ImportError as e:
393 except ImportError as e:
394 # is_cmd_found uses pywin32 on windows, which might not be available
394 # is_cmd_found uses pywin32 on windows, which might not be available
395 if sys.platform == 'win32' and 'pywin32' in str(e):
395 if sys.platform == 'win32' and 'pywin32' in str(e):
396 return skip("This test runs only if pywin32 and command '{0}' "
396 return skip("This test runs only if pywin32 and commands '{0}' "
397 "is installed".format(cmd))
397 "are installed".format(commands))
398 raise e
398 raise e
399 return skip("This test runs only if one of the commands {0} "
399 return skip("This test runs only if one of the commands {0} "
400 "is installed".format(commands))
400 "is installed".format(commands))
General Comments 0
You need to be logged in to leave comments. Login now