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