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 inspect, os, sys, unittest
20
21ME = os.path.abspath(__file__)
22
23# Make camkes.ast importable
24sys.path.append(os.path.join(os.path.dirname(ME), '../../..'))
25
26from camkes.ast.base import *
27from camkes.ast.liftedast import *
28from camkes.ast.objects import ASTObject
29from camkes.ast.objects import *
30from camkes.internal.tests.utils import CAmkESTest
31
32class TestObjects(CAmkESTest):
33    def test_child_fields(self):
34        '''
35        Test all the AST objects have valid `child_fields` class members.
36
37        We do this in a seemingly hacky way by dynamically discovering the AST
38        objects through the `globals` dictionary in order to ensure we notice
39        new AST object classes, even if developers forget to tell us about
40        them.
41
42        This test may seem to be checking something obvious, but it is very
43        easy to write `child_fields = ('hello')` and think you defined a tuple
44        when you really just defined a string.
45        '''
46        for c in globals().values():
47            if inspect.isclass(c) and issubclass(c, ASTObject):
48                self.assertTrue(hasattr(c, 'child_fields'),
49                    '%s has no child_fields class member' % c.__name__)
50                self.assertIsInstance(c.child_fields, tuple, '%s\'s '
51                    'child_field member is not a tuple as expected' %
52                    c.__name__)
53                seen = set()
54                for f in c.child_fields:
55                    self.assertNotIn(f, seen,
56                        'duplicate child field %s in %s' % (f, c.__name__))
57                    seen.add(f)
58
59    def test_no_hash(self):
60        '''
61        Test all AST objects have a well-formed `no_hash` class member.
62
63        `ASTObject`, and all its descendents, should have a `no_hash` class
64        member that defines fields that should not be considered when doing
65        object hashes or comparisons. Importantly, children should never
66        remove `no_hash` members that their parents' defined. This test ensures
67        that every child of `ASTObject` has all the `no_hash` objects of its
68        parents.
69        '''
70        for c in globals().values():
71            if inspect.isclass(c) and issubclass(c, ASTObject):
72                # For each parent class between `c` and `ASTObject` inclusively
73                for parent in [p for p in c.__bases__
74                        if issubclass(p, ASTObject)]:
75                    self.assertLessEqual(set(parent.no_hash), set(c.no_hash),
76                        '%s discards no_hash members from its parent %s' %
77                        (c.__name__, parent.__name__))
78
79    def test_freezing_monotonic(self):
80        '''
81        We should not be able to unfreeze (thaw) an AST object.
82        '''
83        p = Procedure()
84        p.freeze()
85        with self.assertRaises(TypeError):
86            p.frozen = False
87
88if __name__ == '__main__':
89    unittest.main()
90