##// END OF EJS Templates
Add test decorator onlyif_unicode_paths....
Thomas Kluyver -
Show More
@@ -1,68 +1,71 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for IPython.core.application"""
2 """Tests for IPython.core.application"""
3
3
4 import os
4 import os
5 import tempfile
5 import tempfile
6
6
7 from IPython.core.application import Application
7 from IPython.core.application import Application
8 from IPython.testing import decorators as testdec
8
9
10 @testdec.onlyif_unicode_paths
9 def test_unicode_cwd():
11 def test_unicode_cwd():
10 """Check that IPython starts with non-ascii characters in the path."""
12 """Check that IPython starts with non-ascii characters in the path."""
11 wd = tempfile.mkdtemp(suffix=u"€")
13 wd = tempfile.mkdtemp(suffix=u"€")
12
14
13 old_wd = os.getcwdu()
15 old_wd = os.getcwdu()
14 os.chdir(wd)
16 os.chdir(wd)
15 #raise Exception(repr(os.getcwd()))
17 #raise Exception(repr(os.getcwd()))
16 try:
18 try:
17 app = Application()
19 app = Application()
18 # The lines below are copied from Application.initialize()
20 # The lines below are copied from Application.initialize()
19 app.create_default_config()
21 app.create_default_config()
20 app.log_default_config()
22 app.log_default_config()
21 app.set_default_config_log_level()
23 app.set_default_config_log_level()
22
24
23 # Find resources needed for filesystem access, using information from
25 # Find resources needed for filesystem access, using information from
24 # the above two
26 # the above two
25 app.find_ipython_dir()
27 app.find_ipython_dir()
26 app.find_resources()
28 app.find_resources()
27 app.find_config_file_name()
29 app.find_config_file_name()
28 app.find_config_file_paths()
30 app.find_config_file_paths()
29
31
30 # File-based config
32 # File-based config
31 app.pre_load_file_config()
33 app.pre_load_file_config()
32 app.load_file_config(suppress_errors=False)
34 app.load_file_config(suppress_errors=False)
33 finally:
35 finally:
34 os.chdir(old_wd)
36 os.chdir(old_wd)
35
37
38 @testdec.onlyif_unicode_paths
36 def test_unicode_ipdir():
39 def test_unicode_ipdir():
37 """Check that IPython starts with non-ascii characters in the IP dir."""
40 """Check that IPython starts with non-ascii characters in the IP dir."""
38 ipdir = tempfile.mkdtemp(suffix=u"€")
41 ipdir = tempfile.mkdtemp(suffix=u"€")
39
42
40 # Create the config file, so it tries to load it.
43 # Create the config file, so it tries to load it.
41 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
44 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
42 pass
45 pass
43
46
44 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
47 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
45 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
48 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
46 os.environ["IPYTHONDIR"] = ipdir.encode("utf-8")
49 os.environ["IPYTHONDIR"] = ipdir.encode("utf-8")
47 try:
50 try:
48 app = Application()
51 app = Application()
49 # The lines below are copied from Application.initialize()
52 # The lines below are copied from Application.initialize()
50 app.create_default_config()
53 app.create_default_config()
51 app.log_default_config()
54 app.log_default_config()
52 app.set_default_config_log_level()
55 app.set_default_config_log_level()
53
56
54 # Find resources needed for filesystem access, using information from
57 # Find resources needed for filesystem access, using information from
55 # the above two
58 # the above two
56 app.find_ipython_dir()
59 app.find_ipython_dir()
57 app.find_resources()
60 app.find_resources()
58 app.find_config_file_name()
61 app.find_config_file_name()
59 app.find_config_file_paths()
62 app.find_config_file_paths()
60
63
61 # File-based config
64 # File-based config
62 app.pre_load_file_config()
65 app.pre_load_file_config()
63 app.load_file_config(suppress_errors=False)
66 app.load_file_config(suppress_errors=False)
64 finally:
67 finally:
65 if old_ipdir1:
68 if old_ipdir1:
66 os.environ["IPYTHONDIR"] = old_ipdir1
69 os.environ["IPYTHONDIR"] = old_ipdir1
67 if old_ipdir2:
70 if old_ipdir2:
68 os.environ["IPYTHONDIR"] = old_ipdir2
71 os.environ["IPYTHONDIR"] = old_ipdir2
@@ -1,320 +1,333 b''
1 """Decorators for labeling test objects.
1 """Decorators for labeling test objects.
2
2
3 Decorators that merely return a modified version of the original function
3 Decorators that merely return a modified version of the original function
4 object are straightforward. Decorators that return a new function object need
4 object are straightforward. Decorators that return a new function object need
5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 decorator, in order to preserve metadata such as function name, setup and
6 decorator, in order to preserve metadata such as function name, setup and
7 teardown functions and so on - see nose.tools for more information.
7 teardown functions and so on - see nose.tools for more information.
8
8
9 This module provides a set of useful decorators meant to be ready to use in
9 This module provides a set of useful decorators meant to be ready to use in
10 your own tests. See the bottom of the file for the ready-made ones, and if you
10 your own tests. See the bottom of the file for the ready-made ones, and if you
11 find yourself writing a new one that may be of generic use, add it here.
11 find yourself writing a new one that may be of generic use, add it here.
12
12
13 Included decorators:
13 Included decorators:
14
14
15
15
16 Lightweight testing that remains unittest-compatible.
16 Lightweight testing that remains unittest-compatible.
17
17
18 - @parametric, for parametric test support that is vastly easier to use than
18 - @parametric, for parametric test support that is vastly easier to use than
19 nose's for debugging. With ours, if a test fails, the stack under inspection
19 nose's for debugging. With ours, if a test fails, the stack under inspection
20 is that of the test and not that of the test framework.
20 is that of the test and not that of the test framework.
21
21
22 - An @as_unittest decorator can be used to tag any normal parameter-less
22 - An @as_unittest decorator can be used to tag any normal parameter-less
23 function as a unittest TestCase. Then, both nose and normal unittest will
23 function as a unittest TestCase. Then, both nose and normal unittest will
24 recognize it as such. This will make it easier to migrate away from Nose if
24 recognize it as such. This will make it easier to migrate away from Nose if
25 we ever need/want to while maintaining very lightweight tests.
25 we ever need/want to while maintaining very lightweight tests.
26
26
27 NOTE: This file contains IPython-specific decorators. Using the machinery in
27 NOTE: This file contains IPython-specific decorators. Using the machinery in
28 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
28 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
29 available, OR use equivalent code in IPython.external._decorators, which
29 available, OR use equivalent code in IPython.external._decorators, which
30 we've copied verbatim from numpy.
30 we've copied verbatim from numpy.
31
31
32 Authors
32 Authors
33 -------
33 -------
34
34
35 - Fernando Perez <Fernando.Perez@berkeley.edu>
35 - Fernando Perez <Fernando.Perez@berkeley.edu>
36 """
36 """
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Copyright (C) 2009-2010 The IPython Development Team
39 # Copyright (C) 2009-2010 The IPython Development Team
40 #
40 #
41 # Distributed under the terms of the BSD License. The full license is in
41 # Distributed under the terms of the BSD License. The full license is in
42 # the file COPYING, distributed as part of this software.
42 # the file COPYING, distributed as part of this software.
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Imports
46 # Imports
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 # Stdlib imports
49 # Stdlib imports
50 import inspect
50 import inspect
51 import sys
51 import sys
52 import tempfile
52 import unittest
53 import unittest
53
54
54 # Third-party imports
55 # Third-party imports
55
56
56 # This is Michele Simionato's decorator module, kept verbatim.
57 # This is Michele Simionato's decorator module, kept verbatim.
57 from IPython.external.decorator import decorator, update_wrapper
58 from IPython.external.decorator import decorator, update_wrapper
58
59
59 # We already have python3-compliant code for parametric tests
60 # We already have python3-compliant code for parametric tests
60 if sys.version[0]=='2':
61 if sys.version[0]=='2':
61 from _paramtestpy2 import parametric, ParametricTestCase
62 from _paramtestpy2 import parametric, ParametricTestCase
62 else:
63 else:
63 from _paramtestpy3 import parametric, ParametricTestCase
64 from _paramtestpy3 import parametric, ParametricTestCase
64
65
65 # Expose the unittest-driven decorators
66 # Expose the unittest-driven decorators
66 from ipunittest import ipdoctest, ipdocstring
67 from ipunittest import ipdoctest, ipdocstring
67
68
68 # Grab the numpy-specific decorators which we keep in a file that we
69 # Grab the numpy-specific decorators which we keep in a file that we
69 # occasionally update from upstream: decorators.py is a copy of
70 # occasionally update from upstream: decorators.py is a copy of
70 # numpy.testing.decorators, we expose all of it here.
71 # numpy.testing.decorators, we expose all of it here.
71 from IPython.external.decorators import *
72 from IPython.external.decorators import *
72
73
73 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
74 # Classes and functions
75 # Classes and functions
75 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
76
77
77 # Simple example of the basic idea
78 # Simple example of the basic idea
78 def as_unittest(func):
79 def as_unittest(func):
79 """Decorator to make a simple function into a normal test via unittest."""
80 """Decorator to make a simple function into a normal test via unittest."""
80 class Tester(unittest.TestCase):
81 class Tester(unittest.TestCase):
81 def test(self):
82 def test(self):
82 func()
83 func()
83
84
84 Tester.__name__ = func.__name__
85 Tester.__name__ = func.__name__
85
86
86 return Tester
87 return Tester
87
88
88 # Utility functions
89 # Utility functions
89
90
90 def apply_wrapper(wrapper,func):
91 def apply_wrapper(wrapper,func):
91 """Apply a wrapper to a function for decoration.
92 """Apply a wrapper to a function for decoration.
92
93
93 This mixes Michele Simionato's decorator tool with nose's make_decorator,
94 This mixes Michele Simionato's decorator tool with nose's make_decorator,
94 to apply a wrapper in a decorator so that all nose attributes, as well as
95 to apply a wrapper in a decorator so that all nose attributes, as well as
95 function signature and other properties, survive the decoration cleanly.
96 function signature and other properties, survive the decoration cleanly.
96 This will ensure that wrapped functions can still be well introspected via
97 This will ensure that wrapped functions can still be well introspected via
97 IPython, for example.
98 IPython, for example.
98 """
99 """
99 import nose.tools
100 import nose.tools
100
101
101 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
102 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
102
103
103
104
104 def make_label_dec(label,ds=None):
105 def make_label_dec(label,ds=None):
105 """Factory function to create a decorator that applies one or more labels.
106 """Factory function to create a decorator that applies one or more labels.
106
107
107 Parameters
108 Parameters
108 ----------
109 ----------
109 label : string or sequence
110 label : string or sequence
110 One or more labels that will be applied by the decorator to the functions
111 One or more labels that will be applied by the decorator to the functions
111 it decorates. Labels are attributes of the decorated function with their
112 it decorates. Labels are attributes of the decorated function with their
112 value set to True.
113 value set to True.
113
114
114 ds : string
115 ds : string
115 An optional docstring for the resulting decorator. If not given, a
116 An optional docstring for the resulting decorator. If not given, a
116 default docstring is auto-generated.
117 default docstring is auto-generated.
117
118
118 Returns
119 Returns
119 -------
120 -------
120 A decorator.
121 A decorator.
121
122
122 Examples
123 Examples
123 --------
124 --------
124
125
125 A simple labeling decorator:
126 A simple labeling decorator:
126 >>> slow = make_label_dec('slow')
127 >>> slow = make_label_dec('slow')
127 >>> print slow.__doc__
128 >>> print slow.__doc__
128 Labels a test as 'slow'.
129 Labels a test as 'slow'.
129
130
130 And one that uses multiple labels and a custom docstring:
131 And one that uses multiple labels and a custom docstring:
131 >>> rare = make_label_dec(['slow','hard'],
132 >>> rare = make_label_dec(['slow','hard'],
132 ... "Mix labels 'slow' and 'hard' for rare tests.")
133 ... "Mix labels 'slow' and 'hard' for rare tests.")
133 >>> print rare.__doc__
134 >>> print rare.__doc__
134 Mix labels 'slow' and 'hard' for rare tests.
135 Mix labels 'slow' and 'hard' for rare tests.
135
136
136 Now, let's test using this one:
137 Now, let's test using this one:
137 >>> @rare
138 >>> @rare
138 ... def f(): pass
139 ... def f(): pass
139 ...
140 ...
140 >>>
141 >>>
141 >>> f.slow
142 >>> f.slow
142 True
143 True
143 >>> f.hard
144 >>> f.hard
144 True
145 True
145 """
146 """
146
147
147 if isinstance(label,basestring):
148 if isinstance(label,basestring):
148 labels = [label]
149 labels = [label]
149 else:
150 else:
150 labels = label
151 labels = label
151
152
152 # Validate that the given label(s) are OK for use in setattr() by doing a
153 # Validate that the given label(s) are OK for use in setattr() by doing a
153 # dry run on a dummy function.
154 # dry run on a dummy function.
154 tmp = lambda : None
155 tmp = lambda : None
155 for label in labels:
156 for label in labels:
156 setattr(tmp,label,True)
157 setattr(tmp,label,True)
157
158
158 # This is the actual decorator we'll return
159 # This is the actual decorator we'll return
159 def decor(f):
160 def decor(f):
160 for label in labels:
161 for label in labels:
161 setattr(f,label,True)
162 setattr(f,label,True)
162 return f
163 return f
163
164
164 # Apply the user's docstring, or autogenerate a basic one
165 # Apply the user's docstring, or autogenerate a basic one
165 if ds is None:
166 if ds is None:
166 ds = "Labels a test as %r." % label
167 ds = "Labels a test as %r." % label
167 decor.__doc__ = ds
168 decor.__doc__ = ds
168
169
169 return decor
170 return decor
170
171
171
172
172 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
173 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
173 # preserve function metadata better and allows the skip condition to be a
174 # preserve function metadata better and allows the skip condition to be a
174 # callable.
175 # callable.
175 def skipif(skip_condition, msg=None):
176 def skipif(skip_condition, msg=None):
176 ''' Make function raise SkipTest exception if skip_condition is true
177 ''' Make function raise SkipTest exception if skip_condition is true
177
178
178 Parameters
179 Parameters
179 ----------
180 ----------
180 skip_condition : bool or callable.
181 skip_condition : bool or callable.
181 Flag to determine whether to skip test. If the condition is a
182 Flag to determine whether to skip test. If the condition is a
182 callable, it is used at runtime to dynamically make the decision. This
183 callable, it is used at runtime to dynamically make the decision. This
183 is useful for tests that may require costly imports, to delay the cost
184 is useful for tests that may require costly imports, to delay the cost
184 until the test suite is actually executed.
185 until the test suite is actually executed.
185 msg : string
186 msg : string
186 Message to give on raising a SkipTest exception
187 Message to give on raising a SkipTest exception
187
188
188 Returns
189 Returns
189 -------
190 -------
190 decorator : function
191 decorator : function
191 Decorator, which, when applied to a function, causes SkipTest
192 Decorator, which, when applied to a function, causes SkipTest
192 to be raised when the skip_condition was True, and the function
193 to be raised when the skip_condition was True, and the function
193 to be called normally otherwise.
194 to be called normally otherwise.
194
195
195 Notes
196 Notes
196 -----
197 -----
197 You will see from the code that we had to further decorate the
198 You will see from the code that we had to further decorate the
198 decorator with the nose.tools.make_decorator function in order to
199 decorator with the nose.tools.make_decorator function in order to
199 transmit function name, and various other metadata.
200 transmit function name, and various other metadata.
200 '''
201 '''
201
202
202 def skip_decorator(f):
203 def skip_decorator(f):
203 # Local import to avoid a hard nose dependency and only incur the
204 # Local import to avoid a hard nose dependency and only incur the
204 # import time overhead at actual test-time.
205 # import time overhead at actual test-time.
205 import nose
206 import nose
206
207
207 # Allow for both boolean or callable skip conditions.
208 # Allow for both boolean or callable skip conditions.
208 if callable(skip_condition):
209 if callable(skip_condition):
209 skip_val = skip_condition
210 skip_val = skip_condition
210 else:
211 else:
211 skip_val = lambda : skip_condition
212 skip_val = lambda : skip_condition
212
213
213 def get_msg(func,msg=None):
214 def get_msg(func,msg=None):
214 """Skip message with information about function being skipped."""
215 """Skip message with information about function being skipped."""
215 if msg is None: out = 'Test skipped due to test condition.'
216 if msg is None: out = 'Test skipped due to test condition.'
216 else: out = msg
217 else: out = msg
217 return "Skipping test: %s. %s" % (func.__name__,out)
218 return "Skipping test: %s. %s" % (func.__name__,out)
218
219
219 # We need to define *two* skippers because Python doesn't allow both
220 # We need to define *two* skippers because Python doesn't allow both
220 # return with value and yield inside the same function.
221 # return with value and yield inside the same function.
221 def skipper_func(*args, **kwargs):
222 def skipper_func(*args, **kwargs):
222 """Skipper for normal test functions."""
223 """Skipper for normal test functions."""
223 if skip_val():
224 if skip_val():
224 raise nose.SkipTest(get_msg(f,msg))
225 raise nose.SkipTest(get_msg(f,msg))
225 else:
226 else:
226 return f(*args, **kwargs)
227 return f(*args, **kwargs)
227
228
228 def skipper_gen(*args, **kwargs):
229 def skipper_gen(*args, **kwargs):
229 """Skipper for test generators."""
230 """Skipper for test generators."""
230 if skip_val():
231 if skip_val():
231 raise nose.SkipTest(get_msg(f,msg))
232 raise nose.SkipTest(get_msg(f,msg))
232 else:
233 else:
233 for x in f(*args, **kwargs):
234 for x in f(*args, **kwargs):
234 yield x
235 yield x
235
236
236 # Choose the right skipper to use when building the actual generator.
237 # Choose the right skipper to use when building the actual generator.
237 if nose.util.isgenerator(f):
238 if nose.util.isgenerator(f):
238 skipper = skipper_gen
239 skipper = skipper_gen
239 else:
240 else:
240 skipper = skipper_func
241 skipper = skipper_func
241
242
242 return nose.tools.make_decorator(f)(skipper)
243 return nose.tools.make_decorator(f)(skipper)
243
244
244 return skip_decorator
245 return skip_decorator
245
246
246 # A version with the condition set to true, common case just to attacha message
247 # A version with the condition set to true, common case just to attacha message
247 # to a skip decorator
248 # to a skip decorator
248 def skip(msg=None):
249 def skip(msg=None):
249 """Decorator factory - mark a test function for skipping from test suite.
250 """Decorator factory - mark a test function for skipping from test suite.
250
251
251 Parameters
252 Parameters
252 ----------
253 ----------
253 msg : string
254 msg : string
254 Optional message to be added.
255 Optional message to be added.
255
256
256 Returns
257 Returns
257 -------
258 -------
258 decorator : function
259 decorator : function
259 Decorator, which, when applied to a function, causes SkipTest
260 Decorator, which, when applied to a function, causes SkipTest
260 to be raised, with the optional message added.
261 to be raised, with the optional message added.
261 """
262 """
262
263
263 return skipif(True,msg)
264 return skipif(True,msg)
264
265
265
266
266 def onlyif(condition, msg):
267 def onlyif(condition, msg):
267 """The reverse from skipif, see skipif for details."""
268 """The reverse from skipif, see skipif for details."""
268
269
269 if callable(condition):
270 if callable(condition):
270 skip_condition = lambda : not condition()
271 skip_condition = lambda : not condition()
271 else:
272 else:
272 skip_condition = lambda : not condition
273 skip_condition = lambda : not condition
273
274
274 return skipif(skip_condition, msg)
275 return skipif(skip_condition, msg)
275
276
276 #-----------------------------------------------------------------------------
277 #-----------------------------------------------------------------------------
277 # Utility functions for decorators
278 # Utility functions for decorators
278 def module_not_available(module):
279 def module_not_available(module):
279 """Can module be imported? Returns true if module does NOT import.
280 """Can module be imported? Returns true if module does NOT import.
280
281
281 This is used to make a decorator to skip tests that require module to be
282 This is used to make a decorator to skip tests that require module to be
282 available, but delay the 'import numpy' to test execution time.
283 available, but delay the 'import numpy' to test execution time.
283 """
284 """
284 try:
285 try:
285 mod = __import__(module)
286 mod = __import__(module)
286 mod_not_avail = False
287 mod_not_avail = False
287 except ImportError:
288 except ImportError:
288 mod_not_avail = True
289 mod_not_avail = True
289
290
290 return mod_not_avail
291 return mod_not_avail
291
292
292 #-----------------------------------------------------------------------------
293 #-----------------------------------------------------------------------------
293 # Decorators for public use
294 # Decorators for public use
294
295
295 # Decorators to skip certain tests on specific platforms.
296 # Decorators to skip certain tests on specific platforms.
296 skip_win32 = skipif(sys.platform == 'win32',
297 skip_win32 = skipif(sys.platform == 'win32',
297 "This test does not run under Windows")
298 "This test does not run under Windows")
298 skip_linux = skipif(sys.platform == 'linux2',
299 skip_linux = skipif(sys.platform == 'linux2',
299 "This test does not run under Linux")
300 "This test does not run under Linux")
300 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
301 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
301
302
302
303
303 # Decorators to skip tests if not on specific platforms.
304 # Decorators to skip tests if not on specific platforms.
304 skip_if_not_win32 = skipif(sys.platform != 'win32',
305 skip_if_not_win32 = skipif(sys.platform != 'win32',
305 "This test only runs under Windows")
306 "This test only runs under Windows")
306 skip_if_not_linux = skipif(sys.platform != 'linux2',
307 skip_if_not_linux = skipif(sys.platform != 'linux2',
307 "This test only runs under Linux")
308 "This test only runs under Linux")
308 skip_if_not_osx = skipif(sys.platform != 'darwin',
309 skip_if_not_osx = skipif(sys.platform != 'darwin',
309 "This test only runs under OSX")
310 "This test only runs under OSX")
310
311
311 # Other skip decorators
312 # Other skip decorators
312 skipif_not_numpy = skipif(module_not_available('numpy'),"This test requires numpy")
313 skipif_not_numpy = skipif(module_not_available('numpy'),"This test requires numpy")
313
314
314 skipif_not_sympy = skipif(module_not_available('sympy'),"This test requires sympy")
315 skipif_not_sympy = skipif(module_not_available('sympy'),"This test requires sympy")
315
316
316 skip_known_failure = knownfailureif(True,'This test is known to fail')
317 skip_known_failure = knownfailureif(True,'This test is known to fail')
317
318
318 # A null 'decorator', useful to make more readable code that needs to pick
319 # A null 'decorator', useful to make more readable code that needs to pick
319 # between different decorators based on OS or other conditions
320 # between different decorators based on OS or other conditions
320 null_deco = lambda f: f
321 null_deco = lambda f: f
322
323 # Some tests only run where we can use unicode paths. Note that we can't just
324 # check os.path.supports_unicode_filenames, which is always False on Linux.
325 try:
326 tempfile.mkdtemp(u"€")
327 except UnicodeEncodeError:
328 unicode_paths = False
329 else:
330 unicode_paths = True
331
332 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
333 "where we can use unicode in filenames."))
General Comments 0
You need to be logged in to leave comments. Login now