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