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, 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.internal.tests.utils import CAmkESTest, cpp_available
27from camkes.parser.stage0 import CPP, Reader
28from camkes.parser.stage1 import Parse1
29from camkes.parser.stage2 import Parse2
30
31class TestStage2(CAmkESTest):
32    def setUp(self):
33        super(TestStage2, self).setUp()
34        r = Reader()
35        s1 = Parse1(r)
36        self.parser = Parse2(s1)
37
38        r = CPP()
39        s1 = Parse1(r)
40        self.cpp_parser = Parse2(s1)
41
42    def test_empty_string(self):
43        content, read = self.parser.parse_string('')
44
45        self.assertEqual(content, [])
46        self.assertLen(read, 0)
47
48    def test_basic_entity(self):
49        content, read = self.parser.parse_string('component foo {}')
50
51        self.assertLen(content, 1)
52        self.assertEqual(content[0][0], 'component foo {}')
53        self.assertIsNone(content[0][1])
54        comp = content[0][2]
55
56        self.assertEqual(comp.head, 'component_decl')
57        self.assertLen(comp.tail, 2)
58        self.assertEqual(comp.tail[0].head, 'id')
59        self.assertLen(comp.tail[0].tail, 1)
60        self.assertEqual(comp.tail[0].tail[0], 'foo')
61        self.assertEqual(comp.tail[1].head, 'component_defn')
62        self.assertLen(comp.tail[1].tail, 0)
63        self.assertLen(read, 0)
64
65    def test_malformed(self):
66        with self.assertRaises(Exception):
67            self.parser.parse_string('hello world')
68
69    def test_unicode(self):
70        content, read = self.parser.parse_string('component fo�� {}')
71
72        self.assertLen(content, 1)
73        self.assertEqual(content[0][0], 'component fo�� {}')
74        self.assertIsNone(content[0][1])
75        comp = content[0][2]
76
77        self.assertEqual(comp.head, 'component_decl')
78        self.assertLen(comp.tail, 2)
79        self.assertEqual(comp.tail[0].head, 'id')
80        self.assertLen(comp.tail[0].tail, 1)
81        self.assertEqual(comp.tail[0].tail[0], 'fo��')
82        self.assertEqual(comp.tail[1].head, 'component_defn')
83        self.assertLen(comp.tail[1].tail, 0)
84        self.assertLen(read, 0)
85
86    def test_from_file(self):
87        tmp = self.mkstemp()
88        with open(tmp, 'wt') as f:
89            f.write('component foo {}')
90
91        content, read = self.parser.parse_file(tmp)
92
93        self.assertLen(content, 1)
94        self.assertEqual(content[0][0], 'component foo {}')
95        self.assertEqual(content[0][1], tmp)
96        comp = content[0][2]
97
98        self.assertEqual(comp.head, 'component_decl')
99        self.assertLen(comp.tail, 2)
100        self.assertEqual(comp.tail[0].head, 'id')
101        self.assertLen(comp.tail[0].tail, 1)
102        self.assertEqual(comp.tail[0].tail[0], 'foo')
103        self.assertEqual(comp.tail[1].head, 'component_defn')
104        self.assertLen(comp.tail[1].tail, 0)
105        self.assertEqual(read, set([tmp]))
106
107    @unittest.skipIf(not cpp_available(), 'CPP not found')
108    def test_with_cpp(self):
109        parent = self.mkstemp()
110        child = self.mkstemp()
111
112        with open(parent, 'wt') as f:
113            f.write('component foo\n#include "%s"' % child)
114        with open(child, 'wt') as f:
115            f.write('{}')
116
117        content, read = self.cpp_parser.parse_file(parent)
118
119        self.assertLen(content, 1)
120        self.assertEqual(content[0][1], parent)
121        comp = content[0][2]
122
123        self.assertEqual(comp.head, 'component_decl')
124        self.assertLen(comp.tail, 2)
125        self.assertEqual(comp.tail[0].head, 'id')
126        self.assertLen(comp.tail[0].tail, 1)
127        self.assertEqual(comp.tail[0].tail[0], 'foo')
128        self.assertEqual(comp.tail[1].head, 'component_defn')
129        self.assertLen(comp.tail[1].tail, 0)
130        self.assertIn(parent, read)
131        self.assertIn(child, read)
132
133    def test_simple_spec_complete(self):
134        content, _ = self.parser.parse_string('''
135            procedure Hello {
136                void hello(void);
137            }
138
139            component Foo {
140                provides Hello h;
141            }
142
143            component Bar {
144                control;
145                uses Hello w;
146            }
147
148            assembly {
149                composition {
150                    component Foo f;
151                    component Bar b;
152                    connection Conn conn(from Foo.h, to Bar.w);
153                }
154            }
155            ''')
156
157    def test_self_import(self):
158        '''
159        The stage 2 parser should notice cycles in the import graph and
160        automatically terminate. This case validates a trivial cycle.
161        '''
162
163        input = self.mkstemp()
164
165        with open(input, 'wt') as f:
166            f.write('''
167                component Foo {}
168                import "%s";
169                ''' % input)
170
171        content, read = self.parser.parse_file(input)
172
173        self.assertLen(content, 1)
174        Foo = content[0][2]
175        self.assertEqual(Foo.head, 'component_decl')
176
177        self.assertEqual(read, set([input]))
178
179        content, read = self.cpp_parser.parse_file(input)
180
181        self.assertLen(content, 1)
182        Foo = content[0][2]
183        self.assertEqual(Foo.head, 'component_decl')
184
185        self.assertIn(input, read)
186
187    def test_cycle_import(self):
188        '''
189        Similar to the previous test, but a cycle involving multiple files.
190        '''
191
192        a = self.mkstemp()
193        b = self.mkstemp()
194        c = self.mkstemp()
195
196        with open(a, 'wt') as f:
197            f.write('''
198                component Foo {}
199                import "%s";
200                ''' % b)
201        with open(b, 'wt') as f:
202            f.write('import "%s";' % c)
203        with open(c, 'wt') as f:
204            f.write('import "%s";' % a)
205
206        content, read = self.parser.parse_file(a)
207
208        self.assertLen(content, 1)
209        Foo = content[0][2]
210        self.assertEqual(Foo.head, 'component_decl')
211
212        self.assertEqual(read, set([a, b, c]))
213
214        content, read = self.cpp_parser.parse_file(a)
215
216        self.assertLen(content, 1)
217        Foo = content[0][2]
218        self.assertEqual(Foo.head, 'component_decl')
219
220        self.assertIn(a, read)
221        self.assertIn(b, read)
222        self.assertIn(c, read)
223
224if __name__ == '__main__':
225    unittest.main()
226