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 16''' 17This file contains unit test cases related to the template macros. 18''' 19 20from __future__ import absolute_import, division, print_function, \ 21 unicode_literals 22 23import ast, fnmatch, os, re, subprocess, sys, unittest 24 25ME = os.path.abspath(__file__) 26MY_DIR = os.path.dirname(ME) 27 28# Make CapDL importable. Note that we just assume where it is in relation to 29# our own directory. 30sys.path.append(os.path.join(MY_DIR, '../../../../python-capdl')) 31 32# Make CAmkES importable 33sys.path.append(os.path.join(MY_DIR, '../../..')) 34 35from camkes.internal.tests.utils import CAmkESTest, which 36from camkes.templates.macros import NO_CHECK_UNUSED, get_perm 37from camkes.templates import TemplateError 38 39def uname(): 40 ''' 41 Determine the hardware architecture of this machine. Note that we're only 42 really interested in x86 or x86_64. 43 ''' 44 try: 45 machine = subprocess.check_output(['uname', '-m']).strip() 46 except subprocess.CalledProcessError: 47 return None 48 if re.match(r'i\d86$', machine): 49 return 'x86' 50 return machine 51 52class TestMacros(CAmkESTest): 53 54 def test_get_perm(self): 55 conf = {} 56 instance = "bah" 57 iface = "humbug" 58 field = '%s_access' % iface 59 conf[instance] = {} 60 61 self.assertEqual(get_perm(conf, instance, iface), "RWXP") 62 conf[instance][field] = "R" 63 self.assertEqual(get_perm(conf, instance, iface), "R") 64 conf[instance][field] = "FOO" 65 with self.assertRaises(TemplateError): 66 get_perm(conf, instance, iface) 67 68 def test_find_unused_macros(self): 69 ''' 70 Find macros intended for the templates that are never actually used in 71 any template. 72 ''' 73 74 # First obtain a set of available macros by parsing the source of 75 # macros.py. 76 macrospy = os.path.join(MY_DIR, '../macros.py') 77 with open(macrospy) as f: 78 source = f.read() 79 80 node = ast.parse(source, filename=macrospy) 81 82 macros = set() 83 for i in ast.iter_child_nodes(node): 84 if isinstance(i, ast.FunctionDef): 85 macros.add(i.name) 86 87 # Next get a set of gitignored globs. 88 89 # First up, ignore the tests. 90 ignored = set(('%s/**' % MY_DIR,)) 91 92 # Now look at all .gitignores from three directories up. 93 for stem in ('../../..', '../..', '..'): 94 gitignore = os.path.join(MY_DIR, stem, '.gitignore') 95 if os.path.exists(gitignore): 96 with open(gitignore) as f: 97 for line in (x.strip() for x in f 98 if x.strip() != '' and not x.startswith('#')): 99 pattern = os.path.join(os.path.abspath( 100 os.path.dirname(gitignore)), line) 101 ignored.add(pattern) 102 103 # Now let's look at all the templates and note macro calls. 104 105 # A regex to match macro calls from the template context. Note that it is 106 # imprecise, so the resulting analysis could generate false negatives. 107 call = re.compile(r'/\*[-\?].*?\bmacros\.([a-zA-Z][a-zA-Z0-9_]*)\b') 108 109 used = set() 110 for root, _, files in os.walk(os.path.abspath( 111 os.path.join(MY_DIR, '..'))): 112 for f in (os.path.join(root, f) for f in files): 113 for pattern in ignored: 114 try: 115 if fnmatch.fnmatchcase(f, pattern): 116 break 117 except Exception: 118 # Suppress any errors resulting from invalid lines in 119 # .gitignore. 120 pass 121 else: 122 # This file did not match any of the ignore patterns; scan 123 # it for macro calls. 124 125 with open(f) as input: 126 source = input.read() 127 for m in call.finditer(source): 128 used.add(m.group(1)) 129 130 unused = macros - used - NO_CHECK_UNUSED 131 if len(unused) > 0: 132 [print("Unused macro: %s" % u) for u in unused] 133 self.assertSetEqual(unused, set()) 134 135if __name__ == '__main__': 136 unittest.main() 137