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