text_format_test.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  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. """Test for google.protobuf.text_format."""
  33. __author__ = 'kenton@google.com (Kenton Varda)'
  34. import re
  35. import six
  36. import string
  37. try:
  38. import unittest2 as unittest
  39. except ImportError:
  40. import unittest
  41. from google.protobuf.internal import _parameterized
  42. from google.protobuf import map_unittest_pb2
  43. from google.protobuf import unittest_mset_pb2
  44. from google.protobuf import unittest_pb2
  45. from google.protobuf import unittest_proto3_arena_pb2
  46. from google.protobuf.internal import api_implementation
  47. from google.protobuf.internal import test_util
  48. from google.protobuf import text_format
  49. # Base class with some common functionality.
  50. class TextFormatBase(unittest.TestCase):
  51. def ReadGolden(self, golden_filename):
  52. with test_util.GoldenFile(golden_filename) as f:
  53. return (f.readlines() if str is bytes else # PY3
  54. [golden_line.decode('utf-8') for golden_line in f])
  55. def CompareToGoldenFile(self, text, golden_filename):
  56. golden_lines = self.ReadGolden(golden_filename)
  57. self.assertMultiLineEqual(text, ''.join(golden_lines))
  58. def CompareToGoldenText(self, text, golden_text):
  59. self.assertEqual(text, golden_text)
  60. def RemoveRedundantZeros(self, text):
  61. # Some platforms print 1e+5 as 1e+005. This is fine, but we need to remove
  62. # these zeros in order to match the golden file.
  63. text = text.replace('e+0','e+').replace('e+0','e+') \
  64. .replace('e-0','e-').replace('e-0','e-')
  65. # Floating point fields are printed with .0 suffix even if they are
  66. # actualy integer numbers.
  67. text = re.compile('\.0$', re.MULTILINE).sub('', text)
  68. return text
  69. @_parameterized.Parameters(
  70. (unittest_pb2),
  71. (unittest_proto3_arena_pb2))
  72. class TextFormatTest(TextFormatBase):
  73. def testPrintExotic(self, message_module):
  74. message = message_module.TestAllTypes()
  75. message.repeated_int64.append(-9223372036854775808)
  76. message.repeated_uint64.append(18446744073709551615)
  77. message.repeated_double.append(123.456)
  78. message.repeated_double.append(1.23e22)
  79. message.repeated_double.append(1.23e-18)
  80. message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"')
  81. message.repeated_string.append(u'\u00fc\ua71f')
  82. self.CompareToGoldenText(
  83. self.RemoveRedundantZeros(text_format.MessageToString(message)),
  84. 'repeated_int64: -9223372036854775808\n'
  85. 'repeated_uint64: 18446744073709551615\n'
  86. 'repeated_double: 123.456\n'
  87. 'repeated_double: 1.23e+22\n'
  88. 'repeated_double: 1.23e-18\n'
  89. 'repeated_string:'
  90. ' "\\000\\001\\007\\010\\014\\n\\r\\t\\013\\\\\\\'\\""\n'
  91. 'repeated_string: "\\303\\274\\352\\234\\237"\n')
  92. def testPrintExoticUnicodeSubclass(self, message_module):
  93. class UnicodeSub(six.text_type):
  94. pass
  95. message = message_module.TestAllTypes()
  96. message.repeated_string.append(UnicodeSub(u'\u00fc\ua71f'))
  97. self.CompareToGoldenText(
  98. text_format.MessageToString(message),
  99. 'repeated_string: "\\303\\274\\352\\234\\237"\n')
  100. def testPrintNestedMessageAsOneLine(self, message_module):
  101. message = message_module.TestAllTypes()
  102. msg = message.repeated_nested_message.add()
  103. msg.bb = 42
  104. self.CompareToGoldenText(
  105. text_format.MessageToString(message, as_one_line=True),
  106. 'repeated_nested_message { bb: 42 }')
  107. def testPrintRepeatedFieldsAsOneLine(self, message_module):
  108. message = message_module.TestAllTypes()
  109. message.repeated_int32.append(1)
  110. message.repeated_int32.append(1)
  111. message.repeated_int32.append(3)
  112. message.repeated_string.append('Google')
  113. message.repeated_string.append('Zurich')
  114. self.CompareToGoldenText(
  115. text_format.MessageToString(message, as_one_line=True),
  116. 'repeated_int32: 1 repeated_int32: 1 repeated_int32: 3 '
  117. 'repeated_string: "Google" repeated_string: "Zurich"')
  118. def testPrintNestedNewLineInStringAsOneLine(self, message_module):
  119. message = message_module.TestAllTypes()
  120. message.optional_string = 'a\nnew\nline'
  121. self.CompareToGoldenText(
  122. text_format.MessageToString(message, as_one_line=True),
  123. 'optional_string: "a\\nnew\\nline"')
  124. def testPrintExoticAsOneLine(self, message_module):
  125. message = message_module.TestAllTypes()
  126. message.repeated_int64.append(-9223372036854775808)
  127. message.repeated_uint64.append(18446744073709551615)
  128. message.repeated_double.append(123.456)
  129. message.repeated_double.append(1.23e22)
  130. message.repeated_double.append(1.23e-18)
  131. message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"')
  132. message.repeated_string.append(u'\u00fc\ua71f')
  133. self.CompareToGoldenText(
  134. self.RemoveRedundantZeros(
  135. text_format.MessageToString(message, as_one_line=True)),
  136. 'repeated_int64: -9223372036854775808'
  137. ' repeated_uint64: 18446744073709551615'
  138. ' repeated_double: 123.456'
  139. ' repeated_double: 1.23e+22'
  140. ' repeated_double: 1.23e-18'
  141. ' repeated_string: '
  142. '"\\000\\001\\007\\010\\014\\n\\r\\t\\013\\\\\\\'\\""'
  143. ' repeated_string: "\\303\\274\\352\\234\\237"')
  144. def testRoundTripExoticAsOneLine(self, message_module):
  145. message = message_module.TestAllTypes()
  146. message.repeated_int64.append(-9223372036854775808)
  147. message.repeated_uint64.append(18446744073709551615)
  148. message.repeated_double.append(123.456)
  149. message.repeated_double.append(1.23e22)
  150. message.repeated_double.append(1.23e-18)
  151. message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"')
  152. message.repeated_string.append(u'\u00fc\ua71f')
  153. # Test as_utf8 = False.
  154. wire_text = text_format.MessageToString(
  155. message, as_one_line=True, as_utf8=False)
  156. parsed_message = message_module.TestAllTypes()
  157. r = text_format.Parse(wire_text, parsed_message)
  158. self.assertIs(r, parsed_message)
  159. self.assertEqual(message, parsed_message)
  160. # Test as_utf8 = True.
  161. wire_text = text_format.MessageToString(
  162. message, as_one_line=True, as_utf8=True)
  163. parsed_message = message_module.TestAllTypes()
  164. r = text_format.Parse(wire_text, parsed_message)
  165. self.assertIs(r, parsed_message)
  166. self.assertEqual(message, parsed_message,
  167. '\n%s != %s' % (message, parsed_message))
  168. def testPrintRawUtf8String(self, message_module):
  169. message = message_module.TestAllTypes()
  170. message.repeated_string.append(u'\u00fc\ua71f')
  171. text = text_format.MessageToString(message, as_utf8=True)
  172. self.CompareToGoldenText(text, 'repeated_string: "\303\274\352\234\237"\n')
  173. parsed_message = message_module.TestAllTypes()
  174. text_format.Parse(text, parsed_message)
  175. self.assertEqual(message, parsed_message,
  176. '\n%s != %s' % (message, parsed_message))
  177. def testPrintFloatFormat(self, message_module):
  178. # Check that float_format argument is passed to sub-message formatting.
  179. message = message_module.NestedTestAllTypes()
  180. # We use 1.25 as it is a round number in binary. The proto 32-bit float
  181. # will not gain additional imprecise digits as a 64-bit Python float and
  182. # show up in its str. 32-bit 1.2 is noisy when extended to 64-bit:
  183. # >>> struct.unpack('f', struct.pack('f', 1.2))[0]
  184. # 1.2000000476837158
  185. # >>> struct.unpack('f', struct.pack('f', 1.25))[0]
  186. # 1.25
  187. message.payload.optional_float = 1.25
  188. # Check rounding at 15 significant digits
  189. message.payload.optional_double = -.000003456789012345678
  190. # Check no decimal point.
  191. message.payload.repeated_float.append(-5642)
  192. # Check no trailing zeros.
  193. message.payload.repeated_double.append(.000078900)
  194. formatted_fields = ['optional_float: 1.25',
  195. 'optional_double: -3.45678901234568e-6',
  196. 'repeated_float: -5642',
  197. 'repeated_double: 7.89e-5']
  198. text_message = text_format.MessageToString(message, float_format='.15g')
  199. self.CompareToGoldenText(
  200. self.RemoveRedundantZeros(text_message),
  201. 'payload {{\n {0}\n {1}\n {2}\n {3}\n}}\n'.format(*formatted_fields))
  202. # as_one_line=True is a separate code branch where float_format is passed.
  203. text_message = text_format.MessageToString(message, as_one_line=True,
  204. float_format='.15g')
  205. self.CompareToGoldenText(
  206. self.RemoveRedundantZeros(text_message),
  207. 'payload {{ {0} {1} {2} {3} }}'.format(*formatted_fields))
  208. def testMessageToString(self, message_module):
  209. message = message_module.ForeignMessage()
  210. message.c = 123
  211. self.assertEqual('c: 123\n', str(message))
  212. def testParseAllFields(self, message_module):
  213. message = message_module.TestAllTypes()
  214. test_util.SetAllFields(message)
  215. ascii_text = text_format.MessageToString(message)
  216. parsed_message = message_module.TestAllTypes()
  217. text_format.Parse(ascii_text, parsed_message)
  218. self.assertEqual(message, parsed_message)
  219. if message_module is unittest_pb2:
  220. test_util.ExpectAllFieldsSet(self, message)
  221. def testParseExotic(self, message_module):
  222. message = message_module.TestAllTypes()
  223. text = ('repeated_int64: -9223372036854775808\n'
  224. 'repeated_uint64: 18446744073709551615\n'
  225. 'repeated_double: 123.456\n'
  226. 'repeated_double: 1.23e+22\n'
  227. 'repeated_double: 1.23e-18\n'
  228. 'repeated_string: \n'
  229. '"\\000\\001\\007\\010\\014\\n\\r\\t\\013\\\\\\\'\\""\n'
  230. 'repeated_string: "foo" \'corge\' "grault"\n'
  231. 'repeated_string: "\\303\\274\\352\\234\\237"\n'
  232. 'repeated_string: "\\xc3\\xbc"\n'
  233. 'repeated_string: "\xc3\xbc"\n')
  234. text_format.Parse(text, message)
  235. self.assertEqual(-9223372036854775808, message.repeated_int64[0])
  236. self.assertEqual(18446744073709551615, message.repeated_uint64[0])
  237. self.assertEqual(123.456, message.repeated_double[0])
  238. self.assertEqual(1.23e22, message.repeated_double[1])
  239. self.assertEqual(1.23e-18, message.repeated_double[2])
  240. self.assertEqual(
  241. '\000\001\a\b\f\n\r\t\v\\\'"', message.repeated_string[0])
  242. self.assertEqual('foocorgegrault', message.repeated_string[1])
  243. self.assertEqual(u'\u00fc\ua71f', message.repeated_string[2])
  244. self.assertEqual(u'\u00fc', message.repeated_string[3])
  245. def testParseTrailingCommas(self, message_module):
  246. message = message_module.TestAllTypes()
  247. text = ('repeated_int64: 100;\n'
  248. 'repeated_int64: 200;\n'
  249. 'repeated_int64: 300,\n'
  250. 'repeated_string: "one",\n'
  251. 'repeated_string: "two";\n')
  252. text_format.Parse(text, message)
  253. self.assertEqual(100, message.repeated_int64[0])
  254. self.assertEqual(200, message.repeated_int64[1])
  255. self.assertEqual(300, message.repeated_int64[2])
  256. self.assertEqual(u'one', message.repeated_string[0])
  257. self.assertEqual(u'two', message.repeated_string[1])
  258. def testParseEmptyText(self, message_module):
  259. message = message_module.TestAllTypes()
  260. text = ''
  261. text_format.Parse(text, message)
  262. self.assertEqual(message_module.TestAllTypes(), message)
  263. def testParseInvalidUtf8(self, message_module):
  264. message = message_module.TestAllTypes()
  265. text = 'repeated_string: "\\xc3\\xc3"'
  266. self.assertRaises(text_format.ParseError, text_format.Parse, text, message)
  267. def testParseSingleWord(self, message_module):
  268. message = message_module.TestAllTypes()
  269. text = 'foo'
  270. six.assertRaisesRegex(self,
  271. text_format.ParseError,
  272. (r'1:1 : Message type "\w+.TestAllTypes" has no field named '
  273. r'"foo".'),
  274. text_format.Parse, text, message)
  275. def testParseUnknownField(self, message_module):
  276. message = message_module.TestAllTypes()
  277. text = 'unknown_field: 8\n'
  278. six.assertRaisesRegex(self,
  279. text_format.ParseError,
  280. (r'1:1 : Message type "\w+.TestAllTypes" has no field named '
  281. r'"unknown_field".'),
  282. text_format.Parse, text, message)
  283. def testParseBadEnumValue(self, message_module):
  284. message = message_module.TestAllTypes()
  285. text = 'optional_nested_enum: BARR'
  286. six.assertRaisesRegex(self,
  287. text_format.ParseError,
  288. (r'1:23 : Enum type "\w+.TestAllTypes.NestedEnum" '
  289. r'has no value named BARR.'),
  290. text_format.Parse, text, message)
  291. message = message_module.TestAllTypes()
  292. text = 'optional_nested_enum: 100'
  293. six.assertRaisesRegex(self,
  294. text_format.ParseError,
  295. (r'1:23 : Enum type "\w+.TestAllTypes.NestedEnum" '
  296. r'has no value with number 100.'),
  297. text_format.Parse, text, message)
  298. def testParseBadIntValue(self, message_module):
  299. message = message_module.TestAllTypes()
  300. text = 'optional_int32: bork'
  301. six.assertRaisesRegex(self,
  302. text_format.ParseError,
  303. ('1:17 : Couldn\'t parse integer: bork'),
  304. text_format.Parse, text, message)
  305. def testParseStringFieldUnescape(self, message_module):
  306. message = message_module.TestAllTypes()
  307. text = r'''repeated_string: "\xf\x62"
  308. repeated_string: "\\xf\\x62"
  309. repeated_string: "\\\xf\\\x62"
  310. repeated_string: "\\\\xf\\\\x62"
  311. repeated_string: "\\\\\xf\\\\\x62"
  312. repeated_string: "\x5cx20"'''
  313. text_format.Parse(text, message)
  314. SLASH = '\\'
  315. self.assertEqual('\x0fb', message.repeated_string[0])
  316. self.assertEqual(SLASH + 'xf' + SLASH + 'x62', message.repeated_string[1])
  317. self.assertEqual(SLASH + '\x0f' + SLASH + 'b', message.repeated_string[2])
  318. self.assertEqual(SLASH + SLASH + 'xf' + SLASH + SLASH + 'x62',
  319. message.repeated_string[3])
  320. self.assertEqual(SLASH + SLASH + '\x0f' + SLASH + SLASH + 'b',
  321. message.repeated_string[4])
  322. self.assertEqual(SLASH + 'x20', message.repeated_string[5])
  323. def testMergeDuplicateScalars(self, message_module):
  324. message = message_module.TestAllTypes()
  325. text = ('optional_int32: 42 '
  326. 'optional_int32: 67')
  327. r = text_format.Merge(text, message)
  328. self.assertIs(r, message)
  329. self.assertEqual(67, message.optional_int32)
  330. def testMergeDuplicateNestedMessageScalars(self, message_module):
  331. message = message_module.TestAllTypes()
  332. text = ('optional_nested_message { bb: 1 } '
  333. 'optional_nested_message { bb: 2 }')
  334. r = text_format.Merge(text, message)
  335. self.assertTrue(r is message)
  336. self.assertEqual(2, message.optional_nested_message.bb)
  337. def testParseOneof(self, message_module):
  338. m = message_module.TestAllTypes()
  339. m.oneof_uint32 = 11
  340. m2 = message_module.TestAllTypes()
  341. text_format.Parse(text_format.MessageToString(m), m2)
  342. self.assertEqual('oneof_uint32', m2.WhichOneof('oneof_field'))
  343. # These are tests that aren't fundamentally specific to proto2, but are at
  344. # the moment because of differences between the proto2 and proto3 test schemas.
  345. # Ideally the schemas would be made more similar so these tests could pass.
  346. class OnlyWorksWithProto2RightNowTests(TextFormatBase):
  347. def testPrintAllFieldsPointy(self):
  348. message = unittest_pb2.TestAllTypes()
  349. test_util.SetAllFields(message)
  350. self.CompareToGoldenFile(
  351. self.RemoveRedundantZeros(
  352. text_format.MessageToString(message, pointy_brackets=True)),
  353. 'text_format_unittest_data_pointy_oneof.txt')
  354. def testParseGolden(self):
  355. golden_text = '\n'.join(self.ReadGolden('text_format_unittest_data.txt'))
  356. parsed_message = unittest_pb2.TestAllTypes()
  357. r = text_format.Parse(golden_text, parsed_message)
  358. self.assertIs(r, parsed_message)
  359. message = unittest_pb2.TestAllTypes()
  360. test_util.SetAllFields(message)
  361. self.assertEqual(message, parsed_message)
  362. def testPrintAllFields(self):
  363. message = unittest_pb2.TestAllTypes()
  364. test_util.SetAllFields(message)
  365. self.CompareToGoldenFile(
  366. self.RemoveRedundantZeros(text_format.MessageToString(message)),
  367. 'text_format_unittest_data_oneof_implemented.txt')
  368. def testPrintAllFieldsPointy(self):
  369. message = unittest_pb2.TestAllTypes()
  370. test_util.SetAllFields(message)
  371. self.CompareToGoldenFile(
  372. self.RemoveRedundantZeros(
  373. text_format.MessageToString(message, pointy_brackets=True)),
  374. 'text_format_unittest_data_pointy_oneof.txt')
  375. def testPrintInIndexOrder(self):
  376. message = unittest_pb2.TestFieldOrderings()
  377. message.my_string = '115'
  378. message.my_int = 101
  379. message.my_float = 111
  380. message.optional_nested_message.oo = 0
  381. message.optional_nested_message.bb = 1
  382. self.CompareToGoldenText(
  383. self.RemoveRedundantZeros(text_format.MessageToString(
  384. message, use_index_order=True)),
  385. 'my_string: \"115\"\nmy_int: 101\nmy_float: 111\n'
  386. 'optional_nested_message {\n oo: 0\n bb: 1\n}\n')
  387. self.CompareToGoldenText(
  388. self.RemoveRedundantZeros(text_format.MessageToString(
  389. message)),
  390. 'my_int: 101\nmy_string: \"115\"\nmy_float: 111\n'
  391. 'optional_nested_message {\n bb: 1\n oo: 0\n}\n')
  392. def testMergeLinesGolden(self):
  393. opened = self.ReadGolden('text_format_unittest_data.txt')
  394. parsed_message = unittest_pb2.TestAllTypes()
  395. r = text_format.MergeLines(opened, parsed_message)
  396. self.assertIs(r, parsed_message)
  397. message = unittest_pb2.TestAllTypes()
  398. test_util.SetAllFields(message)
  399. self.assertEqual(message, parsed_message)
  400. def testParseLinesGolden(self):
  401. opened = self.ReadGolden('text_format_unittest_data.txt')
  402. parsed_message = unittest_pb2.TestAllTypes()
  403. r = text_format.ParseLines(opened, parsed_message)
  404. self.assertIs(r, parsed_message)
  405. message = unittest_pb2.TestAllTypes()
  406. test_util.SetAllFields(message)
  407. self.assertEqual(message, parsed_message)
  408. def testPrintMap(self):
  409. message = map_unittest_pb2.TestMap()
  410. message.map_int32_int32[-123] = -456
  411. message.map_int64_int64[-2**33] = -2**34
  412. message.map_uint32_uint32[123] = 456
  413. message.map_uint64_uint64[2**33] = 2**34
  414. message.map_string_string["abc"] = "123"
  415. message.map_int32_foreign_message[111].c = 5
  416. # Maps are serialized to text format using their underlying repeated
  417. # representation.
  418. self.CompareToGoldenText(
  419. text_format.MessageToString(message),
  420. 'map_int32_int32 {\n'
  421. ' key: -123\n'
  422. ' value: -456\n'
  423. '}\n'
  424. 'map_int64_int64 {\n'
  425. ' key: -8589934592\n'
  426. ' value: -17179869184\n'
  427. '}\n'
  428. 'map_uint32_uint32 {\n'
  429. ' key: 123\n'
  430. ' value: 456\n'
  431. '}\n'
  432. 'map_uint64_uint64 {\n'
  433. ' key: 8589934592\n'
  434. ' value: 17179869184\n'
  435. '}\n'
  436. 'map_string_string {\n'
  437. ' key: "abc"\n'
  438. ' value: "123"\n'
  439. '}\n'
  440. 'map_int32_foreign_message {\n'
  441. ' key: 111\n'
  442. ' value {\n'
  443. ' c: 5\n'
  444. ' }\n'
  445. '}\n')
  446. def testMapOrderEnforcement(self):
  447. message = map_unittest_pb2.TestMap()
  448. for letter in string.ascii_uppercase[13:26]:
  449. message.map_string_string[letter] = 'dummy'
  450. for letter in reversed(string.ascii_uppercase[0:13]):
  451. message.map_string_string[letter] = 'dummy'
  452. golden = ''.join((
  453. 'map_string_string {\n key: "%c"\n value: "dummy"\n}\n' % (letter,)
  454. for letter in string.ascii_uppercase))
  455. self.CompareToGoldenText(text_format.MessageToString(message), golden)
  456. def testMapOrderSemantics(self):
  457. golden_lines = self.ReadGolden('map_test_data.txt')
  458. # The C++ implementation emits defaulted-value fields, while the Python
  459. # implementation does not. Adjusting for this is awkward, but it is
  460. # valuable to test against a common golden file.
  461. line_blacklist = (' key: 0\n',
  462. ' value: 0\n',
  463. ' key: false\n',
  464. ' value: false\n')
  465. golden_lines = [line for line in golden_lines if line not in line_blacklist]
  466. message = map_unittest_pb2.TestMap()
  467. text_format.ParseLines(golden_lines, message)
  468. candidate = text_format.MessageToString(message)
  469. # The Python implementation emits "1.0" for the double value that the C++
  470. # implementation emits as "1".
  471. candidate = candidate.replace('1.0', '1', 2)
  472. self.assertMultiLineEqual(candidate, ''.join(golden_lines))
  473. # Tests of proto2-only features (MessageSet, extensions, etc.).
  474. class Proto2Tests(TextFormatBase):
  475. def testPrintMessageSet(self):
  476. message = unittest_mset_pb2.TestMessageSetContainer()
  477. ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
  478. ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
  479. message.message_set.Extensions[ext1].i = 23
  480. message.message_set.Extensions[ext2].str = 'foo'
  481. self.CompareToGoldenText(
  482. text_format.MessageToString(message),
  483. 'message_set {\n'
  484. ' [protobuf_unittest.TestMessageSetExtension1] {\n'
  485. ' i: 23\n'
  486. ' }\n'
  487. ' [protobuf_unittest.TestMessageSetExtension2] {\n'
  488. ' str: \"foo\"\n'
  489. ' }\n'
  490. '}\n')
  491. def testPrintMessageSetAsOneLine(self):
  492. message = unittest_mset_pb2.TestMessageSetContainer()
  493. ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
  494. ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
  495. message.message_set.Extensions[ext1].i = 23
  496. message.message_set.Extensions[ext2].str = 'foo'
  497. self.CompareToGoldenText(
  498. text_format.MessageToString(message, as_one_line=True),
  499. 'message_set {'
  500. ' [protobuf_unittest.TestMessageSetExtension1] {'
  501. ' i: 23'
  502. ' }'
  503. ' [protobuf_unittest.TestMessageSetExtension2] {'
  504. ' str: \"foo\"'
  505. ' }'
  506. ' }')
  507. def testParseMessageSet(self):
  508. message = unittest_pb2.TestAllTypes()
  509. text = ('repeated_uint64: 1\n'
  510. 'repeated_uint64: 2\n')
  511. text_format.Parse(text, message)
  512. self.assertEqual(1, message.repeated_uint64[0])
  513. self.assertEqual(2, message.repeated_uint64[1])
  514. message = unittest_mset_pb2.TestMessageSetContainer()
  515. text = ('message_set {\n'
  516. ' [protobuf_unittest.TestMessageSetExtension1] {\n'
  517. ' i: 23\n'
  518. ' }\n'
  519. ' [protobuf_unittest.TestMessageSetExtension2] {\n'
  520. ' str: \"foo\"\n'
  521. ' }\n'
  522. '}\n')
  523. text_format.Parse(text, message)
  524. ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension
  525. ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension
  526. self.assertEqual(23, message.message_set.Extensions[ext1].i)
  527. self.assertEqual('foo', message.message_set.Extensions[ext2].str)
  528. def testPrintAllExtensions(self):
  529. message = unittest_pb2.TestAllExtensions()
  530. test_util.SetAllExtensions(message)
  531. self.CompareToGoldenFile(
  532. self.RemoveRedundantZeros(text_format.MessageToString(message)),
  533. 'text_format_unittest_extensions_data.txt')
  534. def testPrintAllExtensionsPointy(self):
  535. message = unittest_pb2.TestAllExtensions()
  536. test_util.SetAllExtensions(message)
  537. self.CompareToGoldenFile(
  538. self.RemoveRedundantZeros(text_format.MessageToString(
  539. message, pointy_brackets=True)),
  540. 'text_format_unittest_extensions_data_pointy.txt')
  541. def testParseGoldenExtensions(self):
  542. golden_text = '\n'.join(self.ReadGolden(
  543. 'text_format_unittest_extensions_data.txt'))
  544. parsed_message = unittest_pb2.TestAllExtensions()
  545. text_format.Parse(golden_text, parsed_message)
  546. message = unittest_pb2.TestAllExtensions()
  547. test_util.SetAllExtensions(message)
  548. self.assertEqual(message, parsed_message)
  549. def testParseAllExtensions(self):
  550. message = unittest_pb2.TestAllExtensions()
  551. test_util.SetAllExtensions(message)
  552. ascii_text = text_format.MessageToString(message)
  553. parsed_message = unittest_pb2.TestAllExtensions()
  554. text_format.Parse(ascii_text, parsed_message)
  555. self.assertEqual(message, parsed_message)
  556. def testParseBadExtension(self):
  557. message = unittest_pb2.TestAllExtensions()
  558. text = '[unknown_extension]: 8\n'
  559. six.assertRaisesRegex(self,
  560. text_format.ParseError,
  561. '1:2 : Extension "unknown_extension" not registered.',
  562. text_format.Parse, text, message)
  563. message = unittest_pb2.TestAllTypes()
  564. six.assertRaisesRegex(self,
  565. text_format.ParseError,
  566. ('1:2 : Message type "protobuf_unittest.TestAllTypes" does not have '
  567. 'extensions.'),
  568. text_format.Parse, text, message)
  569. def testMergeDuplicateExtensionScalars(self):
  570. message = unittest_pb2.TestAllExtensions()
  571. text = ('[protobuf_unittest.optional_int32_extension]: 42 '
  572. '[protobuf_unittest.optional_int32_extension]: 67')
  573. text_format.Merge(text, message)
  574. self.assertEqual(
  575. 67,
  576. message.Extensions[unittest_pb2.optional_int32_extension])
  577. def testParseDuplicateExtensionScalars(self):
  578. message = unittest_pb2.TestAllExtensions()
  579. text = ('[protobuf_unittest.optional_int32_extension]: 42 '
  580. '[protobuf_unittest.optional_int32_extension]: 67')
  581. six.assertRaisesRegex(self,
  582. text_format.ParseError,
  583. ('1:96 : Message type "protobuf_unittest.TestAllExtensions" '
  584. 'should not have multiple '
  585. '"protobuf_unittest.optional_int32_extension" extensions.'),
  586. text_format.Parse, text, message)
  587. def testParseDuplicateNestedMessageScalars(self):
  588. message = unittest_pb2.TestAllTypes()
  589. text = ('optional_nested_message { bb: 1 } '
  590. 'optional_nested_message { bb: 2 }')
  591. six.assertRaisesRegex(self,
  592. text_format.ParseError,
  593. ('1:65 : Message type "protobuf_unittest.TestAllTypes.NestedMessage" '
  594. 'should not have multiple "bb" fields.'),
  595. text_format.Parse, text, message)
  596. def testParseDuplicateScalars(self):
  597. message = unittest_pb2.TestAllTypes()
  598. text = ('optional_int32: 42 '
  599. 'optional_int32: 67')
  600. six.assertRaisesRegex(self,
  601. text_format.ParseError,
  602. ('1:36 : Message type "protobuf_unittest.TestAllTypes" should not '
  603. 'have multiple "optional_int32" fields.'),
  604. text_format.Parse, text, message)
  605. def testParseGroupNotClosed(self):
  606. message = unittest_pb2.TestAllTypes()
  607. text = 'RepeatedGroup: <'
  608. six.assertRaisesRegex(self,
  609. text_format.ParseError, '1:16 : Expected ">".',
  610. text_format.Parse, text, message)
  611. text = 'RepeatedGroup: {'
  612. six.assertRaisesRegex(self,
  613. text_format.ParseError, '1:16 : Expected "}".',
  614. text_format.Parse, text, message)
  615. def testParseEmptyGroup(self):
  616. message = unittest_pb2.TestAllTypes()
  617. text = 'OptionalGroup: {}'
  618. text_format.Parse(text, message)
  619. self.assertTrue(message.HasField('optionalgroup'))
  620. message.Clear()
  621. message = unittest_pb2.TestAllTypes()
  622. text = 'OptionalGroup: <>'
  623. text_format.Parse(text, message)
  624. self.assertTrue(message.HasField('optionalgroup'))
  625. # Maps aren't really proto2-only, but our test schema only has maps for
  626. # proto2.
  627. def testParseMap(self):
  628. text = ('map_int32_int32 {\n'
  629. ' key: -123\n'
  630. ' value: -456\n'
  631. '}\n'
  632. 'map_int64_int64 {\n'
  633. ' key: -8589934592\n'
  634. ' value: -17179869184\n'
  635. '}\n'
  636. 'map_uint32_uint32 {\n'
  637. ' key: 123\n'
  638. ' value: 456\n'
  639. '}\n'
  640. 'map_uint64_uint64 {\n'
  641. ' key: 8589934592\n'
  642. ' value: 17179869184\n'
  643. '}\n'
  644. 'map_string_string {\n'
  645. ' key: "abc"\n'
  646. ' value: "123"\n'
  647. '}\n'
  648. 'map_int32_foreign_message {\n'
  649. ' key: 111\n'
  650. ' value {\n'
  651. ' c: 5\n'
  652. ' }\n'
  653. '}\n')
  654. message = map_unittest_pb2.TestMap()
  655. text_format.Parse(text, message)
  656. self.assertEqual(-456, message.map_int32_int32[-123])
  657. self.assertEqual(-2**34, message.map_int64_int64[-2**33])
  658. self.assertEqual(456, message.map_uint32_uint32[123])
  659. self.assertEqual(2**34, message.map_uint64_uint64[2**33])
  660. self.assertEqual("123", message.map_string_string["abc"])
  661. self.assertEqual(5, message.map_int32_foreign_message[111].c)
  662. class TokenizerTest(unittest.TestCase):
  663. def testSimpleTokenCases(self):
  664. text = ('identifier1:"string1"\n \n\n'
  665. 'identifier2 : \n \n123 \n identifier3 :\'string\'\n'
  666. 'identifiER_4 : 1.1e+2 ID5:-0.23 ID6:\'aaaa\\\'bbbb\'\n'
  667. 'ID7 : "aa\\"bb"\n\n\n\n ID8: {A:inf B:-inf C:true D:false}\n'
  668. 'ID9: 22 ID10: -111111111111111111 ID11: -22\n'
  669. 'ID12: 2222222222222222222 ID13: 1.23456f ID14: 1.2e+2f '
  670. 'false_bool: 0 true_BOOL:t \n true_bool1: 1 false_BOOL1:f ')
  671. tokenizer = text_format._Tokenizer(text.splitlines())
  672. methods = [(tokenizer.ConsumeIdentifier, 'identifier1'),
  673. ':',
  674. (tokenizer.ConsumeString, 'string1'),
  675. (tokenizer.ConsumeIdentifier, 'identifier2'),
  676. ':',
  677. (tokenizer.ConsumeInt32, 123),
  678. (tokenizer.ConsumeIdentifier, 'identifier3'),
  679. ':',
  680. (tokenizer.ConsumeString, 'string'),
  681. (tokenizer.ConsumeIdentifier, 'identifiER_4'),
  682. ':',
  683. (tokenizer.ConsumeFloat, 1.1e+2),
  684. (tokenizer.ConsumeIdentifier, 'ID5'),
  685. ':',
  686. (tokenizer.ConsumeFloat, -0.23),
  687. (tokenizer.ConsumeIdentifier, 'ID6'),
  688. ':',
  689. (tokenizer.ConsumeString, 'aaaa\'bbbb'),
  690. (tokenizer.ConsumeIdentifier, 'ID7'),
  691. ':',
  692. (tokenizer.ConsumeString, 'aa\"bb'),
  693. (tokenizer.ConsumeIdentifier, 'ID8'),
  694. ':',
  695. '{',
  696. (tokenizer.ConsumeIdentifier, 'A'),
  697. ':',
  698. (tokenizer.ConsumeFloat, float('inf')),
  699. (tokenizer.ConsumeIdentifier, 'B'),
  700. ':',
  701. (tokenizer.ConsumeFloat, -float('inf')),
  702. (tokenizer.ConsumeIdentifier, 'C'),
  703. ':',
  704. (tokenizer.ConsumeBool, True),
  705. (tokenizer.ConsumeIdentifier, 'D'),
  706. ':',
  707. (tokenizer.ConsumeBool, False),
  708. '}',
  709. (tokenizer.ConsumeIdentifier, 'ID9'),
  710. ':',
  711. (tokenizer.ConsumeUint32, 22),
  712. (tokenizer.ConsumeIdentifier, 'ID10'),
  713. ':',
  714. (tokenizer.ConsumeInt64, -111111111111111111),
  715. (tokenizer.ConsumeIdentifier, 'ID11'),
  716. ':',
  717. (tokenizer.ConsumeInt32, -22),
  718. (tokenizer.ConsumeIdentifier, 'ID12'),
  719. ':',
  720. (tokenizer.ConsumeUint64, 2222222222222222222),
  721. (tokenizer.ConsumeIdentifier, 'ID13'),
  722. ':',
  723. (tokenizer.ConsumeFloat, 1.23456),
  724. (tokenizer.ConsumeIdentifier, 'ID14'),
  725. ':',
  726. (tokenizer.ConsumeFloat, 1.2e+2),
  727. (tokenizer.ConsumeIdentifier, 'false_bool'),
  728. ':',
  729. (tokenizer.ConsumeBool, False),
  730. (tokenizer.ConsumeIdentifier, 'true_BOOL'),
  731. ':',
  732. (tokenizer.ConsumeBool, True),
  733. (tokenizer.ConsumeIdentifier, 'true_bool1'),
  734. ':',
  735. (tokenizer.ConsumeBool, True),
  736. (tokenizer.ConsumeIdentifier, 'false_BOOL1'),
  737. ':',
  738. (tokenizer.ConsumeBool, False)]
  739. i = 0
  740. while not tokenizer.AtEnd():
  741. m = methods[i]
  742. if type(m) == str:
  743. token = tokenizer.token
  744. self.assertEqual(token, m)
  745. tokenizer.NextToken()
  746. else:
  747. self.assertEqual(m[1], m[0]())
  748. i += 1
  749. def testConsumeIntegers(self):
  750. # This test only tests the failures in the integer parsing methods as well
  751. # as the '0' special cases.
  752. int64_max = (1 << 63) - 1
  753. uint32_max = (1 << 32) - 1
  754. text = '-1 %d %d' % (uint32_max + 1, int64_max + 1)
  755. tokenizer = text_format._Tokenizer(text.splitlines())
  756. self.assertRaises(text_format.ParseError, tokenizer.ConsumeUint32)
  757. self.assertRaises(text_format.ParseError, tokenizer.ConsumeUint64)
  758. self.assertEqual(-1, tokenizer.ConsumeInt32())
  759. self.assertRaises(text_format.ParseError, tokenizer.ConsumeUint32)
  760. self.assertRaises(text_format.ParseError, tokenizer.ConsumeInt32)
  761. self.assertEqual(uint32_max + 1, tokenizer.ConsumeInt64())
  762. self.assertRaises(text_format.ParseError, tokenizer.ConsumeInt64)
  763. self.assertEqual(int64_max + 1, tokenizer.ConsumeUint64())
  764. self.assertTrue(tokenizer.AtEnd())
  765. text = '-0 -0 0 0'
  766. tokenizer = text_format._Tokenizer(text.splitlines())
  767. self.assertEqual(0, tokenizer.ConsumeUint32())
  768. self.assertEqual(0, tokenizer.ConsumeUint64())
  769. self.assertEqual(0, tokenizer.ConsumeUint32())
  770. self.assertEqual(0, tokenizer.ConsumeUint64())
  771. self.assertTrue(tokenizer.AtEnd())
  772. def testConsumeByteString(self):
  773. text = '"string1\''
  774. tokenizer = text_format._Tokenizer(text.splitlines())
  775. self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
  776. text = 'string1"'
  777. tokenizer = text_format._Tokenizer(text.splitlines())
  778. self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
  779. text = '\n"\\xt"'
  780. tokenizer = text_format._Tokenizer(text.splitlines())
  781. self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
  782. text = '\n"\\"'
  783. tokenizer = text_format._Tokenizer(text.splitlines())
  784. self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
  785. text = '\n"\\x"'
  786. tokenizer = text_format._Tokenizer(text.splitlines())
  787. self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
  788. def testConsumeBool(self):
  789. text = 'not-a-bool'
  790. tokenizer = text_format._Tokenizer(text.splitlines())
  791. self.assertRaises(text_format.ParseError, tokenizer.ConsumeBool)
  792. if __name__ == '__main__':
  793. unittest.main()