1#!/usr/bin/env python 2 3import os 4import re 5import subprocess 6import sys 7import tempfile 8import textwrap 9import unittest 10 11from future.utils import iteritems 12 13 14class ConfgenBaseTestCase(unittest.TestCase): 15 @classmethod 16 def setUpClass(self): 17 self.args = dict() 18 self.functions = {'in': self.assertIn, 19 'not in': self.assertNotIn, 20 'equal': self.assertEqual, 21 'not equal': self.assertNotEqual} 22 23 try: 24 regex_func = self.assertRegex 25 except AttributeError: 26 # Python 2 fallback 27 regex_func = self.assertRegexpMatches 28 finally: 29 self.functions['regex'] = lambda instance, s, expr: regex_func(instance, expr, s) # reverse args order 30 31 def setUp(self): 32 with tempfile.NamedTemporaryFile(prefix='test_confgen_', delete=False) as f: 33 self.output_file = f.name 34 self.addCleanup(os.remove, self.output_file) 35 36 def invoke_confgen(self, args): 37 call_args = [sys.executable, '../../confgen.py'] 38 39 for (k, v) in iteritems(args): 40 if k != 'output': 41 if isinstance(v, type('')): # easy Python 2/3 compatible str/unicode 42 call_args += ['--{}'.format(k), v] 43 else: 44 for i in v: 45 call_args += ['--{}'.format(k), i] 46 call_args += ['--output', args['output'], self.output_file] # these arguments belong together 47 48 subprocess.check_call(call_args) 49 50 def invoke_and_test(self, in_text, out_text, test='in'): 51 """ 52 Main utility function for testing confgen: 53 54 - Runs confgen via invoke_confgen(), using output method pre-set in test class setup 55 - in_text is the Kconfig file input content 56 - out_text is some expected output from confgen 57 - 'test' can be any function key from self.functions dict (see above). Default is 'in' to test if 58 out_text is a substring of the full confgen output. 59 """ 60 61 with tempfile.NamedTemporaryFile(mode='w+', prefix='test_confgen_', delete=False) as f: 62 self.addCleanup(os.remove, f.name) 63 f.write(textwrap.dedent(in_text)) 64 65 self.args['kconfig'] = f.name 66 67 self.invoke_confgen(self.args) 68 69 with open(self.output_file) as f_result: 70 result = f_result.read() 71 72 try: 73 out_text = textwrap.dedent(out_text) 74 except TypeError: 75 pass # probably a regex 76 77 self.functions[test](self, out_text, result) 78 79 80class CmakeTestCase(ConfgenBaseTestCase): 81 @classmethod 82 def setUpClass(self): 83 super(CmakeTestCase, self).setUpClass() 84 self.args.update({'output': 'cmake'}) 85 86 def testStringEscape(self): 87 self.invoke_and_test(""" 88 config PASSWORD 89 string "password" 90 default "\\\\~!@#$%^&*()\\\"" 91 """, 'set(CONFIG_PASSWORD "\\\\~!@#$%^&*()\\\"")') 92 93 def testHexPrefix(self): 94 self.invoke_and_test(HEXPREFIX_KCONFIG, 'set(CONFIG_HEX_NOPREFIX "0x33")') 95 self.invoke_and_test(HEXPREFIX_KCONFIG, 'set(CONFIG_HEX_PREFIX "0x77")') 96 97 98class JsonTestCase(ConfgenBaseTestCase): 99 @classmethod 100 def setUpClass(self): 101 super(JsonTestCase, self).setUpClass() 102 self.args.update({'output': 'json'}) 103 104 def testStringEscape(self): 105 self.invoke_and_test(""" 106 config PASSWORD 107 string "password" 108 default "\\\\~!@#$%^&*()\\\"" 109 """, '"PASSWORD": "\\\\~!@#$%^&*()\\\""') 110 111 def testHexPrefix(self): 112 # hex values come out as integers in JSON, due to no hex type 113 self.invoke_and_test(HEXPREFIX_KCONFIG, '"HEX_NOPREFIX": %d' % 0x33) 114 self.invoke_and_test(HEXPREFIX_KCONFIG, '"HEX_PREFIX": %d' % 0x77) 115 116 117class JsonMenuTestCase(ConfgenBaseTestCase): 118 @classmethod 119 def setUpClass(self): 120 super(JsonMenuTestCase, self).setUpClass() 121 self.args.update({'output': 'json_menus'}) 122 123 def testMultipleRanges(self): 124 self.invoke_and_test(""" 125 config IDF_TARGET 126 string "IDF target" 127 default "esp32" 128 129 config SOME_SETTING 130 int "setting for the chip" 131 range 0 100 if IDF_TARGET="esp32s0" 132 range 0 10 if IDF_TARGET="esp32" 133 range -10 1 if IDF_TARGET="esp32s2" 134 """, re.compile(r'"range":\s+\[\s+0,\s+10\s+\]'), 'regex') 135 136 def testHexRanges(self): 137 self.invoke_and_test(""" 138 config SOME_SETTING 139 hex "setting for the chip" 140 range 0x0 0xaf if UNDEFINED 141 range 0x10 0xaf 142 """, r'"range":\s+\[\s+16,\s+175\s+\]', 'regex') 143 144 145class ConfigTestCase(ConfgenBaseTestCase): 146 @classmethod 147 def setUpClass(self): 148 super(ConfigTestCase, self).setUpClass() 149 self.args.update({'output': 'config'}) 150 self.input = """ 151 config TEST 152 bool "test" 153 default "n" 154 """ 155 156 def setUp(self): 157 super(ConfigTestCase, self).setUp() 158 with tempfile.NamedTemporaryFile(mode='w+', prefix='test_confgen_', delete=False) as f: 159 self.addCleanup(os.remove, f.name) 160 self.args.update({'config': f.name}) # this is input in contrast with {'output': 'config'} 161 f.write(textwrap.dedent(""" 162 CONFIG_TEST=y 163 CONFIG_UNKNOWN=y 164 """)) 165 166 def testKeepSavedOption(self): 167 self.invoke_and_test(self.input, 'CONFIG_TEST=y') 168 169 def testDiscardUnknownOption(self): 170 self.invoke_and_test(self.input, 'CONFIG_UNKNOWN', 'not in') 171 172 173class MakefileTestCase(ConfgenBaseTestCase): 174 @classmethod 175 def setUpClass(self): 176 super(MakefileTestCase, self).setUpClass() 177 self.args.update({'output': 'makefile'}) 178 179 def setUp(self): 180 super(MakefileTestCase, self).setUp() 181 with tempfile.NamedTemporaryFile(mode='w+', prefix='test_confgen_', delete=False) as f1: 182 self.addCleanup(os.remove, f1.name) 183 with tempfile.NamedTemporaryFile(mode='w+', prefix='test_confgen_', delete=False) as f2: 184 self.addCleanup(os.remove, f2.name) 185 self.args.update({'env': ['COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE={}'.format(f1.name), 186 'COMPONENT_KCONFIGS_SOURCE_FILE={}'.format(f2.name), 187 'IDF_TARGET=esp32']}) 188 189 def testTarget(self): 190 with open(os.path.join(os.environ['IDF_PATH'], 'Kconfig')) as f: 191 self.invoke_and_test(f.read(), 'CONFIG_IDF_TARGET="esp32"') 192 193 def testHexPrefix(self): 194 self.invoke_and_test(HEXPREFIX_KCONFIG, 'CONFIG_HEX_NOPREFIX=0x33') 195 self.invoke_and_test(HEXPREFIX_KCONFIG, 'CONFIG_HEX_PREFIX=0x77') 196 197 198class HeaderTestCase(ConfgenBaseTestCase): 199 @classmethod 200 def setUpClass(self): 201 super(HeaderTestCase, self).setUpClass() 202 self.args.update({'output': 'header'}) 203 204 def testStringEscape(self): 205 self.invoke_and_test(""" 206 config PASSWORD 207 string "password" 208 default "\\\\~!@#$%^&*()\\\"" 209 """, '#define CONFIG_PASSWORD "\\\\~!@#$%^&*()\\\""') 210 211 def testHexPrefix(self): 212 self.invoke_and_test(HEXPREFIX_KCONFIG, '#define CONFIG_HEX_NOPREFIX 0x33') 213 self.invoke_and_test(HEXPREFIX_KCONFIG, '#define CONFIG_HEX_PREFIX 0x77') 214 215 216class DocsTestCase(ConfgenBaseTestCase): 217 @classmethod 218 def setUpClass(self): 219 super(DocsTestCase, self).setUpClass() 220 self.args.update({'output': 'docs', 221 'env': 'IDF_TARGET=esp32'}) 222 223 def testChoice(self): 224 self.invoke_and_test(""" 225 menu "TEST" 226 choice TYPES 227 prompt "types" 228 default TYPES_OP2 229 help 230 Description of TYPES 231 232 config TYPES_OP1 233 bool "option 1" 234 config TYPES_OP2 235 bool "option 2" 236 endchoice 237 endmenu 238 """, """ 239 TEST 240 ---- 241 242 Contains: 243 244 - :ref:`CONFIG_TYPES` 245 246 .. _CONFIG_TYPES: 247 248 CONFIG_TYPES 249 ^^^^^^^^^^^^ 250 251 types 252 253 :emphasis:`Found in:` :ref:`test` 254 255 Description of TYPES 256 257 Available options: 258 - option 1 (TYPES_OP1) 259 - option 2 (TYPES_OP2) 260 """) # this is more readable than regex 261 262 263# Used by multiple testHexPrefix() test cases to verify correct hex output for each format 264HEXPREFIX_KCONFIG = """ 265config HEX_NOPREFIX 266hex "Hex Item default no prefix" 267default 33 268 269config HEX_PREFIX 270hex "Hex Item default prefix" 271default 0x77 272""" 273 274if __name__ == '__main__': 275 unittest.main() 276