1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright 2017, Data61 6# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 7# ABN 41 687 119 230. 8# 9# This software may be distributed and modified according to the terms of 10# the BSD 2-Clause license. Note that NO WARRANTY is provided. 11# See "LICENSE_BSD2.txt" for details. 12# 13# @TAG(DATA61_BSD) 14# 15 16from __future__ import absolute_import, division, print_function, \ 17 unicode_literals 18 19import os, six, sys, unittest 20 21ME = os.path.abspath(__file__) 22 23# Make CAmkES importable 24sys.path.append(os.path.join(os.path.dirname(ME), '../../..')) 25 26from camkes.ast import Assembly, AttributeReference, Component, \ 27 Composition, Connection, ConnectionEnd, Connector, Group, Instance, \ 28 LiftedAST, Procedure, Provides, Setting, Uses 29from camkes.internal.tests.utils import CAmkESTest 30from camkes.parser import ParseError 31from camkes.parser.stage0 import CPP, Reader 32from camkes.parser.stage1 import Parse1 33from camkes.parser.stage2 import Parse2 34from camkes.parser.stage3 import Parse3 35from camkes.parser.stage4 import Parse4 36 37class TestStage4(CAmkESTest): 38 def setUp(self): 39 super(TestStage4, self).setUp() 40 r = Reader() 41 s1 = Parse1(r) 42 s2 = Parse2(s1) 43 s3 = Parse3(s2) 44 self.parser = Parse4(s3) 45 self.forward_parser = Parse4(s3, True) 46 47 r = CPP() 48 s1 = Parse1(r) 49 s2 = Parse2(s1) 50 s3 = Parse3(s2) 51 self.cpp_parser = Parse4(s3) 52 53 def test_empty_string(self): 54 ast, read = self.parser.parse_string('') 55 56 self.assertIsInstance(ast, LiftedAST) 57 self.assertLen(ast.items, 0) 58 self.assertLen(read, 0) 59 60 def test_no_references(self): 61 ast, _ = self.parser.parse_string('component foo {}') 62 63 self.assertIsInstance(ast, LiftedAST) 64 self.assertLen(ast.items, 1) 65 component = ast.items[0] 66 67 self.assertIsInstance(component, Component) 68 self.assertEqual(component.name, 'foo') 69 70 def test_basic_reference(self): 71 ast, read = self.parser.parse_string('component Foo {}\n' 72 'assembly { composition { component Foo foo; } }') 73 74 self.assertLen(ast.items, 2) 75 Foo, assembly = ast.items 76 77 self.assertIsInstance(Foo, Component) 78 self.assertIsInstance(assembly, Assembly) 79 80 self.assertLen(assembly.composition.instances, 1) 81 foo = assembly.composition.instances[0] 82 self.assertEqual(foo.name, 'foo') 83 self.assertEqual(foo.type, Foo) 84 85 def test_overlapping_names(self): 86 ast, read = self.parser.parse_string('component foo {}\n' 87 'assembly { composition { component foo foo; } }') 88 89 self.assertLen(ast.items, 2) 90 Foo, assembly = ast.items 91 92 self.assertIsInstance(Foo, Component) 93 self.assertIsInstance(assembly, Assembly) 94 95 self.assertLen(assembly.composition.instances, 1) 96 foo = assembly.composition.instances[0] 97 self.assertEqual(foo.name, 'foo') 98 self.assertEqual(foo.type, Foo) 99 100 def test_overlapping_names2(self): 101 ast, read = self.parser.parse_string(''' 102 procedure foo { } 103 component foo {} 104 assembly { 105 composition { 106 component foo foo; 107 } 108 } 109 ''') 110 111 self.assertLen(ast.items, 3) 112 FooProc, FooComp, assembly = ast.items 113 114 self.assertIsInstance(FooComp, Component) 115 self.assertIsInstance(assembly, Assembly) 116 117 self.assertLen(assembly.composition.instances, 1) 118 foo = assembly.composition.instances[0] 119 self.assertEqual(foo.name, 'foo') 120 self.assertEqual(foo.type, FooComp) 121 122 def test_overlapping_names3(self): 123 ast, read = self.parser.parse_string(''' 124 component foo {} 125 procedure foo { } 126 assembly { 127 composition { 128 component foo foo; 129 } 130 } 131 ''') 132 133 self.assertLen(ast.items, 3) 134 FooComp, FooProc, assembly = ast.items 135 136 self.assertIsInstance(FooComp, Component) 137 self.assertIsInstance(assembly, Assembly) 138 139 self.assertLen(assembly.composition.instances, 1) 140 foo = assembly.composition.instances[0] 141 self.assertEqual(foo.name, 'foo') 142 self.assertEqual(foo.type, FooComp) 143 144 def test_colliding_names(self): 145 with six.assertRaisesRegex(self, ParseError, 146 r'duplicate definition of Component \'foo\''): 147 self.parser.parse_string(''' 148 component foo {} 149 component foo { } 150 ''') 151 152 def test_colliding_names_and_groups(self): 153 with six.assertRaisesRegex(self, ParseError, 154 r'6:31: duplicate definition of group/component \'b\'; previous definition was at <unnamed>:5'): 155 self.parser.parse_string(''' 156 component bar {} 157 assembly { 158 composition { 159 component bar b; 160 group b { 161 component bar c; 162 } 163 } 164 } ''') 165 166 def test_unresolved_reference(self): 167 with six.assertRaisesRegex(self, ParseError, 168 r'unknown reference to \'Foo\''): 169 self.parser.parse_string(''' 170 component bar {} 171 assembly { 172 composition { 173 component Foo foo; 174 } 175 } 176 ''') 177 178 def test_unresolved_reference2(self): 179 with six.assertRaisesRegex(self, ParseError, 180 r'unknown reference to \'Foo\''): 181 self.parser.parse_string(''' 182 procedure Foo {} 183 assembly { 184 composition { 185 component Foo foo; 186 } 187 } 188 ''') 189 190 def test_unresolved_reference3(self): 191 with six.assertRaisesRegex(self, ParseError, 192 r'unknown reference to \'Foo\''): 193 self.parser.parse_string(''' 194 procedure Foo {} 195 composition Foo {} 196 assembly { 197 composition { 198 component Foo foo; 199 } 200 } 201 ''') 202 203 def test_forward_reference(self): 204 with six.assertRaisesRegex(self, ParseError, 205 r'unknown reference to \'Foo\''): 206 self.parser.parse_string(''' 207 component bar {} 208 assembly { 209 composition { 210 component Foo foo; 211 } 212 } 213 component Foo {} 214 ''') 215 216 def test_attribute_reference(self): 217 content, _ = self.parser.parse_string(''' 218 component B { 219 attribute string b_str; 220 } 221 222 component A { 223 attribute string a_str; 224 225 composition { 226 component B b; 227 } 228 configuration { 229 b.b_str <- a_str; 230 } 231 }''') 232 233 self.assertIsInstance(content, LiftedAST) 234 self.assertLen(content.items, 2) 235 A = content.items[1] 236 237 self.assertIsInstance(A, Component) 238 self.assertIsNotNone(A.configuration) 239 self.assertLen(A.configuration.settings, 1) 240 b_b_str = A.configuration.settings[0] 241 242 self.assertIsInstance(b_b_str, Setting) 243 self.assertIsInstance(b_b_str.value, AttributeReference) 244 a_str = b_b_str.value 245 246 self.assertEqual(a_str.reference, 'a_str') 247 248 def test_group_with_connection(self): 249 ast, _ = self.parser.parse_string(''' 250 connector Conn { 251 from Procedure; 252 to Procedure; 253 } 254 procedure P {} 255 component A { 256 provides P a; 257 uses P b; 258 } 259 composition { 260 component A a1; 261 group g { 262 component A a2; 263 } 264 connection Conn c(from a1.b, to g.a2.a); 265 connection Conn c2(from g.a2.b, to a1.a); 266 } 267 ''') 268 269 self.assertLen(ast.items, 4) 270 _, _, A, comp = ast.items 271 272 self.assertIsInstance(A, Component) 273 self.assertLen(A.provides, 1) 274 a = A.provides[0] 275 self.assertIsInstance(a, Provides) 276 self.assertLen(A.uses, 1) 277 b = A.uses[0] 278 self.assertIsInstance(b, Uses) 279 280 self.assertIsInstance(comp, Composition) 281 self.assertLen(comp.groups, 1) 282 g = comp.groups[0] 283 self.assertIsInstance(g, Group) 284 285 self.assertLen(g.instances, 1) 286 a2 = g.instances[0] 287 self.assertIsInstance(a2, Instance) 288 289 self.assertLen(comp.instances, 1) 290 a1 = comp.instances[0] 291 self.assertIsInstance(a1, Instance) 292 293 self.assertLen(comp.connections, 2) 294 c, c2 = comp.connections 295 self.assertIsInstance(c, Connection) 296 self.assertIsInstance(c2, Connection) 297 298 self.assertLen(c.from_ends, 1) 299 a1_b = c.from_ends[0] 300 self.assertIsInstance(a1_b, ConnectionEnd) 301 302 self.assertIs(a1_b.instance, a1) 303 self.assertIs(a1_b.interface, b) 304 305 self.assertLen(c.to_ends, 1) 306 g_a2_a = c.to_ends[0] 307 self.assertIsInstance(g_a2_a, ConnectionEnd) 308 309 self.assertIs(g_a2_a.instance, a2) 310 self.assertIs(g_a2_a.interface, a) 311 312 self.assertLen(c2.from_ends, 1) 313 g_a2_b = c2.from_ends[0] 314 self.assertIsInstance(g_a2_b, ConnectionEnd) 315 316 self.assertIs(g_a2_b.instance, a2) 317 self.assertIs(g_a2_b.interface, b) 318 319 self.assertLen(c2.to_ends, 1) 320 a1_a = c2.to_ends[0] 321 self.assertIsInstance(a1_a, ConnectionEnd) 322 323 self.assertIs(a1_a.instance, a1) 324 self.assertIs(a1_a.interface, a) 325 326 def test_group_with_connection2(self): 327 ''' 328 There's a slight quirk with reference resolution involving groups 329 where, though you may have interleaved instances, groups and 330 connections in your spec, these come out clumped together by type in 331 the AST. This is usually not an issue as connections can reference 332 instances and groups, but instances and groups cannot reference 333 anything else. This test ensures that we get the same results as the 334 previous test case with the group reordered. 335 ''' 336 ast, _ = self.parser.parse_string(''' 337 connector Conn { 338 from Procedure; 339 to Procedure; 340 } 341 procedure P {} 342 component A { 343 provides P a; 344 uses P b; 345 } 346 composition { 347 group g { 348 component A a2; 349 } 350 component A a1; 351 connection Conn c(from a1.b, to g.a2.a); 352 connection Conn c2(from g.a2.b, to a1.a); 353 } 354 ''') 355 356 self.assertLen(ast.items, 4) 357 _, _, A, comp = ast.items 358 359 self.assertIsInstance(A, Component) 360 self.assertLen(A.provides, 1) 361 a = A.provides[0] 362 self.assertIsInstance(a, Provides) 363 self.assertLen(A.uses, 1) 364 b = A.uses[0] 365 self.assertIsInstance(b, Uses) 366 367 self.assertIsInstance(comp, Composition) 368 self.assertLen(comp.groups, 1) 369 g = comp.groups[0] 370 self.assertIsInstance(g, Group) 371 372 self.assertLen(g.instances, 1) 373 a2 = g.instances[0] 374 self.assertIsInstance(a2, Instance) 375 376 self.assertLen(comp.instances, 1) 377 a1 = comp.instances[0] 378 self.assertIsInstance(a1, Instance) 379 380 self.assertLen(comp.connections, 2) 381 c, c2 = comp.connections 382 self.assertIsInstance(c, Connection) 383 self.assertIsInstance(c2, Connection) 384 385 self.assertLen(c.from_ends, 1) 386 a1_b = c.from_ends[0] 387 self.assertIsInstance(a1_b, ConnectionEnd) 388 389 self.assertIs(a1_b.instance, a1) 390 self.assertIs(a1_b.interface, b) 391 392 self.assertLen(c.to_ends, 1) 393 g_a2_a = c.to_ends[0] 394 self.assertIsInstance(g_a2_a, ConnectionEnd) 395 396 self.assertIs(g_a2_a.instance, a2) 397 self.assertIs(g_a2_a.interface, a) 398 399 self.assertLen(c2.from_ends, 1) 400 g_a2_b = c2.from_ends[0] 401 self.assertIsInstance(g_a2_b, ConnectionEnd) 402 403 self.assertIs(g_a2_b.instance, a2) 404 self.assertIs(g_a2_b.interface, b) 405 406 self.assertLen(c2.to_ends, 1) 407 a1_a = c2.to_ends[0] 408 self.assertIsInstance(a1_a, ConnectionEnd) 409 410 self.assertIs(a1_a.instance, a1) 411 self.assertIs(a1_a.interface, a) 412 413 def test_forward_references(self): 414 spec = ''' 415 connector Foo { 416 from Procedure; 417 to Procedure; 418 } 419 component Client { 420 uses P p; /* <- forward reference */ 421 } 422 procedure P { 423 void bar(); 424 } 425 component Server { 426 provides P p; 427 } 428 assembly { 429 composition { 430 component Client c; 431 component Server s; 432 connection Foo f(from c.p, to s.p); 433 } 434 } 435 ''' 436 437 # This specification should be rejected by the default parser because 438 # forward references are not supported. 439 with self.assertRaises(ParseError): 440 self.parser.parse_string(spec) 441 442 # This specification should be accepted by the parser that supports 443 # forward references. 444 content, _ = self.forward_parser.parse_string(spec) 445 446 self.assertLen(content.items, 5) 447 Foo, Client, P, Server, assembly = content.items 448 449 self.assertIsInstance(Foo, Connector) 450 self.assertIsInstance(Client, Component) 451 self.assertIsInstance(P, Procedure) 452 self.assertIsInstance(Server, Component) 453 self.assertIsInstance(assembly, Assembly) 454 455 self.assertLen(Client.uses, 1) 456 self.assertIs(Client.uses[0].type, P) 457 458 def test_cycles(self): 459 ''' 460 Test that we can correctly detect and prevent cycles in the AST. 461 ''' 462 spec = ''' 463 component Client { 464 composition { 465 component Client c; 466 } 467 } 468 ''' 469 with six.assertRaisesRegex(self, ParseError, 470 r'AST cycle involving entity Client'): 471 self.forward_parser.parse_string(spec) 472 473 def test_cross_assembly_reference(self): 474 ''' 475 Test that we can make references from one assembly to another. 476 ''' 477 spec = ''' 478 connector Conn { 479 from Procedure; 480 to Procedure; 481 } 482 procedure P { 483 void foo(); 484 } 485 component Client { 486 uses P p; 487 } 488 component Server { 489 provides P p; 490 } 491 assembly { 492 composition { 493 component Client c; 494 component Server s; 495 } 496 } 497 assembly { 498 composition { 499 connection Conn conn(from c.p, to s.p); 500 } 501 }''' 502 content, _ = self.parser.parse_string(spec) 503 504 self.assertLen(content.items, 6) 505 Conn, P, Client, Server, a1, a2 = content.items 506 507 self.assertLen(a1.composition.children, 2) 508 c, s = a1.composition.children 509 self.assertIsInstance(c, Instance) 510 self.assertLen(c.type.uses, 1) 511 self.assertIsInstance(s, Instance) 512 self.assertLen(s.type.provides, 1) 513 514 self.assertLen(a2.composition.children, 1) 515 conn = a2.composition.children[0] 516 self.assertIsInstance(conn, Connection) 517 518 self.assertIs(conn.type, Conn) 519 520 self.assertLen(conn.from_ends, 1) 521 self.assertIs(conn.from_end.instance, c) 522 self.assertIs(conn.from_end.interface, c.type.uses[0]) 523 524 self.assertLen(conn.to_ends, 1) 525 self.assertIs(conn.to_end.instance, s) 526 self.assertIs(conn.to_end.interface, s.type.provides[0]) 527 528 def test_cross_assembly_forward_reference(self): 529 ''' 530 Test that we can make a reference from one assembly to a future one 531 when forward references are enabled. 532 ''' 533 spec = ''' 534 connector Conn { 535 from Procedure; 536 to Procedure; 537 } 538 procedure P { 539 void foo(); 540 } 541 component Client { 542 uses P p; 543 } 544 component Server { 545 provides P p; 546 } 547 assembly { 548 composition { 549 connection Conn conn(from c.p, to s.p); 550 } 551 } 552 assembly { 553 composition { 554 component Client c; 555 component Server s; 556 } 557 }''' 558 content, _ = self.forward_parser.parse_string(spec) 559 560 self.assertLen(content.items, 6) 561 Conn, P, Client, Server, a2, a1 = content.items 562 563 self.assertLen(a1.composition.children, 2) 564 c, s = a1.composition.children 565 self.assertIsInstance(c, Instance) 566 self.assertLen(c.type.uses, 1) 567 self.assertIsInstance(s, Instance) 568 self.assertLen(s.type.provides, 1) 569 570 self.assertLen(a2.composition.children, 1) 571 conn = a2.composition.children[0] 572 self.assertIsInstance(conn, Connection) 573 574 self.assertIs(conn.type, Conn) 575 576 self.assertLen(conn.from_ends, 1) 577 self.assertIs(conn.from_end.instance, c) 578 self.assertIs(conn.from_end.interface, c.type.uses[0]) 579 580 self.assertLen(conn.to_ends, 1) 581 self.assertIs(conn.to_end.instance, s) 582 self.assertIs(conn.to_end.interface, s.type.provides[0]) 583 584 def test_duplicate_parameter_names(self): 585 ''' 586 Previously, with forward resolution enabled, a ParseError would be 587 incorrectly raised when you re-used a method argument name. Method 588 names should essentially be meaningless to the parser and should not 589 escape the method in which they are contained. This test validates that 590 this bug has not been re-introduced. 591 ''' 592 spec = ''' 593 procedure P { 594 void foo(in int x); 595 void bar(in int x); 596 } 597 ''' 598 599 self.parser.parse_string(spec) 600 601 self.forward_parser.parse_string(spec) 602 603if __name__ == '__main__': 604 unittest.main() 605