Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(206)

Side by Side Diff: third_party/protobuf/python/google/protobuf/internal/_parameterized.py

Issue 1842653006: Update //third_party/protobuf to version 3. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: update sync unittest and README.chromium Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #! /usr/bin/env python
2 #
3 # Protocol Buffers - Google's data interchange format
4 # Copyright 2008 Google Inc. All rights reserved.
5 # https://developers.google.com/protocol-buffers/
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following disclaimer
15 # in the documentation and/or other materials provided with the
16 # distribution.
17 # * Neither the name of Google Inc. nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33 """Adds support for parameterized tests to Python's unittest TestCase class.
34
35 A parameterized test is a method in a test case that is invoked with different
36 argument tuples.
37
38 A simple example:
39
40 class AdditionExample(parameterized.ParameterizedTestCase):
41 @parameterized.Parameters(
42 (1, 2, 3),
43 (4, 5, 9),
44 (1, 1, 3))
45 def testAddition(self, op1, op2, result):
46 self.assertEqual(result, op1 + op2)
47
48
49 Each invocation is a separate test case and properly isolated just
50 like a normal test method, with its own setUp/tearDown cycle. In the
51 example above, there are three separate testcases, one of which will
52 fail due to an assertion error (1 + 1 != 3).
53
54 Parameters for invididual test cases can be tuples (with positional parameters)
55 or dictionaries (with named parameters):
56
57 class AdditionExample(parameterized.ParameterizedTestCase):
58 @parameterized.Parameters(
59 {'op1': 1, 'op2': 2, 'result': 3},
60 {'op1': 4, 'op2': 5, 'result': 9},
61 )
62 def testAddition(self, op1, op2, result):
63 self.assertEqual(result, op1 + op2)
64
65 If a parameterized test fails, the error message will show the
66 original test name (which is modified internally) and the arguments
67 for the specific invocation, which are part of the string returned by
68 the shortDescription() method on test cases.
69
70 The id method of the test, used internally by the unittest framework,
71 is also modified to show the arguments. To make sure that test names
72 stay the same across several invocations, object representations like
73
74 >>> class Foo(object):
75 ... pass
76 >>> repr(Foo())
77 '<__main__.Foo object at 0x23d8610>'
78
79 are turned into '<__main__.Foo>'. For even more descriptive names,
80 especially in test logs, you can use the NamedParameters decorator. In
81 this case, only tuples are supported, and the first parameters has to
82 be a string (or an object that returns an apt name when converted via
83 str()):
84
85 class NamedExample(parameterized.ParameterizedTestCase):
86 @parameterized.NamedParameters(
87 ('Normal', 'aa', 'aaa', True),
88 ('EmptyPrefix', '', 'abc', True),
89 ('BothEmpty', '', '', True))
90 def testStartsWith(self, prefix, string, result):
91 self.assertEqual(result, strings.startswith(prefix))
92
93 Named tests also have the benefit that they can be run individually
94 from the command line:
95
96 $ testmodule.py NamedExample.testStartsWithNormal
97 .
98 --------------------------------------------------------------------
99 Ran 1 test in 0.000s
100
101 OK
102
103 Parameterized Classes
104 =====================
105 If invocation arguments are shared across test methods in a single
106 ParameterizedTestCase class, instead of decorating all test methods
107 individually, the class itself can be decorated:
108
109 @parameterized.Parameters(
110 (1, 2, 3)
111 (4, 5, 9))
112 class ArithmeticTest(parameterized.ParameterizedTestCase):
113 def testAdd(self, arg1, arg2, result):
114 self.assertEqual(arg1 + arg2, result)
115
116 def testSubtract(self, arg2, arg2, result):
117 self.assertEqual(result - arg1, arg2)
118
119 Inputs from Iterables
120 =====================
121 If parameters should be shared across several test cases, or are dynamically
122 created from other sources, a single non-tuple iterable can be passed into
123 the decorator. This iterable will be used to obtain the test cases:
124
125 class AdditionExample(parameterized.ParameterizedTestCase):
126 @parameterized.Parameters(
127 c.op1, c.op2, c.result for c in testcases
128 )
129 def testAddition(self, op1, op2, result):
130 self.assertEqual(result, op1 + op2)
131
132
133 Single-Argument Test Methods
134 ============================
135 If a test method takes only one argument, the single argument does not need to
136 be wrapped into a tuple:
137
138 class NegativeNumberExample(parameterized.ParameterizedTestCase):
139 @parameterized.Parameters(
140 -1, -3, -4, -5
141 )
142 def testIsNegative(self, arg):
143 self.assertTrue(IsNegative(arg))
144 """
145
146 __author__ = 'tmarek@google.com (Torsten Marek)'
147
148 import collections
149 import functools
150 import re
151 import types
152 try:
153 import unittest2 as unittest
154 except ImportError:
155 import unittest
156 import uuid
157
158 import six
159
160 ADDR_RE = re.compile(r'\<([a-zA-Z0-9_\-\.]+) object at 0x[a-fA-F0-9]+\>')
161 _SEPARATOR = uuid.uuid1().hex
162 _FIRST_ARG = object()
163 _ARGUMENT_REPR = object()
164
165
166 def _CleanRepr(obj):
167 return ADDR_RE.sub(r'<\1>', repr(obj))
168
169
170 # Helper function formerly from the unittest module, removed from it in
171 # Python 2.7.
172 def _StrClass(cls):
173 return '%s.%s' % (cls.__module__, cls.__name__)
174
175
176 def _NonStringIterable(obj):
177 return (isinstance(obj, collections.Iterable) and not
178 isinstance(obj, six.string_types))
179
180
181 def _FormatParameterList(testcase_params):
182 if isinstance(testcase_params, collections.Mapping):
183 return ', '.join('%s=%s' % (argname, _CleanRepr(value))
184 for argname, value in testcase_params.items())
185 elif _NonStringIterable(testcase_params):
186 return ', '.join(map(_CleanRepr, testcase_params))
187 else:
188 return _FormatParameterList((testcase_params,))
189
190
191 class _ParameterizedTestIter(object):
192 """Callable and iterable class for producing new test cases."""
193
194 def __init__(self, test_method, testcases, naming_type):
195 """Returns concrete test functions for a test and a list of parameters.
196
197 The naming_type is used to determine the name of the concrete
198 functions as reported by the unittest framework. If naming_type is
199 _FIRST_ARG, the testcases must be tuples, and the first element must
200 have a string representation that is a valid Python identifier.
201
202 Args:
203 test_method: The decorated test method.
204 testcases: (list of tuple/dict) A list of parameter
205 tuples/dicts for individual test invocations.
206 naming_type: The test naming type, either _NAMED or _ARGUMENT_REPR.
207 """
208 self._test_method = test_method
209 self.testcases = testcases
210 self._naming_type = naming_type
211
212 def __call__(self, *args, **kwargs):
213 raise RuntimeError('You appear to be running a parameterized test case '
214 'without having inherited from parameterized.'
215 'ParameterizedTestCase. This is bad because none of '
216 'your test cases are actually being run.')
217
218 def __iter__(self):
219 test_method = self._test_method
220 naming_type = self._naming_type
221
222 def MakeBoundParamTest(testcase_params):
223 @functools.wraps(test_method)
224 def BoundParamTest(self):
225 if isinstance(testcase_params, collections.Mapping):
226 test_method(self, **testcase_params)
227 elif _NonStringIterable(testcase_params):
228 test_method(self, *testcase_params)
229 else:
230 test_method(self, testcase_params)
231
232 if naming_type is _FIRST_ARG:
233 # Signal the metaclass that the name of the test function is unique
234 # and descriptive.
235 BoundParamTest.__x_use_name__ = True
236 BoundParamTest.__name__ += str(testcase_params[0])
237 testcase_params = testcase_params[1:]
238 elif naming_type is _ARGUMENT_REPR:
239 # __x_extra_id__ is used to pass naming information to the __new__
240 # method of TestGeneratorMetaclass.
241 # The metaclass will make sure to create a unique, but nondescriptive
242 # name for this test.
243 BoundParamTest.__x_extra_id__ = '(%s)' % (
244 _FormatParameterList(testcase_params),)
245 else:
246 raise RuntimeError('%s is not a valid naming type.' % (naming_type,))
247
248 BoundParamTest.__doc__ = '%s(%s)' % (
249 BoundParamTest.__name__, _FormatParameterList(testcase_params))
250 if test_method.__doc__:
251 BoundParamTest.__doc__ += '\n%s' % (test_method.__doc__,)
252 return BoundParamTest
253 return (MakeBoundParamTest(c) for c in self.testcases)
254
255
256 def _IsSingletonList(testcases):
257 """True iff testcases contains only a single non-tuple element."""
258 return len(testcases) == 1 and not isinstance(testcases[0], tuple)
259
260
261 def _ModifyClass(class_object, testcases, naming_type):
262 assert not getattr(class_object, '_id_suffix', None), (
263 'Cannot add parameters to %s,'
264 ' which already has parameterized methods.' % (class_object,))
265 class_object._id_suffix = id_suffix = {}
266 # We change the size of __dict__ while we iterate over it,
267 # which Python 3.x will complain about, so use copy().
268 for name, obj in class_object.__dict__.copy().items():
269 if (name.startswith(unittest.TestLoader.testMethodPrefix)
270 and isinstance(obj, types.FunctionType)):
271 delattr(class_object, name)
272 methods = {}
273 _UpdateClassDictForParamTestCase(
274 methods, id_suffix, name,
275 _ParameterizedTestIter(obj, testcases, naming_type))
276 for name, meth in methods.items():
277 setattr(class_object, name, meth)
278
279
280 def _ParameterDecorator(naming_type, testcases):
281 """Implementation of the parameterization decorators.
282
283 Args:
284 naming_type: The naming type.
285 testcases: Testcase parameters.
286
287 Returns:
288 A function for modifying the decorated object.
289 """
290 def _Apply(obj):
291 if isinstance(obj, type):
292 _ModifyClass(
293 obj,
294 list(testcases) if not isinstance(testcases, collections.Sequence)
295 else testcases,
296 naming_type)
297 return obj
298 else:
299 return _ParameterizedTestIter(obj, testcases, naming_type)
300
301 if _IsSingletonList(testcases):
302 assert _NonStringIterable(testcases[0]), (
303 'Single parameter argument must be a non-string iterable')
304 testcases = testcases[0]
305
306 return _Apply
307
308
309 def Parameters(*testcases):
310 """A decorator for creating parameterized tests.
311
312 See the module docstring for a usage example.
313 Args:
314 *testcases: Parameters for the decorated method, either a single
315 iterable, or a list of tuples/dicts/objects (for tests
316 with only one argument).
317
318 Returns:
319 A test generator to be handled by TestGeneratorMetaclass.
320 """
321 return _ParameterDecorator(_ARGUMENT_REPR, testcases)
322
323
324 def NamedParameters(*testcases):
325 """A decorator for creating parameterized tests.
326
327 See the module docstring for a usage example. The first element of
328 each parameter tuple should be a string and will be appended to the
329 name of the test method.
330
331 Args:
332 *testcases: Parameters for the decorated method, either a single
333 iterable, or a list of tuples.
334
335 Returns:
336 A test generator to be handled by TestGeneratorMetaclass.
337 """
338 return _ParameterDecorator(_FIRST_ARG, testcases)
339
340
341 class TestGeneratorMetaclass(type):
342 """Metaclass for test cases with test generators.
343
344 A test generator is an iterable in a testcase that produces callables. These
345 callables must be single-argument methods. These methods are injected into
346 the class namespace and the original iterable is removed. If the name of the
347 iterable conforms to the test pattern, the injected methods will be picked
348 up as tests by the unittest framework.
349
350 In general, it is supposed to be used in conjuction with the
351 Parameters decorator.
352 """
353
354 def __new__(mcs, class_name, bases, dct):
355 dct['_id_suffix'] = id_suffix = {}
356 for name, obj in dct.items():
357 if (name.startswith(unittest.TestLoader.testMethodPrefix) and
358 _NonStringIterable(obj)):
359 iterator = iter(obj)
360 dct.pop(name)
361 _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator)
362
363 return type.__new__(mcs, class_name, bases, dct)
364
365
366 def _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator):
367 """Adds individual test cases to a dictionary.
368
369 Args:
370 dct: The target dictionary.
371 id_suffix: The dictionary for mapping names to test IDs.
372 name: The original name of the test case.
373 iterator: The iterator generating the individual test cases.
374 """
375 for idx, func in enumerate(iterator):
376 assert callable(func), 'Test generators must yield callables, got %r' % (
377 func,)
378 if getattr(func, '__x_use_name__', False):
379 new_name = func.__name__
380 else:
381 new_name = '%s%s%d' % (name, _SEPARATOR, idx)
382 assert new_name not in dct, (
383 'Name of parameterized test case "%s" not unique' % (new_name,))
384 dct[new_name] = func
385 id_suffix[new_name] = getattr(func, '__x_extra_id__', '')
386
387
388 class ParameterizedTestCase(unittest.TestCase):
389 """Base class for test cases using the Parameters decorator."""
390 __metaclass__ = TestGeneratorMetaclass
391
392 def _OriginalName(self):
393 return self._testMethodName.split(_SEPARATOR)[0]
394
395 def __str__(self):
396 return '%s (%s)' % (self._OriginalName(), _StrClass(self.__class__))
397
398 def id(self): # pylint: disable=invalid-name
399 """Returns the descriptive ID of the test.
400
401 This is used internally by the unittesting framework to get a name
402 for the test to be used in reports.
403
404 Returns:
405 The test id.
406 """
407 return '%s.%s%s' % (_StrClass(self.__class__),
408 self._OriginalName(),
409 self._id_suffix.get(self._testMethodName, ''))
410
411
412 def CoopParameterizedTestCase(other_base_class):
413 """Returns a new base class with a cooperative metaclass base.
414
415 This enables the ParameterizedTestCase to be used in combination
416 with other base classes that have custom metaclasses, such as
417 mox.MoxTestBase.
418
419 Only works with metaclasses that do not override type.__new__.
420
421 Example:
422
423 import google3
424 import mox
425
426 from google3.testing.pybase import parameterized
427
428 class ExampleTest(parameterized.CoopParameterizedTestCase(mox.MoxTestBase)):
429 ...
430
431 Args:
432 other_base_class: (class) A test case base class.
433
434 Returns:
435 A new class object.
436 """
437 metaclass = type(
438 'CoopMetaclass',
439 (other_base_class.__metaclass__,
440 TestGeneratorMetaclass), {})
441 return metaclass(
442 'CoopParameterizedTestCase',
443 (other_base_class, ParameterizedTestCase), {})
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698