1#!/usr/bin/env python3
2
3# Copyright 2023 Google LLC
4# SPDX-License-Identifier: Apache-2.0
5"""
6Tests for check_init_priorities
7"""
8
9import mock
10import pathlib
11import unittest
12
13from elftools.elf.relocation import Section
14from elftools.elf.sections import SymbolTableSection
15
16import check_init_priorities
17
18class TestPriority(unittest.TestCase):
19    """Tests for the Priority class."""
20
21    def test_priority_parsing(self):
22        prio1 = check_init_priorities.Priority("POST_KERNEL", 12)
23        self.assertEqual(prio1._level_priority, (3, 12))
24
25        prio1 = check_init_priorities.Priority("APPLICATION", 9999)
26        self.assertEqual(prio1._level_priority, (4, 9999))
27
28        with self.assertRaises(ValueError):
29            check_init_priorities.Priority("i-am-not-a-priority", 0)
30            check_init_priorities.Priority("_DOESNOTEXIST0_", 0)
31
32    def test_priority_levels(self):
33        prios = [
34                check_init_priorities.Priority("EARLY", 0),
35                check_init_priorities.Priority("EARLY", 1),
36                check_init_priorities.Priority("PRE_KERNEL_1", 0),
37                check_init_priorities.Priority("PRE_KERNEL_1", 1),
38                check_init_priorities.Priority("PRE_KERNEL_2", 0),
39                check_init_priorities.Priority("PRE_KERNEL_2", 1),
40                check_init_priorities.Priority("POST_KERNEL", 0),
41                check_init_priorities.Priority("POST_KERNEL", 1),
42                check_init_priorities.Priority("APPLICATION", 0),
43                check_init_priorities.Priority("APPLICATION", 1),
44                check_init_priorities.Priority("SMP", 0),
45                check_init_priorities.Priority("SMP", 1),
46                ]
47
48        self.assertListEqual(prios, sorted(prios))
49
50    def test_priority_strings(self):
51        prio = check_init_priorities.Priority("POST_KERNEL", 12)
52        self.assertEqual(str(prio), "POST_KERNEL+12")
53        self.assertEqual(repr(prio), "<Priority POST_KERNEL 12>")
54
55class testZephyrInitLevels(unittest.TestCase):
56    """Tests for the ZephyrInitLevels class."""
57
58    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
59    def test_load_objects(self, mock_zilinit):
60        mock_elf = mock.Mock()
61
62        sts = mock.Mock(spec=SymbolTableSection)
63        rel = mock.Mock(spec=Section)
64        mock_elf.iter_sections.return_value = [sts, rel]
65
66        s0 = mock.Mock()
67        s0.name = "a"
68        s0.entry.st_info.type = "STT_OBJECT"
69        s0.entry.st_size = 4
70        s0.entry.st_value = 0xaa
71        s0.entry.st_shndx = 1
72
73        s1 = mock.Mock()
74        s1.name = None
75
76        s2 = mock.Mock()
77        s2.name = "b"
78        s2.entry.st_info.type = "STT_FUNC"
79        s2.entry.st_size = 8
80        s2.entry.st_value = 0xbb
81        s2.entry.st_shndx = 2
82
83        sts.iter_symbols.return_value = [s0, s1, s2]
84
85        obj = check_init_priorities.ZephyrInitLevels("")
86        obj._elf = mock_elf
87        obj._load_objects()
88
89        self.assertDictEqual(obj._objects, {0xaa: ("a", 4, 1), 0xbb: ("b", 8, 2)})
90
91    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
92    def test_load_level_addr(self, mock_zilinit):
93        mock_elf = mock.Mock()
94
95        sts = mock.Mock(spec=SymbolTableSection)
96        rel = mock.Mock(spec=Section)
97        mock_elf.iter_sections.return_value = [sts, rel]
98
99        s0 = mock.Mock()
100        s0.name = "__init_EARLY_start"
101        s0.entry.st_value = 0x00
102
103        s1 = mock.Mock()
104        s1.name = "__init_PRE_KERNEL_1_start"
105        s1.entry.st_value = 0x11
106
107        s2 = mock.Mock()
108        s2.name = "__init_PRE_KERNEL_2_start"
109        s2.entry.st_value = 0x22
110
111        s3 = mock.Mock()
112        s3.name = "__init_POST_KERNEL_start"
113        s3.entry.st_value = 0x33
114
115        s4 = mock.Mock()
116        s4.name = "__init_APPLICATION_start"
117        s4.entry.st_value = 0x44
118
119        s5 = mock.Mock()
120        s5.name = "__init_SMP_start"
121        s5.entry.st_value = 0x55
122
123        s6 = mock.Mock()
124        s6.name = "__init_end"
125        s6.entry.st_value = 0x66
126
127        sts.iter_symbols.return_value = [s0, s1, s2, s3, s4, s5, s6]
128
129        obj = check_init_priorities.ZephyrInitLevels("")
130        obj._elf = mock_elf
131        obj._load_level_addr()
132
133        self.assertDictEqual(obj._init_level_addr, {
134            "EARLY": 0x00,
135            "PRE_KERNEL_1": 0x11,
136            "PRE_KERNEL_2": 0x22,
137            "POST_KERNEL": 0x33,
138            "APPLICATION": 0x44,
139            "SMP": 0x55,
140            })
141        self.assertEqual(obj._init_level_end, 0x66)
142
143    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
144    def test_device_ord_from_name(self, mock_zilinit):
145        obj = check_init_priorities.ZephyrInitLevels("")
146
147        self.assertEqual(obj._device_ord_from_name(None), None)
148        self.assertEqual(obj._device_ord_from_name("hey, hi!"), None)
149        self.assertEqual(obj._device_ord_from_name("__device_dts_ord_123"), 123)
150
151    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
152    def test_object_name(self, mock_zilinit):
153        obj = check_init_priorities.ZephyrInitLevels("")
154        obj._objects = {0x123: ("name", 4)}
155
156        self.assertEqual(obj._object_name(0), "NULL")
157        self.assertEqual(obj._object_name(73), "unknown")
158        self.assertEqual(obj._object_name(0x123), "name")
159
160    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
161    def test_initlevel_pointer_32(self, mock_zilinit):
162        obj = check_init_priorities.ZephyrInitLevels("")
163        obj._elf = mock.Mock()
164        obj._elf.elfclass = 32
165        mock_section = mock.Mock()
166        obj._elf.get_section.return_value = mock_section
167        mock_section.header.sh_addr = 0x100
168        mock_section.data.return_value = (b"\x01\x00\x00\x00"
169                                          b"\x02\x00\x00\x00"
170                                          b"\x03\x00\x00\x00")
171
172        self.assertEqual(obj._initlevel_pointer(0x100, 0, 0), 1)
173        self.assertEqual(obj._initlevel_pointer(0x100, 1, 0), 2)
174        self.assertEqual(obj._initlevel_pointer(0x104, 0, 0), 2)
175        self.assertEqual(obj._initlevel_pointer(0x104, 1, 0), 3)
176
177    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
178    def test_initlevel_pointer_64(self, mock_zilinit):
179        obj = check_init_priorities.ZephyrInitLevels("")
180        obj._elf = mock.Mock()
181        obj._elf.elfclass = 64
182        mock_section = mock.Mock()
183        obj._elf.get_section.return_value = mock_section
184        mock_section.header.sh_addr = 0x100
185        mock_section.data.return_value = (b"\x01\x00\x00\x00\x00\x00\x00\x00"
186                                          b"\x02\x00\x00\x00\x00\x00\x00\x00"
187                                          b"\x03\x00\x00\x00\x00\x00\x00\x00")
188
189        self.assertEqual(obj._initlevel_pointer(0x100, 0, 0), 1)
190        self.assertEqual(obj._initlevel_pointer(0x100, 1, 0), 2)
191        self.assertEqual(obj._initlevel_pointer(0x108, 0, 0), 2)
192        self.assertEqual(obj._initlevel_pointer(0x108, 1, 0), 3)
193
194    @mock.patch("check_init_priorities.ZephyrInitLevels._object_name")
195    @mock.patch("check_init_priorities.ZephyrInitLevels._initlevel_pointer")
196    @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
197    def test_process_initlevels(self, mock_zilinit, mock_ip, mock_on):
198        obj = check_init_priorities.ZephyrInitLevels("")
199        obj._init_level_addr = {
200            "EARLY": 0x00,
201            "PRE_KERNEL_1": 0x00,
202            "PRE_KERNEL_2": 0x00,
203            "POST_KERNEL": 0x08,
204            "APPLICATION": 0x0c,
205            "SMP": 0x0c,
206            }
207        obj._init_level_end = 0x0c
208        obj._objects = {
209                0x00: ("a", 4, 0),
210                0x04: ("b", 4, 0),
211                0x08: ("c", 4, 0),
212                }
213
214        mock_ip.side_effect = lambda *args: args
215
216        def mock_obj_name(*args):
217            if args[0] == (0, 0, 0):
218                return "i0"
219            elif args[0] == (0, 1, 0):
220                return "__device_dts_ord_11"
221            elif args[0] == (4, 0, 0):
222                return "i1"
223            elif args[0] == (4, 1, 0):
224                return "__device_dts_ord_22"
225            return f"name_{args[0][0]}_{args[0][1]}"
226        mock_on.side_effect = mock_obj_name
227
228        obj._process_initlevels()
229
230        self.assertDictEqual(obj.initlevels, {
231            "EARLY": [],
232            "PRE_KERNEL_1": [],
233            "PRE_KERNEL_2": ["a: i0(__device_dts_ord_11)", "b: i1(__device_dts_ord_22)"],
234            "POST_KERNEL": ["c: name_8_0(name_8_1)"],
235            "APPLICATION": [],
236            "SMP": [],
237            })
238        self.assertDictEqual(obj.devices, {
239            11: (check_init_priorities.Priority("PRE_KERNEL_2", 0), "i0"),
240            22: (check_init_priorities.Priority("PRE_KERNEL_2", 1), "i1"),
241            })
242
243class testValidator(unittest.TestCase):
244    """Tests for the Validator class."""
245
246    @mock.patch("check_init_priorities.ZephyrInitLevels")
247    @mock.patch("pickle.load")
248    def test_initialize(self, mock_pl, mock_zil):
249        mock_log = mock.Mock()
250        mock_prio = mock.Mock()
251        mock_obj = mock.Mock()
252        mock_obj.defined_devices = {123: mock_prio}
253        mock_zil.return_value = mock_obj
254
255        with mock.patch("builtins.open", mock.mock_open()) as mock_open:
256            validator = check_init_priorities.Validator("path", "pickle", mock_log)
257
258        self.assertEqual(validator._obj, mock_obj)
259        mock_zil.assert_called_once_with("path")
260        mock_open.assert_called_once_with(pathlib.Path("pickle"), "rb")
261
262    @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
263    def test_check_dep_same_node(self, mock_vinit):
264        validator = check_init_priorities.Validator("", "", None)
265        validator.log = mock.Mock()
266
267        validator._check_dep(123, 123)
268
269        self.assertFalse(validator.log.info.called)
270        self.assertFalse(validator.log.warning.called)
271        self.assertFalse(validator.log.error.called)
272
273    @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
274    def test_check_dep_no_prio(self, mock_vinit):
275        validator = check_init_priorities.Validator("", "", None)
276        validator.log = mock.Mock()
277        validator._obj = mock.Mock()
278
279        validator._ord2node = {1: mock.Mock(), 2: mock.Mock()}
280        validator._ord2node[1]._binding = None
281        validator._ord2node[2]._binding = None
282
283        validator._obj.devices = {1: (10, "i1")}
284        validator._check_dep(1, 2)
285
286        validator._obj.devices = {2: (20, "i2")}
287        validator._check_dep(1, 2)
288
289        self.assertFalse(validator.log.info.called)
290        self.assertFalse(validator.log.warning.called)
291        self.assertFalse(validator.log.error.called)
292
293    @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
294    def test_check(self, mock_vinit):
295        validator = check_init_priorities.Validator("", "", None)
296        validator.log = mock.Mock()
297        validator._obj = mock.Mock()
298        validator.errors = 0
299
300        validator._ord2node = {1: mock.Mock(), 2: mock.Mock()}
301        validator._ord2node[1]._binding = None
302        validator._ord2node[1].path = "/1"
303        validator._ord2node[2]._binding = None
304        validator._ord2node[2].path = "/2"
305
306        validator._obj.devices = {1: (10, "i1"), 2: (20, "i2")}
307
308        validator._check_dep(2, 1)
309        validator._check_dep(1, 2)
310
311        validator.log.info.assert_called_once_with("/2 <i2> 20 > /1 <i1> 10")
312        validator.log.error.assert_has_calls([
313            mock.call("/1 <i1> is initialized before its dependency /2 <i2> (10 < 20)")
314            ])
315        self.assertEqual(validator.errors, 1)
316
317    @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
318    def test_check_same_prio_assert(self, mock_vinit):
319        validator = check_init_priorities.Validator("", "", None)
320        validator.log = mock.Mock()
321        validator._obj = mock.Mock()
322        validator.errors = 0
323
324        validator._ord2node = {1: mock.Mock(), 2: mock.Mock()}
325        validator._ord2node[1]._binding = None
326        validator._ord2node[1].path = "/1"
327        validator._ord2node[2]._binding = None
328        validator._ord2node[2].path = "/2"
329
330        validator._obj.devices = {1: (10, "i1"), 2: (10, "i2")}
331
332        with self.assertRaises(ValueError):
333            validator._check_dep(1, 2)
334
335    @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
336    def test_check_ignored(self, mock_vinit):
337        validator = check_init_priorities.Validator("", "", None)
338        validator.log = mock.Mock()
339        validator._obj = mock.Mock()
340        validator.errors = 0
341
342        save_ignore_compatibles = check_init_priorities._IGNORE_COMPATIBLES
343
344        check_init_priorities._IGNORE_COMPATIBLES = set(["compat-3"])
345
346        validator._ord2node = {1: mock.Mock(), 3: mock.Mock()}
347        validator._ord2node[1]._binding.compatible = "compat-1"
348        validator._ord2node[1].path = "/1"
349        validator._ord2node[3]._binding.compatible = "compat-3"
350        validator._ord2node[3].path = "/3"
351
352        validator._obj.devices = {1: 20, 3: 10}
353
354        validator._check_dep(3, 1)
355
356        self.assertListEqual(validator.log.info.call_args_list, [
357            mock.call("Ignoring priority: compat-3"),
358        ])
359        self.assertEqual(validator.errors, 0)
360
361        check_init_priorities._IGNORE_COMPATIBLES = save_ignore_compatibles
362
363    @mock.patch("check_init_priorities.Validator._check_dep")
364    @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
365    def test_check_edt(self, mock_vinit, mock_cd):
366        d0 = mock.Mock()
367        d0.dep_ordinal = 1
368        d1 = mock.Mock()
369        d1.dep_ordinal = 2
370        d2 = mock.Mock()
371        d2.dep_ordinal = 3
372
373        dev0 = mock.Mock()
374        dev0.depends_on = [d0]
375        dev1 = mock.Mock()
376        dev1.depends_on = [d1]
377        dev2 = mock.Mock()
378        dev2.depends_on = [d2]
379
380        validator = check_init_priorities.Validator("", "", None)
381        validator._ord2node = {1: dev0, 2: dev1, 3: dev2}
382        validator._obj = mock.Mock()
383        validator._obj.devices = {1: 10, 2: 10, 3: 20}
384
385        validator.check_edt()
386
387        self.assertListEqual(mock_cd.call_args_list, [
388            mock.call(1, 1),
389            mock.call(2, 2),
390            mock.call(3, 3),
391            ])
392
393if __name__ == "__main__":
394    unittest.main()
395