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