9cd3458585321dba1f992cf10532218ef37d9d7d
[kconfig-hardened-check.git] / test_engine.py
1 #!/usr/bin/env python3
2
3 """
4 This tool is for checking the security hardening options of the Linux kernel.
5
6 Author: Alexander Popov <alex.popov@linux.com>
7
8 This module performs unit-testing of the kernel-hardening-checker engine.
9 """
10
11 # pylint: disable=missing-function-docstring,line-too-long
12
13 import unittest
14 import io
15 import sys
16 from collections import OrderedDict
17 import json
18 import inspect
19 from .engine import KconfigCheck, CmdlineCheck, SysctlCheck, VersionCheck, OR, AND, populate_with_data, perform_checks, override_expected_value
20
21
22 class TestEngine(unittest.TestCase):
23     """
24     Example test scenario:
25
26         # 1. prepare the checklist
27         config_checklist = []
28         config_checklist += [KconfigCheck('reason_1', 'decision_1', 'KCONFIG_NAME', 'expected_1')]
29         config_checklist += [CmdlineCheck('reason_2', 'decision_2', 'cmdline_name', 'expected_2')]
30         config_checklist += [SysctlCheck('reason_3', 'decision_3', 'sysctl_name', 'expected_3')]
31
32         # 2. prepare the parsed kconfig options
33         parsed_kconfig_options = OrderedDict()
34         parsed_kconfig_options['CONFIG_KCONFIG_NAME'] = 'UNexpected_1'
35
36         # 3. prepare the parsed cmdline options
37         parsed_cmdline_options = OrderedDict()
38         parsed_cmdline_options['cmdline_name'] = 'expected_2'
39
40         # 4. prepare the parsed sysctl options
41         parsed_sysctl_options = OrderedDict()
42         parsed_sysctl_options['sysctl_name'] = 'expected_3'
43
44         # 5. prepare the kernel version
45         kernel_version = (42, 43)
46
47         # 6. run the engine
48         self.run_engine(config_checklist, parsed_kconfig_options, parsed_cmdline_options, parsed_sysctl_options, kernel_version)
49
50         # 7. check that the results are correct
51         result = []
52         self.get_engine_result(config_checklist, result, 'json')
53         self.assertEqual(...
54     """
55
56     maxDiff = None
57
58     @staticmethod
59     def run_engine(checklist, parsed_kconfig_options, parsed_cmdline_options, parsed_sysctl_options, kernel_version):
60         # populate the checklist with data
61         if parsed_kconfig_options:
62             populate_with_data(checklist, parsed_kconfig_options, 'kconfig')
63         if parsed_cmdline_options:
64             populate_with_data(checklist, parsed_cmdline_options, 'cmdline')
65         if parsed_sysctl_options:
66             populate_with_data(checklist, parsed_sysctl_options, 'sysctl')
67         if kernel_version:
68             populate_with_data(checklist, kernel_version, 'version')
69
70         # now everything is ready, perform the checks
71         perform_checks(checklist)
72
73         # print the table with the results
74         print(f'\n{inspect.stack()[1].function}():')
75         print('=' * 121)
76         for opt in checklist:
77             opt.table_print('verbose', True) # verbose mode, with_results
78             print()
79             print('=' * 121)
80
81         # print the results in JSON
82         result = []
83         for opt in checklist:
84             result.append(opt.json_dump(True)) # with_results
85         print(json.dumps(result))
86         print()
87
88     @staticmethod
89     def get_engine_result(checklist, result, result_type):
90         assert(result_type in ('json', 'stdout', 'stdout_verbose')), \
91                f'invalid result type "{result_type}"'
92
93         if result_type == 'json':
94             for opt in checklist:
95                 result.append(opt.json_dump(True)) # with_results
96             return
97
98         captured_output = io.StringIO()
99         stdout_backup = sys.stdout
100         sys.stdout = captured_output
101         for opt in checklist:
102             if result_type == 'stdout_verbose':
103                 opt.table_print('verbose', True) # verbose mode, with_results
104             else:
105                 opt.table_print(None, True) # normal mode, with_results
106         sys.stdout = stdout_backup
107         result.append(captured_output.getvalue())
108
109     def test_simple_kconfig(self):
110         # 1. prepare the checklist
111         config_checklist = []
112         config_checklist += [KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1')]
113         config_checklist += [KconfigCheck('reason_2', 'decision_2', 'NAME_2', 'expected_2')]
114         config_checklist += [KconfigCheck('reason_3', 'decision_3', 'NAME_3', 'expected_3')]
115         config_checklist += [KconfigCheck('reason_4', 'decision_4', 'NAME_4', 'is not set')]
116         config_checklist += [KconfigCheck('reason_5', 'decision_5', 'NAME_5', 'is present')]
117         config_checklist += [KconfigCheck('reason_6', 'decision_6', 'NAME_6', 'is present')]
118         config_checklist += [KconfigCheck('reason_7', 'decision_7', 'NAME_7', 'is not off')]
119         config_checklist += [KconfigCheck('reason_8', 'decision_8', 'NAME_8', 'is not off')]
120         config_checklist += [KconfigCheck('reason_9', 'decision_9', 'NAME_9', 'is not off')]
121         config_checklist += [KconfigCheck('reason_10', 'decision_10', 'NAME_10', 'is not off')]
122
123         # 2. prepare the parsed kconfig options
124         parsed_kconfig_options = OrderedDict()
125         parsed_kconfig_options['CONFIG_NAME_1'] = 'expected_1'
126         parsed_kconfig_options['CONFIG_NAME_2'] = 'UNexpected_2'
127         parsed_kconfig_options['CONFIG_NAME_5'] = 'UNexpected_5'
128         parsed_kconfig_options['CONFIG_NAME_7'] = 'really_not_off'
129         parsed_kconfig_options['CONFIG_NAME_8'] = 'off'
130         parsed_kconfig_options['CONFIG_NAME_9'] = '0'
131
132         # 3. run the engine
133         self.run_engine(config_checklist, parsed_kconfig_options, None, None, None)
134
135         # 4. check that the results are correct
136         result = []
137         self.get_engine_result(config_checklist, result, 'json')
138         self.assertEqual(
139                 result,
140                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
141                  {'option_name': 'CONFIG_NAME_2', 'type': 'kconfig', 'desired_val': 'expected_2', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'FAIL: "UNexpected_2"', 'check_result': False},
142                  {'option_name': 'CONFIG_NAME_3', 'type': 'kconfig', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: is not found', 'check_result': False},
143                  {'option_name': 'CONFIG_NAME_4', 'type': 'kconfig', 'desired_val': 'is not set', 'decision': 'decision_4', 'reason': 'reason_4', 'check_result_text': 'OK: is not found', 'check_result': True},
144                  {'option_name': 'CONFIG_NAME_5', 'type': 'kconfig', 'desired_val': 'is present', 'decision': 'decision_5', 'reason': 'reason_5', 'check_result_text': 'OK: is present', 'check_result': True},
145                  {'option_name': 'CONFIG_NAME_6', 'type': 'kconfig', 'desired_val': 'is present', 'decision': 'decision_6', 'reason': 'reason_6', 'check_result_text': 'FAIL: is not present', 'check_result': False},
146                  {'option_name': 'CONFIG_NAME_7', 'type': 'kconfig', 'desired_val': 'is not off', 'decision': 'decision_7', 'reason': 'reason_7', 'check_result_text': 'OK: is not off, "really_not_off"', 'check_result': True},
147                  {'option_name': 'CONFIG_NAME_8', 'type': 'kconfig', 'desired_val': 'is not off', 'decision': 'decision_8', 'reason': 'reason_8', 'check_result_text': 'FAIL: is off', 'check_result': False},
148                  {'option_name': 'CONFIG_NAME_9', 'type': 'kconfig', 'desired_val': 'is not off', 'decision': 'decision_9', 'reason': 'reason_9', 'check_result_text': 'FAIL: is off, "0"', 'check_result': False},
149                  {'option_name': 'CONFIG_NAME_10', 'type': 'kconfig', 'desired_val': 'is not off', 'decision': 'decision_10', 'reason': 'reason_10', 'check_result_text': 'FAIL: is off, not found', 'check_result': False}]
150         )
151
152     def test_simple_cmdline(self):
153         # 1. prepare the checklist
154         config_checklist = []
155         config_checklist += [CmdlineCheck('reason_1', 'decision_1', 'name_1', 'expected_1')]
156         config_checklist += [CmdlineCheck('reason_2', 'decision_2', 'name_2', 'expected_2')]
157         config_checklist += [CmdlineCheck('reason_3', 'decision_3', 'name_3', 'expected_3')]
158         config_checklist += [CmdlineCheck('reason_4', 'decision_4', 'name_4', 'is not set')]
159         config_checklist += [CmdlineCheck('reason_5', 'decision_5', 'name_5', 'is present')]
160         config_checklist += [CmdlineCheck('reason_6', 'decision_6', 'name_6', 'is present')]
161         config_checklist += [CmdlineCheck('reason_7', 'decision_7', 'name_7', 'is not off')]
162         config_checklist += [CmdlineCheck('reason_8', 'decision_8', 'name_8', 'is not off')]
163         config_checklist += [CmdlineCheck('reason_9', 'decision_9', 'name_9', 'is not off')]
164         config_checklist += [CmdlineCheck('reason_10', 'decision_10', 'name_10', 'is not off')]
165
166         # 2. prepare the parsed cmdline options
167         parsed_cmdline_options = OrderedDict()
168         parsed_cmdline_options['name_1'] = 'expected_1'
169         parsed_cmdline_options['name_2'] = 'UNexpected_2'
170         parsed_cmdline_options['name_5'] = ''
171         parsed_cmdline_options['name_7'] = ''
172         parsed_cmdline_options['name_8'] = 'off'
173         parsed_cmdline_options['name_9'] = '0'
174
175         # 3. run the engine
176         self.run_engine(config_checklist, None, parsed_cmdline_options, None, None)
177
178         # 4. check that the results are correct
179         result = []
180         self.get_engine_result(config_checklist, result, 'json')
181         self.assertEqual(
182                 result,
183                 [{'option_name': 'name_1', 'type': 'cmdline', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
184                  {'option_name': 'name_2', 'type': 'cmdline', 'desired_val': 'expected_2', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'FAIL: "UNexpected_2"', 'check_result': False},
185                  {'option_name': 'name_3', 'type': 'cmdline', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: is not found', 'check_result': False},
186                  {'option_name': 'name_4', 'type': 'cmdline', 'desired_val': 'is not set', 'decision': 'decision_4', 'reason': 'reason_4', 'check_result_text': 'OK: is not found', 'check_result': True},
187                  {'option_name': 'name_5', 'type': 'cmdline', 'desired_val': 'is present', 'decision': 'decision_5', 'reason': 'reason_5', 'check_result_text': 'OK: is present', 'check_result': True},
188                  {'option_name': 'name_6', 'type': 'cmdline', 'desired_val': 'is present', 'decision': 'decision_6', 'reason': 'reason_6', 'check_result_text': 'FAIL: is not present', 'check_result': False},
189                  {'option_name': 'name_7', 'type': 'cmdline', 'desired_val': 'is not off', 'decision': 'decision_7', 'reason': 'reason_7', 'check_result_text': 'OK: is not off, ""', 'check_result': True},
190                  {'option_name': 'name_8', 'type': 'cmdline', 'desired_val': 'is not off', 'decision': 'decision_8', 'reason': 'reason_8', 'check_result_text': 'FAIL: is off', 'check_result': False},
191                  {'option_name': 'name_9', 'type': 'cmdline', 'desired_val': 'is not off', 'decision': 'decision_9', 'reason': 'reason_9', 'check_result_text': 'FAIL: is off, "0"', 'check_result': False},
192                  {'option_name': 'name_10', 'type': 'cmdline', 'desired_val': 'is not off', 'decision': 'decision_10', 'reason': 'reason_10', 'check_result_text': 'FAIL: is off, not found', 'check_result': False}]
193         )
194
195     def test_simple_sysctl(self):
196         # 1. prepare the checklist
197         config_checklist = []
198         config_checklist += [SysctlCheck('reason_1', 'decision_1', 'name_1', 'expected_1')]
199         config_checklist += [SysctlCheck('reason_2', 'decision_2', 'name_2', 'expected_2')]
200         config_checklist += [SysctlCheck('reason_3', 'decision_3', 'name_3', 'expected_3')]
201         config_checklist += [SysctlCheck('reason_4', 'decision_4', 'name_4', 'is not set')]
202         config_checklist += [SysctlCheck('reason_5', 'decision_5', 'name_5', 'is present')]
203         config_checklist += [SysctlCheck('reason_6', 'decision_6', 'name_6', 'is present')]
204         config_checklist += [SysctlCheck('reason_7', 'decision_7', 'name_7', 'is not off')]
205         config_checklist += [SysctlCheck('reason_8', 'decision_8', 'name_8', 'is not off')]
206         config_checklist += [SysctlCheck('reason_9', 'decision_9', 'name_9', 'is not off')]
207         config_checklist += [SysctlCheck('reason_10', 'decision_10', 'name_10', 'is not off')]
208
209         # 2. prepare the parsed sysctl options
210         parsed_sysctl_options = OrderedDict()
211         parsed_sysctl_options['name_1'] = 'expected_1'
212         parsed_sysctl_options['name_2'] = 'UNexpected_2'
213         parsed_sysctl_options['name_5'] = ''
214         parsed_sysctl_options['name_7'] = ''
215         parsed_sysctl_options['name_8'] = 'off'
216         parsed_sysctl_options['name_9'] = '0'
217
218         # 3. run the engine
219         self.run_engine(config_checklist, None, None, parsed_sysctl_options, None)
220
221         # 4. check that the results are correct
222         result = []
223         self.get_engine_result(config_checklist, result, 'json')
224         self.assertEqual(
225                 result,
226                 [{'option_name': 'name_1', 'type': 'sysctl', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
227                  {'option_name': 'name_2', 'type': 'sysctl', 'desired_val': 'expected_2', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'FAIL: "UNexpected_2"', 'check_result': False},
228                  {'option_name': 'name_3', 'type': 'sysctl', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: is not found', 'check_result': False},
229                  {'option_name': 'name_4', 'type': 'sysctl', 'desired_val': 'is not set', 'decision': 'decision_4', 'reason': 'reason_4', 'check_result_text': 'OK: is not found', 'check_result': True},
230                  {'option_name': 'name_5', 'type': 'sysctl', 'desired_val': 'is present', 'decision': 'decision_5', 'reason': 'reason_5', 'check_result_text': 'OK: is present', 'check_result': True},
231                  {'option_name': 'name_6', 'type': 'sysctl', 'desired_val': 'is present', 'decision': 'decision_6', 'reason': 'reason_6', 'check_result_text': 'FAIL: is not present', 'check_result': False},
232                  {'option_name': 'name_7', 'type': 'sysctl', 'desired_val': 'is not off', 'decision': 'decision_7', 'reason': 'reason_7', 'check_result_text': 'OK: is not off, ""', 'check_result': True},
233                  {'option_name': 'name_8', 'type': 'sysctl', 'desired_val': 'is not off', 'decision': 'decision_8', 'reason': 'reason_8', 'check_result_text': 'FAIL: is off', 'check_result': False},
234                  {'option_name': 'name_9', 'type': 'sysctl', 'desired_val': 'is not off', 'decision': 'decision_9', 'reason': 'reason_9', 'check_result_text': 'FAIL: is off, "0"', 'check_result': False},
235                  {'option_name': 'name_10', 'type': 'sysctl', 'desired_val': 'is not off', 'decision': 'decision_10', 'reason': 'reason_10', 'check_result_text': 'FAIL: is off, not found', 'check_result': False}]
236         )
237
238     def test_complex_or(self):
239         # 1. prepare the checklist
240         config_checklist = []
241         config_checklist += [OR(KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1'),
242                                 KconfigCheck('reason_2', 'decision_2', 'NAME_2', 'expected_2'))]
243         config_checklist += [OR(KconfigCheck('reason_3', 'decision_3', 'NAME_3', 'expected_3'),
244                                 KconfigCheck('reason_4', 'decision_4', 'NAME_4', 'expected_4'))]
245         config_checklist += [OR(KconfigCheck('reason_5', 'decision_5', 'NAME_5', 'expected_5'),
246                                 KconfigCheck('reason_6', 'decision_6', 'NAME_6', 'expected_6'))]
247         config_checklist += [OR(KconfigCheck('reason_7', 'decision_7', 'NAME_7', 'expected_7'),
248                                 KconfigCheck('reason_8', 'decision_8', 'NAME_8', 'is not set'))]
249         config_checklist += [OR(KconfigCheck('reason_9', 'decision_9', 'NAME_9', 'expected_9'),
250                                 KconfigCheck('reason_10', 'decision_10', 'NAME_10', 'is present'))]
251         config_checklist += [OR(KconfigCheck('reason_11', 'decision_11', 'NAME_11', 'expected_11'),
252                                 KconfigCheck('reason_12', 'decision_12', 'NAME_12', 'is not off'))]
253
254         # 2. prepare the parsed kconfig options
255         parsed_kconfig_options = OrderedDict()
256         parsed_kconfig_options['CONFIG_NAME_1'] = 'expected_1'
257         parsed_kconfig_options['CONFIG_NAME_2'] = 'UNexpected_2'
258         parsed_kconfig_options['CONFIG_NAME_3'] = 'UNexpected_3'
259         parsed_kconfig_options['CONFIG_NAME_4'] = 'expected_4'
260         parsed_kconfig_options['CONFIG_NAME_5'] = 'UNexpected_5'
261         parsed_kconfig_options['CONFIG_NAME_6'] = 'UNexpected_6'
262         parsed_kconfig_options['CONFIG_NAME_10'] = 'UNexpected_10'
263         parsed_kconfig_options['CONFIG_NAME_12'] = 'really_not_off'
264
265         # 3. run the engine
266         self.run_engine(config_checklist, parsed_kconfig_options, None, None, None)
267
268         # 4. check that the results are correct
269         result = []
270         self.get_engine_result(config_checklist, result, 'json')
271         self.assertEqual(
272                 result,
273                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
274                  {'option_name': 'CONFIG_NAME_3', 'type': 'kconfig', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'OK: CONFIG_NAME_4 is "expected_4"', 'check_result': True},
275                  {'option_name': 'CONFIG_NAME_5', 'type': 'kconfig', 'desired_val': 'expected_5', 'decision': 'decision_5', 'reason': 'reason_5', 'check_result_text': 'FAIL: "UNexpected_5"', 'check_result': False},
276                  {'option_name': 'CONFIG_NAME_7', 'type': 'kconfig', 'desired_val': 'expected_7', 'decision': 'decision_7', 'reason': 'reason_7', 'check_result_text': 'OK: CONFIG_NAME_8 is not found', 'check_result': True},
277                  {'option_name': 'CONFIG_NAME_9', 'type': 'kconfig', 'desired_val': 'expected_9', 'decision': 'decision_9', 'reason': 'reason_9', 'check_result_text': 'OK: CONFIG_NAME_10 is present', 'check_result': True},
278                  {'option_name': 'CONFIG_NAME_11', 'type': 'kconfig', 'desired_val': 'expected_11', 'decision': 'decision_11', 'reason': 'reason_11', 'check_result_text': 'OK: CONFIG_NAME_12 is not off', 'check_result': True}]
279         )
280
281     def test_complex_and(self):
282         # 1. prepare the checklist
283         config_checklist = []
284         config_checklist += [AND(KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1'),
285                                  KconfigCheck('reason_2', 'decision_2', 'NAME_2', 'expected_2'))]
286         config_checklist += [AND(KconfigCheck('reason_3', 'decision_3', 'NAME_3', 'expected_3'),
287                                  KconfigCheck('reason_4', 'decision_4', 'NAME_4', 'expected_4'))]
288         config_checklist += [AND(KconfigCheck('reason_5', 'decision_5', 'NAME_5', 'expected_5'),
289                                  KconfigCheck('reason_6', 'decision_6', 'NAME_6', 'expected_6'))]
290         config_checklist += [AND(KconfigCheck('reason_7', 'decision_7', 'NAME_7', 'expected_7'),
291                                  KconfigCheck('reason_8', 'decision_8', 'NAME_8', 'is present'))]
292         config_checklist += [AND(KconfigCheck('reason_9', 'decision_9', 'NAME_9', 'expected_9'),
293                                  KconfigCheck('reason_10', 'decision_10', 'NAME_10', 'is not off'))]
294         config_checklist += [AND(KconfigCheck('reason_11', 'decision_11', 'NAME_11', 'expected_11'),
295                                  KconfigCheck('reason_12', 'decision_12', 'NAME_12', 'is not off'))]
296
297         # 2. prepare the parsed kconfig options
298         parsed_kconfig_options = OrderedDict()
299         parsed_kconfig_options['CONFIG_NAME_1'] = 'expected_1'
300         parsed_kconfig_options['CONFIG_NAME_2'] = 'expected_2'
301         parsed_kconfig_options['CONFIG_NAME_3'] = 'expected_3'
302         parsed_kconfig_options['CONFIG_NAME_4'] = 'UNexpected_4'
303         parsed_kconfig_options['CONFIG_NAME_5'] = 'UNexpected_5'
304         parsed_kconfig_options['CONFIG_NAME_6'] = 'expected_6'
305         parsed_kconfig_options['CONFIG_NAME_7'] = 'expected_7'
306         parsed_kconfig_options['CONFIG_NAME_9'] = 'expected_9'
307         parsed_kconfig_options['CONFIG_NAME_10'] = '0'
308         parsed_kconfig_options['CONFIG_NAME_11'] = 'expected_11'
309
310         # 3. run the engine
311         self.run_engine(config_checklist, parsed_kconfig_options, None, None, None)
312
313         # 4. check that the results are correct
314         result = []
315         self.get_engine_result(config_checklist, result, 'json')
316         self.assertEqual(
317                 result,
318                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
319                  {'option_name': 'CONFIG_NAME_3', 'type': 'kconfig', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: CONFIG_NAME_4 is not "expected_4"', 'check_result': False},
320                  {'option_name': 'CONFIG_NAME_5', 'type': 'kconfig', 'desired_val': 'expected_5', 'decision': 'decision_5', 'reason': 'reason_5', 'check_result_text': 'FAIL: "UNexpected_5"', 'check_result': False},
321                  {'option_name': 'CONFIG_NAME_7', 'type': 'kconfig', 'desired_val': 'expected_7', 'decision': 'decision_7', 'reason': 'reason_7', 'check_result_text': 'FAIL: CONFIG_NAME_8 is not present', 'check_result': False},
322                  {'option_name': 'CONFIG_NAME_9', 'type': 'kconfig', 'desired_val': 'expected_9', 'decision': 'decision_9', 'reason': 'reason_9', 'check_result_text': 'FAIL: CONFIG_NAME_10 is off', 'check_result': False},
323                  {'option_name': 'CONFIG_NAME_11', 'type': 'kconfig', 'desired_val': 'expected_11', 'decision': 'decision_11', 'reason': 'reason_11', 'check_result_text': 'FAIL: CONFIG_NAME_12 is off, not found', 'check_result': False}]
324         )
325
326     def test_complex_nested(self):
327         # 1. prepare the checklist
328         config_checklist = []
329         config_checklist += [AND(KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1'),
330                                  OR(KconfigCheck('reason_2', 'decision_2', 'NAME_2', 'expected_2'),
331                                     KconfigCheck('reason_3', 'decision_3', 'NAME_3', 'expected_3')))]
332         config_checklist += [AND(KconfigCheck('reason_4', 'decision_4', 'NAME_4', 'expected_4'),
333                                  OR(KconfigCheck('reason_5', 'decision_5', 'NAME_5', 'expected_5'),
334                                     KconfigCheck('reason_6', 'decision_6', 'NAME_6', 'expected_6')))]
335         config_checklist += [OR(KconfigCheck('reason_7', 'decision_7', 'NAME_7', 'expected_7'),
336                                  AND(KconfigCheck('reason_8', 'decision_8', 'NAME_8', 'expected_8'),
337                                      KconfigCheck('reason_9', 'decision_9', 'NAME_9', 'expected_9')))]
338         config_checklist += [OR(KconfigCheck('reason_10', 'decision_10', 'NAME_10', 'expected_10'),
339                                  AND(KconfigCheck('reason_11', 'decision_11', 'NAME_11', 'expected_11'),
340                                      KconfigCheck('reason_12', 'decision_12', 'NAME_12', 'expected_12')))]
341
342         # 2. prepare the parsed kconfig options
343         parsed_kconfig_options = OrderedDict()
344         parsed_kconfig_options['CONFIG_NAME_1'] = 'expected_1'
345         parsed_kconfig_options['CONFIG_NAME_2'] = 'UNexpected_2'
346         parsed_kconfig_options['CONFIG_NAME_3'] = 'expected_3'
347         parsed_kconfig_options['CONFIG_NAME_4'] = 'expected_4'
348         parsed_kconfig_options['CONFIG_NAME_5'] = 'UNexpected_5'
349         parsed_kconfig_options['CONFIG_NAME_6'] = 'UNexpected_6'
350         parsed_kconfig_options['CONFIG_NAME_7'] = 'UNexpected_7'
351         parsed_kconfig_options['CONFIG_NAME_8'] = 'expected_8'
352         parsed_kconfig_options['CONFIG_NAME_9'] = 'expected_9'
353         parsed_kconfig_options['CONFIG_NAME_10'] = 'UNexpected_10'
354         parsed_kconfig_options['CONFIG_NAME_11'] = 'UNexpected_11'
355         parsed_kconfig_options['CONFIG_NAME_12'] = 'expected_12'
356
357         # 3. run the engine
358         self.run_engine(config_checklist, parsed_kconfig_options, None, None, None)
359
360         # 4. check that the results are correct
361         result = []
362         self.get_engine_result(config_checklist, result, 'json')
363         self.assertEqual(
364                 result,
365                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
366                  {'option_name': 'CONFIG_NAME_4', 'type': 'kconfig', 'desired_val': 'expected_4', 'decision': 'decision_4', 'reason': 'reason_4', 'check_result_text': 'FAIL: CONFIG_NAME_5 is not "expected_5"', 'check_result': False},
367                  {'option_name': 'CONFIG_NAME_7', 'type': 'kconfig', 'desired_val': 'expected_7', 'decision': 'decision_7', 'reason': 'reason_7', 'check_result_text': 'OK: CONFIG_NAME_8 is "expected_8"', 'check_result': True},
368                  {'option_name': 'CONFIG_NAME_10', 'type': 'kconfig', 'desired_val': 'expected_10', 'decision': 'decision_10', 'reason': 'reason_10', 'check_result_text': 'FAIL: "UNexpected_10"', 'check_result': False}]
369         )
370
371     def test_version(self):
372         # 1. prepare the checklist
373         config_checklist = []
374         config_checklist += [OR(KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1'),
375                                 VersionCheck((41, 101, 0)))]
376         config_checklist += [AND(KconfigCheck('reason_2', 'decision_2', 'NAME_2', 'expected_2'),
377                                  VersionCheck((43, 1, 0)))]
378         config_checklist += [OR(KconfigCheck('reason_3', 'decision_3', 'NAME_3', 'expected_3'),
379                                 VersionCheck((42, 42, 101)))]
380         config_checklist += [AND(KconfigCheck('reason_4', 'decision_4', 'NAME_4', 'expected_4'),
381                                  VersionCheck((42, 44, 1)))]
382         config_checklist += [OR(KconfigCheck('reason_5', 'decision_5', 'NAME_5', 'expected_5'),
383                                 VersionCheck((42, 43, 44)))]
384         config_checklist += [AND(KconfigCheck('reason_6', 'decision_6', 'NAME_6', 'expected_6'),
385                                  VersionCheck((42, 43, 45)))]
386
387         # 2. prepare the parsed kconfig options
388         parsed_kconfig_options = OrderedDict()
389         parsed_kconfig_options['CONFIG_NAME_2'] = 'expected_2'
390         parsed_kconfig_options['CONFIG_NAME_4'] = 'expected_4'
391         parsed_kconfig_options['CONFIG_NAME_6'] = 'expected_6'
392
393         # 3. prepare the kernel version
394         kernel_version = (42, 43, 44)
395
396         # 4. run the engine
397         self.run_engine(config_checklist, parsed_kconfig_options, None, None, kernel_version)
398
399         # 5. check that the results are correct
400         result = []
401         self.get_engine_result(config_checklist, result, 'json')
402         self.assertEqual(
403                 result,
404                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK: version >= (41, 101, 0)', 'check_result': True},
405                  {'option_name': 'CONFIG_NAME_2', 'type': 'kconfig', 'desired_val': 'expected_2', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'FAIL: version < (43, 1, 0)', 'check_result': False},
406                  {'option_name': 'CONFIG_NAME_3', 'type': 'kconfig', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'OK: version >= (42, 42, 101)', 'check_result': True},
407                  {'option_name': 'CONFIG_NAME_4', 'type': 'kconfig', 'desired_val': 'expected_4', 'decision': 'decision_4', 'reason': 'reason_4', 'check_result_text': 'FAIL: version < (42, 44, 1)', 'check_result': False},
408                  {'option_name': 'CONFIG_NAME_5', 'type': 'kconfig', 'desired_val': 'expected_5', 'decision': 'decision_5', 'reason': 'reason_5', 'check_result_text': 'OK: version >= (42, 43, 44)', 'check_result': True},
409                  {'option_name': 'CONFIG_NAME_6', 'type': 'kconfig', 'desired_val': 'expected_6', 'decision': 'decision_6', 'reason': 'reason_6', 'check_result_text': 'FAIL: version < (42, 43, 45)', 'check_result': False}]
410         )
411
412     def test_stdout(self):
413         # 1. prepare the checklist
414         config_checklist = []
415         config_checklist += [OR(KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1'),
416                                 CmdlineCheck('reason_2', 'decision_2', 'name_2', 'expected_2'),
417                                 SysctlCheck('reason_3', 'decision_3', 'name_3', 'expected_3'))]
418         config_checklist += [AND(KconfigCheck('reason_4', 'decision_4', 'NAME_4', 'expected_4'),
419                                  CmdlineCheck('reason_5', 'decision_5', 'name_5', 'expected_5'),
420                                  SysctlCheck('reason_6', 'decision_6', 'name_6', 'expected_6'))]
421
422         # 2. prepare the parsed kconfig options
423         parsed_kconfig_options = OrderedDict()
424         parsed_kconfig_options['CONFIG_NAME_1'] = 'UNexpected_1'
425
426         # 3. prepare the parsed cmdline options
427         parsed_cmdline_options = OrderedDict()
428         parsed_cmdline_options['name_2'] = 'expected_2'
429         parsed_cmdline_options['name_5'] = 'UNexpected_5'
430
431         # 4. prepare the parsed sysctl options
432         parsed_sysctl_options = OrderedDict()
433         parsed_sysctl_options['name_6'] = 'expected_6'
434
435         # 5. run the engine
436         self.run_engine(config_checklist, parsed_kconfig_options, parsed_cmdline_options, parsed_sysctl_options, None)
437
438         # 6. check that the results are correct
439         json_result = []
440         self.get_engine_result(config_checklist, json_result, 'json')
441         self.assertEqual(
442                 json_result,
443                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK: name_2 is "expected_2"', 'check_result': True},
444                  {'option_name': 'CONFIG_NAME_4', 'type': 'kconfig', 'desired_val': 'expected_4', 'decision': 'decision_4', 'reason': 'reason_4', 'check_result_text': 'FAIL: name_5 is not "expected_5"', 'check_result': False}]
445         )
446
447         stdout_result = []
448         self.get_engine_result(config_checklist, stdout_result, 'stdout')
449         self.assertEqual(
450                 stdout_result,
451                 [
452 '\
453 CONFIG_NAME_1                           |kconfig| expected_1 |decision_1|     reason_1     | \x1b[32mOK: name_2 is "expected_2"\x1b[0m\
454 CONFIG_NAME_4                           |kconfig| expected_4 |decision_4|     reason_4     | \x1b[31mFAIL: name_5 is not "expected_5"\x1b[0m\
455 '               ]
456         )
457
458         stdout_result = []
459         self.get_engine_result(config_checklist, stdout_result, 'stdout_verbose')
460         self.assertEqual(
461                 stdout_result,
462                 [
463 '\
464     <<< OR >>>                                                                             | \x1b[32mOK: name_2 is "expected_2"\x1b[0m\n\
465 CONFIG_NAME_1                           |kconfig| expected_1 |decision_1|     reason_1     | \x1b[31mFAIL: "UNexpected_1"\x1b[0m\n\
466 name_2                                  |cmdline| expected_2 |decision_2|     reason_2     | \x1b[32mOK\x1b[0m\n\
467 name_3                                  |sysctl | expected_3 |decision_3|     reason_3     | None\
468 '\
469 '\
470     <<< AND >>>                                                                            | \x1b[31mFAIL: name_5 is not "expected_5"\x1b[0m\n\
471 CONFIG_NAME_4                           |kconfig| expected_4 |decision_4|     reason_4     | None\n\
472 name_5                                  |cmdline| expected_5 |decision_5|     reason_5     | \x1b[31mFAIL: "UNexpected_5"\x1b[0m\n\
473 name_6                                  |sysctl | expected_6 |decision_6|     reason_6     | \x1b[32mOK\x1b[0m\
474 '               ]
475         )
476
477     def test_value_overriding(self):
478         # 1. prepare the checklist
479         config_checklist = []
480         config_checklist += [KconfigCheck('reason_1', 'decision_1', 'NAME_1', 'expected_1')]
481         config_checklist += [CmdlineCheck('reason_2', 'decision_2', 'name_2', 'expected_2')]
482         config_checklist += [SysctlCheck('reason_3', 'decision_3', 'name_3', 'expected_3')]
483
484         # 2. prepare the parsed kconfig options
485         parsed_kconfig_options = OrderedDict()
486         parsed_kconfig_options['CONFIG_NAME_1'] = 'expected_1_new'
487
488         # 3. prepare the parsed cmdline options
489         parsed_cmdline_options = OrderedDict()
490         parsed_cmdline_options['name_2'] = 'expected_2_new'
491
492         # 4. prepare the parsed sysctl options
493         parsed_sysctl_options = OrderedDict()
494         parsed_sysctl_options['name_3'] = 'expected_3_new'
495
496         # 5. run the engine
497         self.run_engine(config_checklist, parsed_kconfig_options, parsed_cmdline_options, parsed_sysctl_options, None)
498
499         # 6. check that the results are correct
500         result = []
501         self.get_engine_result(config_checklist, result, 'json')
502         self.assertEqual(
503                 result,
504                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'FAIL: "expected_1_new"', 'check_result': False},
505                  {'option_name': 'name_2', 'type': 'cmdline', 'desired_val': 'expected_2', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'FAIL: "expected_2_new"', 'check_result': False},
506                  {'option_name': 'name_3', 'type': 'sysctl', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: "expected_3_new"', 'check_result': False}]
507         )
508
509         # 7. override expected value and perform the checks again
510         override_expected_value(config_checklist, 'CONFIG_NAME_1', 'expected_1_new')
511         perform_checks(config_checklist)
512
513         # 8. check that the results are correct
514         result = []
515         self.get_engine_result(config_checklist, result, 'json')
516         self.assertEqual(
517                 result,
518                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1_new', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
519                  {'option_name': 'name_2', 'type': 'cmdline', 'desired_val': 'expected_2', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'FAIL: "expected_2_new"', 'check_result': False},
520                  {'option_name': 'name_3', 'type': 'sysctl', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: "expected_3_new"', 'check_result': False}]
521         )
522
523         # 9. override expected value and perform the checks again
524         override_expected_value(config_checklist, 'name_2', 'expected_2_new')
525         perform_checks(config_checklist)
526
527         # 10. check that the results are correct
528         result = []
529         self.get_engine_result(config_checklist, result, 'json')
530         self.assertEqual(
531                 result,
532                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1_new', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
533                  {'option_name': 'name_2', 'type': 'cmdline', 'desired_val': 'expected_2_new', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'OK', 'check_result': True},
534                  {'option_name': 'name_3', 'type': 'sysctl', 'desired_val': 'expected_3', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'FAIL: "expected_3_new"', 'check_result': False}]
535         )
536
537         # 11. override expected value and perform the checks again
538         override_expected_value(config_checklist, 'name_3', 'expected_3_new')
539         perform_checks(config_checklist)
540
541         # 12. check that the results are correct
542         result = []
543         self.get_engine_result(config_checklist, result, 'json')
544         self.assertEqual(
545                 result,
546                 [{'option_name': 'CONFIG_NAME_1', 'type': 'kconfig', 'desired_val': 'expected_1_new', 'decision': 'decision_1', 'reason': 'reason_1', 'check_result_text': 'OK', 'check_result': True},
547                  {'option_name': 'name_2', 'type': 'cmdline', 'desired_val': 'expected_2_new', 'decision': 'decision_2', 'reason': 'reason_2', 'check_result_text': 'OK', 'check_result': True},
548                  {'option_name': 'name_3', 'type': 'sysctl', 'desired_val': 'expected_3_new', 'decision': 'decision_3', 'reason': 'reason_3', 'check_result_text': 'OK', 'check_result': True}]
549         )