1 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import imp 6 import os.path 7 import sys 8 import unittest 9 10 # Disable lint check for finding modules: 11 # pylint: disable=F0401 12 13 def _GetDirAbove(dirname): 14 """Returns the directory "above" this file containing |dirname| (which must 15 also be "above" this file).""" 16 path = os.path.abspath(__file__) 17 while True: 18 path, tail = os.path.split(path) 19 assert tail 20 if tail == dirname: 21 return path 22 23 try: 24 imp.find_module("mojom") 25 except ImportError: 26 sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) 27 import mojom.parse.ast as ast 28 import mojom.parse.lexer as lexer 29 import mojom.parse.parser as parser 30 31 32 class ParserTest(unittest.TestCase): 33 """Tests |parser.Parse()|.""" 34 35 def testTrivialValidSource(self): 36 """Tests a trivial, but valid, .mojom source.""" 37 source = """\ 38 // This is a comment. 39 40 module my_module { 41 } 42 """ 43 self.assertEquals(parser.Parse(source, "my_file.mojom"), 44 [("MODULE", "my_module", None, None)]) 45 46 def testSourceWithCrLfs(self): 47 """Tests a .mojom source with CR-LFs instead of LFs.""" 48 source = "// This is a comment.\r\n\r\nmodule my_module {\r\n}\r\n" 49 self.assertEquals(parser.Parse(source, "my_file.mojom"), 50 [("MODULE", "my_module", None, None)]) 51 52 def testUnexpectedEOF(self): 53 """Tests a "truncated" .mojom source.""" 54 source = """\ 55 // This is a comment. 56 57 module my_module { 58 """ 59 with self.assertRaisesRegexp( 60 parser.ParseError, 61 r"^my_file\.mojom: Error: Unexpected end of file$"): 62 parser.Parse(source, "my_file.mojom") 63 64 def testCommentLineNumbers(self): 65 """Tests that line numbers are correctly tracked when comments are 66 present.""" 67 source1 = """\ 68 // Isolated C++-style comments. 69 70 // Foo. 71 asdf1 72 """ 73 with self.assertRaisesRegexp( 74 parser.ParseError, 75 r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\nasdf1$"): 76 parser.Parse(source1, "my_file.mojom") 77 78 source2 = """\ 79 // Consecutive C++-style comments. 80 // Foo. 81 // Bar. 82 83 struct Yada { // Baz. 84 // Quux. 85 int32 x; 86 }; 87 88 asdf2 89 """ 90 with self.assertRaisesRegexp( 91 parser.ParseError, 92 r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\nasdf2$"): 93 parser.Parse(source2, "my_file.mojom") 94 95 source3 = """\ 96 /* Single-line C-style comments. */ 97 /* Foobar. */ 98 99 /* Baz. */ 100 asdf3 101 """ 102 with self.assertRaisesRegexp( 103 parser.ParseError, 104 r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\nasdf3$"): 105 parser.Parse(source3, "my_file.mojom") 106 107 source4 = """\ 108 /* Multi-line C-style comments. 109 */ 110 /* 111 Foo. 112 Bar. 113 */ 114 115 /* Baz 116 Quux. */ 117 asdf4 118 """ 119 with self.assertRaisesRegexp( 120 parser.ParseError, 121 r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\nasdf4$"): 122 parser.Parse(source4, "my_file.mojom") 123 124 125 def testSimpleStruct(self): 126 """Tests a simple .mojom source that just defines a struct.""" 127 source = """\ 128 module my_module { 129 130 struct MyStruct { 131 int32 a; 132 double b; 133 }; 134 135 } // module my_module 136 """ 137 expected = \ 138 [('MODULE', 139 'my_module', 140 None, 141 [('STRUCT', 142 'MyStruct', 143 None, 144 [('FIELD', 'int32', 'a', ast.Ordinal(None), None), 145 ('FIELD', 'double', 'b', ast.Ordinal(None), None)])])] 146 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 147 148 def testSimpleStructWithoutModule(self): 149 """Tests a simple struct without an enclosing module.""" 150 source = """\ 151 struct MyStruct { 152 int32 a; 153 double b; 154 }; 155 """ 156 expected = \ 157 [('MODULE', 158 '', 159 None, 160 [('STRUCT', 161 'MyStruct', 162 None, 163 [('FIELD', 'int32', 'a', ast.Ordinal(None), None), 164 ('FIELD', 'double', 'b', ast.Ordinal(None), None)])])] 165 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 166 167 def testMissingModuleName(self): 168 """Tests an (invalid) .mojom with a missing module name.""" 169 source1 = """\ 170 // Missing module name. 171 module { 172 struct MyStruct { 173 int32 a; 174 }; 175 } 176 """ 177 with self.assertRaisesRegexp( 178 parser.ParseError, 179 r"^my_file\.mojom:2: Error: Unexpected '{':\nmodule {$"): 180 parser.Parse(source1, "my_file.mojom") 181 182 # Another similar case, but make sure that line-number tracking/reporting 183 # is correct. 184 source2 = """\ 185 module 186 // This line intentionally left unblank. 187 188 { 189 } 190 """ 191 with self.assertRaisesRegexp( 192 parser.ParseError, 193 r"^my_file\.mojom:4: Error: Unexpected '{':\n{$"): 194 parser.Parse(source2, "my_file.mojom") 195 196 def testEnumInitializers(self): 197 """Tests an enum with simple initialized values.""" 198 source = """\ 199 module my_module { 200 201 enum MyEnum { 202 MY_ENUM_NEG1 = -1, 203 MY_ENUM_ZERO = 0, 204 MY_ENUM_1 = +1, 205 MY_ENUM_2, 206 }; 207 208 } // my_module 209 """ 210 expected = \ 211 [('MODULE', 212 'my_module', 213 None, 214 [('ENUM', 215 'MyEnum', 216 [('ENUM_FIELD', 'MY_ENUM_NEG1', '-1'), 217 ('ENUM_FIELD', 'MY_ENUM_ZERO', '0'), 218 ('ENUM_FIELD', 'MY_ENUM_1', '+1'), 219 ('ENUM_FIELD', 'MY_ENUM_2', None)])])] 220 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 221 222 def testConst(self): 223 """Tests some constants and struct memebers initialized with them.""" 224 source = """\ 225 module my_module { 226 227 struct MyStruct { 228 const int8 kNumber = -1; 229 int8 number@0 = kNumber; 230 }; 231 232 } // my_module 233 """ 234 expected = \ 235 [('MODULE', 236 'my_module', 237 None, 238 [('STRUCT', 239 'MyStruct', None, 240 [('CONST', 'int8', 'kNumber', '-1'), 241 ('FIELD', 'int8', 'number', 242 ast.Ordinal(0), ('IDENTIFIER', 'kNumber'))])])] 243 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 244 245 def testNoConditionals(self): 246 """Tests that ?: is not allowed.""" 247 source = """\ 248 module my_module { 249 250 enum MyEnum { 251 MY_ENUM_1 = 1 ? 2 : 3 252 }; 253 254 } // my_module 255 """ 256 with self.assertRaisesRegexp( 257 lexer.LexError, 258 r"^my_file\.mojom:4: Error: Illegal character '\?'$"): 259 parser.Parse(source, "my_file.mojom") 260 261 def testSimpleOrdinals(self): 262 """Tests that (valid) ordinal values are scanned correctly.""" 263 source = """\ 264 module my_module { 265 266 // This isn't actually valid .mojom, but the problem (missing ordinals) should 267 // be handled at a different level. 268 struct MyStruct { 269 int32 a0@0; 270 int32 a1@1; 271 int32 a2@2; 272 int32 a9@9; 273 int32 a10 @10; 274 int32 a11 @11; 275 int32 a29 @29; 276 int32 a1234567890 @1234567890; 277 }; 278 279 } // module my_module 280 """ 281 expected = \ 282 [('MODULE', 283 'my_module', 284 None, 285 [('STRUCT', 286 'MyStruct', 287 None, 288 [('FIELD', 'int32', 'a0', ast.Ordinal(0), None), 289 ('FIELD', 'int32', 'a1', ast.Ordinal(1), None), 290 ('FIELD', 'int32', 'a2', ast.Ordinal(2), None), 291 ('FIELD', 'int32', 'a9', ast.Ordinal(9), None), 292 ('FIELD', 'int32', 'a10', ast.Ordinal(10), None), 293 ('FIELD', 'int32', 'a11', ast.Ordinal(11), None), 294 ('FIELD', 'int32', 'a29', ast.Ordinal(29), None), 295 ('FIELD', 'int32', 'a1234567890', ast.Ordinal(1234567890), None)])])] 296 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 297 298 def testInvalidOrdinals(self): 299 """Tests that (lexically) invalid ordinals are correctly detected.""" 300 source1 = """\ 301 module my_module { 302 303 struct MyStruct { 304 int32 a_missing@; 305 }; 306 307 } // module my_module 308 """ 309 with self.assertRaisesRegexp( 310 lexer.LexError, 311 r"^my_file\.mojom:4: Error: Missing ordinal value$"): 312 parser.Parse(source1, "my_file.mojom") 313 314 source2 = """\ 315 module my_module { 316 317 struct MyStruct { 318 int32 a_octal@01; 319 }; 320 321 } // module my_module 322 """ 323 with self.assertRaisesRegexp( 324 lexer.LexError, 325 r"^my_file\.mojom:4: Error: " 326 r"Octal and hexadecimal ordinal values not allowed$"): 327 parser.Parse(source2, "my_file.mojom") 328 329 source3 = """\ 330 module my_module { struct MyStruct { int32 a_invalid_octal@08; }; } 331 """ 332 with self.assertRaisesRegexp( 333 lexer.LexError, 334 r"^my_file\.mojom:1: Error: " 335 r"Octal and hexadecimal ordinal values not allowed$"): 336 parser.Parse(source3, "my_file.mojom") 337 338 source4 = """\ 339 module my_module { struct MyStruct { int32 a_hex@0x1aB9; }; } 340 """ 341 with self.assertRaisesRegexp( 342 lexer.LexError, 343 r"^my_file\.mojom:1: Error: " 344 r"Octal and hexadecimal ordinal values not allowed$"): 345 parser.Parse(source4, "my_file.mojom") 346 347 source5 = """\ 348 module my_module { struct MyStruct { int32 a_hex@0X0; }; } 349 """ 350 with self.assertRaisesRegexp( 351 lexer.LexError, 352 r"^my_file\.mojom:1: Error: " 353 r"Octal and hexadecimal ordinal values not allowed$"): 354 parser.Parse(source5, "my_file.mojom") 355 356 source6 = """\ 357 struct MyStruct { 358 int32 a_too_big@999999999999; 359 }; 360 """ 361 with self.assertRaisesRegexp( 362 parser.ParseError, 363 r"^my_file\.mojom:2: Error: " 364 r"Ordinal value 999999999999 too large:\n" 365 r" int32 a_too_big@999999999999;$"): 366 parser.Parse(source6, "my_file.mojom") 367 368 def testNestedNamespace(self): 369 """Tests that "nested" namespaces work.""" 370 source = """\ 371 module my.mod { 372 373 struct MyStruct { 374 int32 a; 375 }; 376 377 } // module my.mod 378 """ 379 expected = \ 380 [('MODULE', 381 'my.mod', 382 None, 383 [('STRUCT', 384 'MyStruct', 385 None, 386 [('FIELD', 'int32', 'a', ast.Ordinal(None), None)])])] 387 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 388 389 def testValidHandleTypes(self): 390 """Tests (valid) handle types.""" 391 source = """\ 392 struct MyStruct { 393 handle a; 394 handle<data_pipe_consumer> b; 395 handle <data_pipe_producer> c; 396 handle < message_pipe > d; 397 handle 398 < shared_buffer 399 > e; 400 }; 401 """ 402 expected = \ 403 [('MODULE', 404 '', 405 None, 406 [('STRUCT', 407 'MyStruct', 408 None, 409 [('FIELD', 'handle', 'a', ast.Ordinal(None), None), 410 ('FIELD', 'handle<data_pipe_consumer>', 'b', ast.Ordinal(None), None), 411 ('FIELD', 'handle<data_pipe_producer>', 'c', ast.Ordinal(None), None), 412 ('FIELD', 'handle<message_pipe>', 'd', ast.Ordinal(None), None), 413 ('FIELD', 'handle<shared_buffer>', 'e', ast.Ordinal(None), None)])])] 414 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 415 416 def testInvalidHandleType(self): 417 """Tests an invalid (unknown) handle type.""" 418 source = """\ 419 struct MyStruct { 420 handle<wtf_is_this> foo; 421 }; 422 """ 423 with self.assertRaisesRegexp( 424 parser.ParseError, 425 r"^my_file\.mojom:2: Error: " 426 r"Invalid handle type 'wtf_is_this':\n" 427 r" handle<wtf_is_this> foo;$"): 428 parser.Parse(source, "my_file.mojom") 429 430 def testValidDefaultValues(self): 431 """Tests default values that are valid (to the parser).""" 432 source = """\ 433 struct MyStruct { 434 int16 a0 = 0; 435 uint16 a1 = 0x0; 436 uint16 a2 = 0x00; 437 uint16 a3 = 0x01; 438 uint16 a4 = 0xcd; 439 int32 a5 = 12345; 440 int64 a6 = -12345; 441 int64 a7 = +12345; 442 uint32 a8 = 0x12cd3; 443 uint32 a9 = -0x12cD3; 444 uint32 a10 = +0x12CD3; 445 bool a11 = true; 446 bool a12 = false; 447 float a13 = 1.2345; 448 float a14 = -1.2345; 449 float a15 = +1.2345; 450 float a16 = 123.; 451 float a17 = .123; 452 double a18 = 1.23E10; 453 double a19 = 1.E-10; 454 double a20 = .5E+10; 455 double a21 = -1.23E10; 456 double a22 = +.123E10; 457 }; 458 """ 459 expected = \ 460 [('MODULE', 461 '', 462 None, 463 [('STRUCT', 464 'MyStruct', 465 None, 466 [('FIELD', 'int16', 'a0', ast.Ordinal(None), '0'), 467 ('FIELD', 'uint16', 'a1', ast.Ordinal(None), '0x0'), 468 ('FIELD', 'uint16', 'a2', ast.Ordinal(None), '0x00'), 469 ('FIELD', 'uint16', 'a3', ast.Ordinal(None), '0x01'), 470 ('FIELD', 'uint16', 'a4', ast.Ordinal(None), '0xcd'), 471 ('FIELD', 'int32', 'a5' , ast.Ordinal(None), '12345'), 472 ('FIELD', 'int64', 'a6', ast.Ordinal(None), '-12345'), 473 ('FIELD', 'int64', 'a7', ast.Ordinal(None), '+12345'), 474 ('FIELD', 'uint32', 'a8', ast.Ordinal(None), '0x12cd3'), 475 ('FIELD', 'uint32', 'a9', ast.Ordinal(None), '-0x12cD3'), 476 ('FIELD', 'uint32', 'a10', ast.Ordinal(None), '+0x12CD3'), 477 ('FIELD', 'bool', 'a11', ast.Ordinal(None), 'true'), 478 ('FIELD', 'bool', 'a12', ast.Ordinal(None), 'false'), 479 ('FIELD', 'float', 'a13', ast.Ordinal(None), '1.2345'), 480 ('FIELD', 'float', 'a14', ast.Ordinal(None), '-1.2345'), 481 ('FIELD', 'float', 'a15', ast.Ordinal(None), '+1.2345'), 482 ('FIELD', 'float', 'a16', ast.Ordinal(None), '123.'), 483 ('FIELD', 'float', 'a17', ast.Ordinal(None), '.123'), 484 ('FIELD', 'double', 'a18', ast.Ordinal(None), '1.23E10'), 485 ('FIELD', 'double', 'a19', ast.Ordinal(None), '1.E-10'), 486 ('FIELD', 'double', 'a20', ast.Ordinal(None), '.5E+10'), 487 ('FIELD', 'double', 'a21', ast.Ordinal(None), '-1.23E10'), 488 ('FIELD', 'double', 'a22', ast.Ordinal(None), '+.123E10')])])] 489 self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) 490 491 492 if __name__ == "__main__": 493 unittest.main() 494