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

Side by Side Diff: third_party/google-endpoints/apitools/base/protorpclite/test_util.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 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 # Copyright 2010 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 """Test utilities for message testing.
19
20 Includes module interface test to ensure that public parts of module are
21 correctly declared in __all__.
22
23 Includes message types that correspond to those defined in
24 services_test.proto.
25
26 Includes additional test utilities to make sure encoding/decoding libraries
27 conform.
28 """
29 import cgi
30 import datetime
31 import inspect
32 import os
33 import re
34 import socket
35 import types
36
37 import six
38 from six.moves import range # pylint: disable=redefined-builtin
39 import unittest2 as unittest
40
41 from apitools.base.protorpclite import message_types
42 from apitools.base.protorpclite import messages
43 from apitools.base.protorpclite import util
44
45 # Unicode of the word "Russian" in cyrillic.
46 RUSSIAN = u'\u0440\u0443\u0441\u0441\u043a\u0438\u0439'
47
48 # All characters binary value interspersed with nulls.
49 BINARY = b''.join(six.int2byte(value) + b'\0' for value in range(256))
50
51
52 class TestCase(unittest.TestCase):
53
54 def assertRaisesWithRegexpMatch(self,
55 exception,
56 regexp,
57 function,
58 *params,
59 **kwargs):
60 """Check that exception is raised and text matches regular expression.
61
62 Args:
63 exception: Exception type that is expected.
64 regexp: String regular expression that is expected in error message.
65 function: Callable to test.
66 params: Parameters to forward to function.
67 kwargs: Keyword arguments to forward to function.
68 """
69 try:
70 function(*params, **kwargs)
71 self.fail('Expected exception %s was not raised' %
72 exception.__name__)
73 except exception as err:
74 match = bool(re.match(regexp, str(err)))
75 self.assertTrue(match, 'Expected match "%s", found "%s"' % (regexp,
76 err))
77
78 def assertHeaderSame(self, header1, header2):
79 """Check that two HTTP headers are the same.
80
81 Args:
82 header1: Header value string 1.
83 header2: header value string 2.
84 """
85 value1, params1 = cgi.parse_header(header1)
86 value2, params2 = cgi.parse_header(header2)
87 self.assertEqual(value1, value2)
88 self.assertEqual(params1, params2)
89
90 def assertIterEqual(self, iter1, iter2):
91 """Check that two iterators or iterables are equal independent of order.
92
93 Similar to Python 2.7 assertItemsEqual. Named differently in order to
94 avoid potential conflict.
95
96 Args:
97 iter1: An iterator or iterable.
98 iter2: An iterator or iterable.
99 """
100 list1 = list(iter1)
101 list2 = list(iter2)
102
103 unmatched1 = list()
104
105 while list1:
106 item1 = list1[0]
107 del list1[0]
108 for index in range(len(list2)):
109 if item1 == list2[index]:
110 del list2[index]
111 break
112 else:
113 unmatched1.append(item1)
114
115 error_message = []
116 for item in unmatched1:
117 error_message.append(
118 ' Item from iter1 not found in iter2: %r' % item)
119 for item in list2:
120 error_message.append(
121 ' Item from iter2 not found in iter1: %r' % item)
122 if error_message:
123 self.fail('Collections not equivalent:\n' +
124 '\n'.join(error_message))
125
126
127 class ModuleInterfaceTest(object):
128 """Test to ensure module interface is carefully constructed.
129
130 A module interface is the set of public objects listed in the
131 module __all__ attribute. Modules that that are considered public
132 should have this interface carefully declared. At all times, the
133 __all__ attribute should have objects intended to be publically
134 used and all other objects in the module should be considered
135 unused.
136
137 Protected attributes (those beginning with '_') and other imported
138 modules should not be part of this set of variables. An exception
139 is for variables that begin and end with '__' which are implicitly
140 part of the interface (eg. __name__, __file__, __all__ itself,
141 etc.).
142
143 Modules that are imported in to the tested modules are an
144 exception and may be left out of the __all__ definition. The test
145 is done by checking the value of what would otherwise be a public
146 name and not allowing it to be exported if it is an instance of a
147 module. Modules that are explicitly exported are for the time
148 being not permitted.
149
150 To use this test class a module should define a new class that
151 inherits first from ModuleInterfaceTest and then from
152 test_util.TestCase. No other tests should be added to this test
153 case, making the order of inheritance less important, but if setUp
154 for some reason is overidden, it is important that
155 ModuleInterfaceTest is first in the list so that its setUp method
156 is invoked.
157
158 Multiple inheritance is required so that ModuleInterfaceTest is
159 not itself a test, and is not itself executed as one.
160
161 The test class is expected to have the following class attributes
162 defined:
163
164 MODULE: A reference to the module that is being validated for interface
165 correctness.
166
167 Example:
168 Module definition (hello.py):
169
170 import sys
171
172 __all__ = ['hello']
173
174 def _get_outputter():
175 return sys.stdout
176
177 def hello():
178 _get_outputter().write('Hello\n')
179
180 Test definition:
181
182 import unittest
183 from protorpc import test_util
184
185 import hello
186
187 class ModuleInterfaceTest(test_util.ModuleInterfaceTest,
188 test_util.TestCase):
189
190 MODULE = hello
191
192
193 class HelloTest(test_util.TestCase):
194 ... Test 'hello' module ...
195
196
197 if __name__ == '__main__':
198 unittest.main()
199
200 """
201
202 def setUp(self):
203 """Set up makes sure that MODULE and IMPORTED_MODULES is defined.
204
205 This is a basic configuration test for the test itself so does not
206 get it's own test case.
207 """
208 if not hasattr(self, 'MODULE'):
209 self.fail(
210 "You must define 'MODULE' on ModuleInterfaceTest sub-class "
211 "%s." % type(self).__name__)
212
213 def testAllExist(self):
214 """Test that all attributes defined in __all__ exist."""
215 missing_attributes = []
216 for attribute in self.MODULE.__all__:
217 if not hasattr(self.MODULE, attribute):
218 missing_attributes.append(attribute)
219 if missing_attributes:
220 self.fail('%s of __all__ are not defined in module.' %
221 missing_attributes)
222
223 def testAllExported(self):
224 """Test that all public attributes not imported are in __all__."""
225 missing_attributes = []
226 for attribute in dir(self.MODULE):
227 if not attribute.startswith('_'):
228 if (attribute not in self.MODULE.__all__ and
229 not isinstance(getattr(self.MODULE, attribute),
230 types.ModuleType) and
231 attribute != 'with_statement'):
232 missing_attributes.append(attribute)
233 if missing_attributes:
234 self.fail('%s are not modules and not defined in __all__.' %
235 missing_attributes)
236
237 def testNoExportedProtectedVariables(self):
238 """Test that there are no protected variables listed in __all__."""
239 protected_variables = []
240 for attribute in self.MODULE.__all__:
241 if attribute.startswith('_'):
242 protected_variables.append(attribute)
243 if protected_variables:
244 self.fail('%s are protected variables and may not be exported.' %
245 protected_variables)
246
247 def testNoExportedModules(self):
248 """Test that no modules exist in __all__."""
249 exported_modules = []
250 for attribute in self.MODULE.__all__:
251 try:
252 value = getattr(self.MODULE, attribute)
253 except AttributeError:
254 # This is a different error case tested for in testAllExist.
255 pass
256 else:
257 if isinstance(value, types.ModuleType):
258 exported_modules.append(attribute)
259 if exported_modules:
260 self.fail('%s are modules and may not be exported.' %
261 exported_modules)
262
263
264 class NestedMessage(messages.Message):
265 """Simple message that gets nested in another message."""
266
267 a_value = messages.StringField(1, required=True)
268
269
270 class HasNestedMessage(messages.Message):
271 """Message that has another message nested in it."""
272
273 nested = messages.MessageField(NestedMessage, 1)
274 repeated_nested = messages.MessageField(NestedMessage, 2, repeated=True)
275
276
277 class HasDefault(messages.Message):
278 """Has a default value."""
279
280 a_value = messages.StringField(1, default=u'a default')
281
282
283 class OptionalMessage(messages.Message):
284 """Contains all message types."""
285
286 class SimpleEnum(messages.Enum):
287 """Simple enumeration type."""
288 VAL1 = 1
289 VAL2 = 2
290
291 double_value = messages.FloatField(1, variant=messages.Variant.DOUBLE)
292 float_value = messages.FloatField(2, variant=messages.Variant.FLOAT)
293 int64_value = messages.IntegerField(3, variant=messages.Variant.INT64)
294 uint64_value = messages.IntegerField(4, variant=messages.Variant.UINT64)
295 int32_value = messages.IntegerField(5, variant=messages.Variant.INT32)
296 bool_value = messages.BooleanField(6, variant=messages.Variant.BOOL)
297 string_value = messages.StringField(7, variant=messages.Variant.STRING)
298 bytes_value = messages.BytesField(8, variant=messages.Variant.BYTES)
299 enum_value = messages.EnumField(SimpleEnum, 10)
300
301
302 class RepeatedMessage(messages.Message):
303 """Contains all message types as repeated fields."""
304
305 class SimpleEnum(messages.Enum):
306 """Simple enumeration type."""
307 VAL1 = 1
308 VAL2 = 2
309
310 double_value = messages.FloatField(1,
311 variant=messages.Variant.DOUBLE,
312 repeated=True)
313 float_value = messages.FloatField(2,
314 variant=messages.Variant.FLOAT,
315 repeated=True)
316 int64_value = messages.IntegerField(3,
317 variant=messages.Variant.INT64,
318 repeated=True)
319 uint64_value = messages.IntegerField(4,
320 variant=messages.Variant.UINT64,
321 repeated=True)
322 int32_value = messages.IntegerField(5,
323 variant=messages.Variant.INT32,
324 repeated=True)
325 bool_value = messages.BooleanField(6,
326 variant=messages.Variant.BOOL,
327 repeated=True)
328 string_value = messages.StringField(7,
329 variant=messages.Variant.STRING,
330 repeated=True)
331 bytes_value = messages.BytesField(8,
332 variant=messages.Variant.BYTES,
333 repeated=True)
334 enum_value = messages.EnumField(SimpleEnum,
335 10,
336 repeated=True)
337
338
339 class HasOptionalNestedMessage(messages.Message):
340
341 nested = messages.MessageField(OptionalMessage, 1)
342 repeated_nested = messages.MessageField(OptionalMessage, 2, repeated=True)
343
344
345 # pylint:disable=anomalous-unicode-escape-in-string
346 class ProtoConformanceTestBase(object):
347 """Protocol conformance test base class.
348
349 Each supported protocol should implement two methods that support encoding
350 and decoding of Message objects in that format:
351
352 encode_message(message) - Serialize to encoding.
353 encode_message(message, encoded_message) - Deserialize from encoding.
354
355 Tests for the modules where these functions are implemented should extend
356 this class in order to support basic behavioral expectations. This ensures
357 that protocols correctly encode and decode message transparently to the
358 caller.
359
360 In order to support these test, the base class should also extend
361 the TestCase class and implement the following class attributes
362 which define the encoded version of certain protocol buffers:
363
364 encoded_partial:
365 <OptionalMessage
366 double_value: 1.23
367 int64_value: -100000000000
368 string_value: u"a string"
369 enum_value: OptionalMessage.SimpleEnum.VAL2
370 >
371
372 encoded_full:
373 <OptionalMessage
374 double_value: 1.23
375 float_value: -2.5
376 int64_value: -100000000000
377 uint64_value: 102020202020
378 int32_value: 1020
379 bool_value: true
380 string_value: u"a string\u044f"
381 bytes_value: b"a bytes\xff\xfe"
382 enum_value: OptionalMessage.SimpleEnum.VAL2
383 >
384
385 encoded_repeated:
386 <RepeatedMessage
387 double_value: [1.23, 2.3]
388 float_value: [-2.5, 0.5]
389 int64_value: [-100000000000, 20]
390 uint64_value: [102020202020, 10]
391 int32_value: [1020, 718]
392 bool_value: [true, false]
393 string_value: [u"a string\u044f", u"another string"]
394 bytes_value: [b"a bytes\xff\xfe", b"another bytes"]
395 enum_value: [OptionalMessage.SimpleEnum.VAL2,
396 OptionalMessage.SimpleEnum.VAL 1]
397 >
398
399 encoded_nested:
400 <HasNestedMessage
401 nested: <NestedMessage
402 a_value: "a string"
403 >
404 >
405
406 encoded_repeated_nested:
407 <HasNestedMessage
408 repeated_nested: [
409 <NestedMessage a_value: "a string">,
410 <NestedMessage a_value: "another string">
411 ]
412 >
413
414 unexpected_tag_message:
415 An encoded message that has an undefined tag or number in the stream.
416
417 encoded_default_assigned:
418 <HasDefault
419 a_value: "a default"
420 >
421
422 encoded_nested_empty:
423 <HasOptionalNestedMessage
424 nested: <OptionalMessage>
425 >
426
427 encoded_invalid_enum:
428 <OptionalMessage
429 enum_value: (invalid value for serialization type)
430 >
431 """
432
433 encoded_empty_message = ''
434
435 def testEncodeInvalidMessage(self):
436 message = NestedMessage()
437 self.assertRaises(messages.ValidationError,
438 self.PROTOLIB.encode_message, message)
439
440 def CompareEncoded(self, expected_encoded, actual_encoded):
441 """Compare two encoded protocol values.
442
443 Can be overridden by sub-classes to special case comparison.
444 For example, to eliminate white space from output that is not
445 relevant to encoding.
446
447 Args:
448 expected_encoded: Expected string encoded value.
449 actual_encoded: Actual string encoded value.
450 """
451 self.assertEquals(expected_encoded, actual_encoded)
452
453 def EncodeDecode(self, encoded, expected_message):
454 message = self.PROTOLIB.decode_message(type(expected_message), encoded)
455 self.assertEquals(expected_message, message)
456 self.CompareEncoded(encoded, self.PROTOLIB.encode_message(message))
457
458 def testEmptyMessage(self):
459 self.EncodeDecode(self.encoded_empty_message, OptionalMessage())
460
461 def testPartial(self):
462 """Test message with a few values set."""
463 message = OptionalMessage()
464 message.double_value = 1.23
465 message.int64_value = -100000000000
466 message.int32_value = 1020
467 message.string_value = u'a string'
468 message.enum_value = OptionalMessage.SimpleEnum.VAL2
469
470 self.EncodeDecode(self.encoded_partial, message)
471
472 def testFull(self):
473 """Test all types."""
474 message = OptionalMessage()
475 message.double_value = 1.23
476 message.float_value = -2.5
477 message.int64_value = -100000000000
478 message.uint64_value = 102020202020
479 message.int32_value = 1020
480 message.bool_value = True
481 message.string_value = u'a string\u044f'
482 message.bytes_value = b'a bytes\xff\xfe'
483 message.enum_value = OptionalMessage.SimpleEnum.VAL2
484
485 self.EncodeDecode(self.encoded_full, message)
486
487 def testRepeated(self):
488 """Test repeated fields."""
489 message = RepeatedMessage()
490 message.double_value = [1.23, 2.3]
491 message.float_value = [-2.5, 0.5]
492 message.int64_value = [-100000000000, 20]
493 message.uint64_value = [102020202020, 10]
494 message.int32_value = [1020, 718]
495 message.bool_value = [True, False]
496 message.string_value = [u'a string\u044f', u'another string']
497 message.bytes_value = [b'a bytes\xff\xfe', b'another bytes']
498 message.enum_value = [RepeatedMessage.SimpleEnum.VAL2,
499 RepeatedMessage.SimpleEnum.VAL1]
500
501 self.EncodeDecode(self.encoded_repeated, message)
502
503 def testNested(self):
504 """Test nested messages."""
505 nested_message = NestedMessage()
506 nested_message.a_value = u'a string'
507
508 message = HasNestedMessage()
509 message.nested = nested_message
510
511 self.EncodeDecode(self.encoded_nested, message)
512
513 def testRepeatedNested(self):
514 """Test repeated nested messages."""
515 nested_message1 = NestedMessage()
516 nested_message1.a_value = u'a string'
517 nested_message2 = NestedMessage()
518 nested_message2.a_value = u'another string'
519
520 message = HasNestedMessage()
521 message.repeated_nested = [nested_message1, nested_message2]
522
523 self.EncodeDecode(self.encoded_repeated_nested, message)
524
525 def testStringTypes(self):
526 """Test that encoding str on StringField works."""
527 message = OptionalMessage()
528 message.string_value = 'Latin'
529 self.EncodeDecode(self.encoded_string_types, message)
530
531 def testEncodeUninitialized(self):
532 """Test that cannot encode uninitialized message."""
533 required = NestedMessage()
534 self.assertRaisesWithRegexpMatch(messages.ValidationError,
535 "Message NestedMessage is missing "
536 "required field a_value",
537 self.PROTOLIB.encode_message,
538 required)
539
540 def testUnexpectedField(self):
541 """Test decoding and encoding unexpected fields."""
542 loaded_message = self.PROTOLIB.decode_message(
543 OptionalMessage, self.unexpected_tag_message)
544 # Message should be equal to an empty message, since unknown
545 # values aren't included in equality.
546 self.assertEquals(OptionalMessage(), loaded_message)
547 # Verify that the encoded message matches the source, including the
548 # unknown value.
549 self.assertEquals(self.unexpected_tag_message,
550 self.PROTOLIB.encode_message(loaded_message))
551
552 def testDoNotSendDefault(self):
553 """Test that default is not sent when nothing is assigned."""
554 self.EncodeDecode(self.encoded_empty_message, HasDefault())
555
556 def testSendDefaultExplicitlyAssigned(self):
557 """Test that default is sent when explcitly assigned."""
558 message = HasDefault()
559
560 message.a_value = HasDefault.a_value.default
561
562 self.EncodeDecode(self.encoded_default_assigned, message)
563
564 def testEncodingNestedEmptyMessage(self):
565 """Test encoding a nested empty message."""
566 message = HasOptionalNestedMessage()
567 message.nested = OptionalMessage()
568
569 self.EncodeDecode(self.encoded_nested_empty, message)
570
571 def testEncodingRepeatedNestedEmptyMessage(self):
572 """Test encoding a nested empty message."""
573 message = HasOptionalNestedMessage()
574 message.repeated_nested = [OptionalMessage(), OptionalMessage()]
575
576 self.EncodeDecode(self.encoded_repeated_nested_empty, message)
577
578 def testContentType(self):
579 self.assertTrue(isinstance(self.PROTOLIB.CONTENT_TYPE, str))
580
581 def testDecodeInvalidEnumType(self):
582 self.assertRaisesWithRegexpMatch(messages.DecodeError,
583 'Invalid enum value ',
584 self.PROTOLIB.decode_message,
585 OptionalMessage,
586 self.encoded_invalid_enum)
587
588 def testDateTimeNoTimeZone(self):
589 """Test that DateTimeFields are encoded/decoded correctly."""
590
591 class MyMessage(messages.Message):
592 value = message_types.DateTimeField(1)
593
594 value = datetime.datetime(2013, 1, 3, 11, 36, 30, 123000)
595 message = MyMessage(value=value)
596 decoded = self.PROTOLIB.decode_message(
597 MyMessage, self.PROTOLIB.encode_message(message))
598 self.assertEquals(decoded.value, value)
599
600 def testDateTimeWithTimeZone(self):
601 """Test DateTimeFields with time zones."""
602
603 class MyMessage(messages.Message):
604 value = message_types.DateTimeField(1)
605
606 value = datetime.datetime(2013, 1, 3, 11, 36, 30, 123000,
607 util.TimeZoneOffset(8 * 60))
608 message = MyMessage(value=value)
609 decoded = self.PROTOLIB.decode_message(
610 MyMessage, self.PROTOLIB.encode_message(message))
611 self.assertEquals(decoded.value, value)
612
613
614 def pick_unused_port():
615 """Find an unused port to use in tests.
616
617 Derived from Damon Kohlers example:
618
619 http://code.activestate.com/recipes/531822-pick-unused-port
620 """
621 temp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
622 try:
623 temp.bind(('localhost', 0))
624 port = temp.getsockname()[1]
625 finally:
626 temp.close()
627 return port
628
629
630 def get_module_name(module_attribute):
631 """Get the module name.
632
633 Args:
634 module_attribute: An attribute of the module.
635
636 Returns:
637 The fully qualified module name or simple module name where
638 'module_attribute' is defined if the module name is "__main__".
639 """
640 if module_attribute.__module__ == '__main__':
641 module_file = inspect.getfile(module_attribute)
642 default = os.path.basename(module_file).split('.')[0]
643 return default
644 else:
645 return module_attribute.__module__
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698