blob: d32aa58400f4c107eb13203379d695571cb18be3 [file] [log] [blame]
Simon Glassdf692c32020-12-28 20:35:07 -07001# SPDX-License-Identifier: GPL-2.0+
2# Copyright 2020 Google LLC
3#
4
5"""Tests for the src_scan module
6
7This includes unit tests for scanning of the source code
8"""
9
Simon Glass2e199ab2021-02-03 06:00:54 -070010import copy
Simon Glassdf692c32020-12-28 20:35:07 -070011import os
12import shutil
13import tempfile
14import unittest
15from unittest import mock
16
17from dtoc import src_scan
Simon Glassb430e9e2020-12-28 20:35:08 -070018from patman import test_util
Simon Glassdf692c32020-12-28 20:35:07 -070019from patman import tools
20
Simon Glass78933b72021-02-03 06:00:50 -070021OUR_PATH = os.path.dirname(os.path.realpath(__file__))
22
23class FakeNode:
24 """Fake Node object for testing"""
25 def __init__(self):
26 self.name = None
27 self.props = {}
28
29class FakeProp:
30 """Fake Prop object for testing"""
31 def __init__(self):
32 self.name = None
33 self.value = None
34
Simon Glassdf692c32020-12-28 20:35:07 -070035# This is a test so is allowed to access private things in the module it is
36# testing
37# pylint: disable=W0212
38
39class TestSrcScan(unittest.TestCase):
40 """Tests for src_scan"""
41 @classmethod
42 def setUpClass(cls):
43 tools.PrepareOutputDir(None)
44
45 @classmethod
46 def tearDownClass(cls):
47 tools.FinaliseOutputDir()
48
Simon Glassb430e9e2020-12-28 20:35:08 -070049 def test_simple(self):
50 """Simple test of scanning drivers"""
51 scan = src_scan.Scanner(None, True, None)
52 scan.scan_drivers()
53 self.assertIn('sandbox_gpio', scan._drivers)
54 self.assertIn('sandbox_gpio_alias', scan._driver_aliases)
55 self.assertEqual('sandbox_gpio',
56 scan._driver_aliases['sandbox_gpio_alias'])
57 self.assertNotIn('sandbox_gpio_alias2', scan._driver_aliases)
58
59 def test_additional(self):
60 """Test with additional drivers to scan"""
Simon Glassdf692c32020-12-28 20:35:07 -070061 scan = src_scan.Scanner(
Simon Glass4f4b2402021-02-03 06:00:56 -070062 None, True,
63 [None, '', 'tools/dtoc/test/dtoc_test_scan_drivers.cxx'])
Simon Glassdf692c32020-12-28 20:35:07 -070064 scan.scan_drivers()
Simon Glassb430e9e2020-12-28 20:35:08 -070065 self.assertIn('sandbox_gpio_alias2', scan._driver_aliases)
66 self.assertEqual('sandbox_gpio',
67 scan._driver_aliases['sandbox_gpio_alias2'])
Simon Glassdf692c32020-12-28 20:35:07 -070068
Simon Glassb430e9e2020-12-28 20:35:08 -070069 def test_unicode_error(self):
Simon Glassdf692c32020-12-28 20:35:07 -070070 """Test running dtoc with an invalid unicode file
71
72 To be able to perform this test without adding a weird text file which
73 would produce issues when using checkpatch.pl or patman, generate the
74 file at runtime and then process it.
75 """
76 driver_fn = '/tmp/' + next(tempfile._get_candidate_names())
77 with open(driver_fn, 'wb+') as fout:
78 fout.write(b'\x81')
79
Simon Glassb430e9e2020-12-28 20:35:08 -070080 scan = src_scan.Scanner(None, True, [driver_fn])
81 with test_util.capture_sys_output() as (stdout, _):
82 scan.scan_drivers()
83 self.assertRegex(stdout.getvalue(),
84 r"Skipping file '.*' due to unicode error\s*")
Simon Glassdf692c32020-12-28 20:35:07 -070085
86 def test_driver(self):
87 """Test the Driver class"""
Simon Glass78933b72021-02-03 06:00:50 -070088 i2c = 'I2C_UCLASS'
89 compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF',
90 'rockchip,rk3288-srf': None}
91 drv1 = src_scan.Driver('fred', 'fred.c')
92 drv2 = src_scan.Driver('mary', 'mary.c')
93 drv3 = src_scan.Driver('fred', 'fred.c')
94 drv1.uclass_id = i2c
95 drv1.compat = compat
96 drv2.uclass_id = i2c
97 drv2.compat = compat
98 drv3.uclass_id = i2c
99 drv3.compat = compat
100 self.assertEqual(
Simon Glasseb3c2492021-02-03 06:01:01 -0700101 "Driver(name='fred', used=False, uclass_id='I2C_UCLASS', "
Simon Glass78933b72021-02-03 06:00:50 -0700102 "compat={'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', "
103 "'rockchip,rk3288-srf': None}, priv=)", str(drv1))
Simon Glassdf692c32020-12-28 20:35:07 -0700104 self.assertEqual(drv1, drv3)
105 self.assertNotEqual(drv1, drv2)
106 self.assertNotEqual(drv2, drv3)
107
108 def test_scan_dirs(self):
109 """Test scanning of source directories"""
110 def add_file(fname):
111 pathname = os.path.join(indir, fname)
112 dirname = os.path.dirname(pathname)
113 os.makedirs(dirname, exist_ok=True)
114 tools.WriteFile(pathname, '', binary=False)
115 fname_list.append(pathname)
116
117 try:
118 indir = tempfile.mkdtemp(prefix='dtoc.')
119
120 fname_list = []
121 add_file('fname.c')
Simon Glassdc37c812021-02-03 06:00:52 -0700122 add_file('.git/ignoreme.c')
Simon Glassdf692c32020-12-28 20:35:07 -0700123 add_file('dir/fname2.c')
Simon Glassdc37c812021-02-03 06:00:52 -0700124 add_file('build-sandbox/ignoreme2.c')
Simon Glassdf692c32020-12-28 20:35:07 -0700125
126 # Mock out scan_driver and check that it is called with the
127 # expected files
128 with mock.patch.object(src_scan.Scanner, "scan_driver") as mocked:
129 scan = src_scan.Scanner(indir, True, None)
130 scan.scan_drivers()
131 self.assertEqual(2, len(mocked.mock_calls))
132 self.assertEqual(mock.call(fname_list[0]),
133 mocked.mock_calls[0])
Simon Glassdc37c812021-02-03 06:00:52 -0700134 # .git file should be ignored
135 self.assertEqual(mock.call(fname_list[2]),
Simon Glassdf692c32020-12-28 20:35:07 -0700136 mocked.mock_calls[1])
137 finally:
138 shutil.rmtree(indir)
Simon Glass78933b72021-02-03 06:00:50 -0700139
140 def test_scan(self):
141 """Test scanning of a driver"""
142 fname = os.path.join(OUR_PATH, '..', '..', 'drivers/i2c/tegra_i2c.c')
143 buff = tools.ReadFile(fname, False)
144 scan = src_scan.Scanner(None, False, None)
145 scan._parse_driver(fname, buff)
146 self.assertIn('i2c_tegra', scan._drivers)
147 drv = scan._drivers['i2c_tegra']
148 self.assertEqual('i2c_tegra', drv.name)
149 self.assertEqual('UCLASS_I2C', drv.uclass_id)
150 self.assertEqual(
151 {'nvidia,tegra114-i2c': 'TYPE_114',
152 'nvidia,tegra20-i2c': 'TYPE_STD',
153 'nvidia,tegra20-i2c-dvc': 'TYPE_DVC'}, drv.compat)
154 self.assertEqual('i2c_bus', drv.priv)
155 self.assertEqual(1, len(scan._drivers))
156
157 def test_normalized_name(self):
158 """Test operation of get_normalized_compat_name()"""
159 prop = FakeProp()
160 prop.name = 'compatible'
161 prop.value = 'rockchip,rk3288-grf'
162 node = FakeNode()
163 node.props = {'compatible': prop}
164 scan = src_scan.Scanner(None, False, None)
165 with test_util.capture_sys_output() as (stdout, _):
166 name, aliases = scan.get_normalized_compat_name(node)
167 self.assertEqual('rockchip_rk3288_grf', name)
168 self.assertEqual([], aliases)
169 self.assertEqual(
170 'WARNING: the driver rockchip_rk3288_grf was not found in the driver list',
171 stdout.getvalue().strip())
172
173 i2c = 'I2C_UCLASS'
174 compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF',
175 'rockchip,rk3288-srf': None}
176 drv = src_scan.Driver('fred', 'fred.c')
177 drv.uclass_id = i2c
178 drv.compat = compat
179 scan._drivers['rockchip_rk3288_grf'] = drv
180
181 scan._driver_aliases['rockchip_rk3288_srf'] = 'rockchip_rk3288_grf'
182
183 with test_util.capture_sys_output() as (stdout, _):
184 name, aliases = scan.get_normalized_compat_name(node)
185 self.assertEqual('', stdout.getvalue().strip())
186 self.assertEqual('rockchip_rk3288_grf', name)
187 self.assertEqual([], aliases)
188
189 prop.value = 'rockchip,rk3288-srf'
190 with test_util.capture_sys_output() as (stdout, _):
191 name, aliases = scan.get_normalized_compat_name(node)
192 self.assertEqual('', stdout.getvalue().strip())
193 self.assertEqual('rockchip_rk3288_grf', name)
194 self.assertEqual(['rockchip_rk3288_srf'], aliases)
195
196 def test_scan_errors(self):
197 """Test detection of scanning errors"""
198 buff = '''
199static const struct udevice_id tegra_i2c_ids2[] = {
200 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
201 { }
202};
203
204U_BOOT_DRIVER(i2c_tegra) = {
205 .name = "i2c_tegra",
206 .id = UCLASS_I2C,
207 .of_match = tegra_i2c_ids,
208};
209'''
210 scan = src_scan.Scanner(None, False, None)
211 with self.assertRaises(ValueError) as exc:
212 scan._parse_driver('file.c', buff)
213 self.assertIn(
214 "file.c: Unknown compatible var 'tegra_i2c_ids' (found: tegra_i2c_ids2)",
215 str(exc.exception))
216
217 def test_of_match(self):
218 """Test detection of of_match_ptr() member"""
219 buff = '''
220static const struct udevice_id tegra_i2c_ids[] = {
221 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
222 { }
223};
224
225U_BOOT_DRIVER(i2c_tegra) = {
226 .name = "i2c_tegra",
227 .id = UCLASS_I2C,
228 .of_match = of_match_ptr(tegra_i2c_ids),
229};
230'''
231 scan = src_scan.Scanner(None, False, None)
232 scan._parse_driver('file.c', buff)
233 self.assertIn('i2c_tegra', scan._drivers)
234 drv = scan._drivers['i2c_tegra']
235 self.assertEqual('i2c_tegra', drv.name)
Simon Glassf303ee72021-02-03 06:01:02 -0700236 self.assertEqual('', drv.phase)
Simon Glassa7b1c772021-02-03 06:01:04 -0700237 self.assertEqual([], drv.headers)
Simon Glass0f3b1412021-02-03 06:00:53 -0700238
239 def test_priv(self):
240 """Test collection of struct info from drivers"""
241 buff = '''
242static const struct udevice_id test_ids[] = {
243 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
244 { }
245};
246
247U_BOOT_DRIVER(testing) = {
248 .name = "testing",
249 .id = UCLASS_I2C,
250 .of_match = test_ids,
251 .priv_auto = sizeof(struct some_priv),
252 .plat_auto = sizeof(struct some_plat),
253 .per_child_auto = sizeof(struct some_cpriv),
254 .per_child_plat_auto = sizeof(struct some_cplat),
Simon Glassf303ee72021-02-03 06:01:02 -0700255 DM_PHASE(tpl)
Simon Glassa7b1c772021-02-03 06:01:04 -0700256 DM_HEADER(<i2c.h>)
257 DM_HEADER(<asm/clk.h>)
Simon Glass0f3b1412021-02-03 06:00:53 -0700258};
259'''
260 scan = src_scan.Scanner(None, False, None)
261 scan._parse_driver('file.c', buff)
262 self.assertIn('testing', scan._drivers)
263 drv = scan._drivers['testing']
264 self.assertEqual('testing', drv.name)
265 self.assertEqual('UCLASS_I2C', drv.uclass_id)
266 self.assertEqual(
267 {'nvidia,tegra114-i2c': 'TYPE_114'}, drv.compat)
268 self.assertEqual('some_priv', drv.priv)
269 self.assertEqual('some_plat', drv.plat)
270 self.assertEqual('some_cpriv', drv.child_priv)
271 self.assertEqual('some_cplat', drv.child_plat)
Simon Glassf303ee72021-02-03 06:01:02 -0700272 self.assertEqual('tpl', drv.phase)
Simon Glassa7b1c772021-02-03 06:01:04 -0700273 self.assertEqual(['<i2c.h>', '<asm/clk.h>'], drv.headers)
Simon Glass0f3b1412021-02-03 06:00:53 -0700274 self.assertEqual(1, len(scan._drivers))
Simon Glass2e199ab2021-02-03 06:00:54 -0700275
276 def test_uclass_scan(self):
277 """Test collection of uclass-driver info"""
278 buff = '''
279UCLASS_DRIVER(i2c) = {
280 .id = UCLASS_I2C,
281 .name = "i2c",
282 .flags = DM_UC_FLAG_SEQ_ALIAS,
283 .priv_auto = sizeof(struct some_priv),
284 .per_device_auto = sizeof(struct per_dev_priv),
285 .per_device_plat_auto = sizeof(struct per_dev_plat),
286 .per_child_auto = sizeof(struct per_child_priv),
287 .per_child_plat_auto = sizeof(struct per_child_plat),
288 .child_post_bind = i2c_child_post_bind,
289};
290
291'''
292 scan = src_scan.Scanner(None, False, None)
293 scan._parse_uclass_driver('file.c', buff)
294 self.assertIn('UCLASS_I2C', scan._uclass)
295 drv = scan._uclass['UCLASS_I2C']
296 self.assertEqual('i2c', drv.name)
297 self.assertEqual('UCLASS_I2C', drv.uclass_id)
298 self.assertEqual('some_priv', drv.priv)
299 self.assertEqual('per_dev_priv', drv.per_dev_priv)
300 self.assertEqual('per_dev_plat', drv.per_dev_plat)
301 self.assertEqual('per_child_priv', drv.per_child_priv)
302 self.assertEqual('per_child_plat', drv.per_child_plat)
303 self.assertEqual(1, len(scan._uclass))
304
305 drv2 = copy.deepcopy(drv)
306 self.assertEqual(drv, drv2)
307 drv2.priv = 'other_priv'
308 self.assertNotEqual(drv, drv2)
309
310 # The hashes only depend on the uclass ID, so should be equal
311 self.assertEqual(drv.__hash__(), drv2.__hash__())
312
313 self.assertEqual("UclassDriver(name='i2c', uclass_id='UCLASS_I2C')",
314 str(drv))
315
316 def test_uclass_scan_errors(self):
317 """Test detection of uclass scanning errors"""
318 buff = '''
319UCLASS_DRIVER(i2c) = {
320 .name = "i2c",
321};
322
323'''
324 scan = src_scan.Scanner(None, False, None)
325 with self.assertRaises(ValueError) as exc:
326 scan._parse_uclass_driver('file.c', buff)
327 self.assertIn("file.c: Cannot parse uclass ID in driver 'i2c'",
328 str(exc.exception))
Simon Glass88bd5382021-02-03 06:00:55 -0700329
330 def test_struct_scan(self):
331 """Test collection of struct info"""
332 buff = '''
333/* some comment */
334struct some_struct1 {
335 struct i2c_msg *msgs;
336 uint nmsgs;
337};
338'''
339 scan = src_scan.Scanner(None, False, None)
340 scan._basedir = os.path.join(OUR_PATH, '..', '..')
341 scan._parse_structs('arch/arm/include/asm/file.h', buff)
342 self.assertIn('some_struct1', scan._structs)
343 struc = scan._structs['some_struct1']
344 self.assertEqual('some_struct1', struc.name)
345 self.assertEqual('asm/file.h', struc.fname)
346
347 buff = '''
348/* another comment */
349struct another_struct {
350 int speed_hz;
351 int max_transaction_bytes;
352};
353'''
354 scan._parse_structs('include/file2.h', buff)
355 self.assertIn('another_struct', scan._structs)
356 struc = scan._structs['another_struct']
357 self.assertEqual('another_struct', struc.name)
358 self.assertEqual('file2.h', struc.fname)
359
360 self.assertEqual(2, len(scan._structs))
361
362 self.assertEqual("Struct(name='another_struct', fname='file2.h')",
363 str(struc))
364
365 def test_struct_scan_errors(self):
366 """Test scanning a header file with an invalid unicode file"""
367 output = tools.GetOutputFilename('output.h')
368 tools.WriteFile(output, b'struct this is a test \x81 of bad unicode')
369
370 scan = src_scan.Scanner(None, False, None)
371 with test_util.capture_sys_output() as (stdout, _):
372 scan.scan_header(output)
373 self.assertIn('due to unicode error', stdout.getvalue())
Simon Glass9b2eac02021-02-03 06:01:06 -0700374
375 def setup_dup_drivers(self, name, phase=''):
376 """Set up for a duplcate test
377
378 Returns:
379 tuple:
380 Scanner to use
381 Driver record for first driver
382 Text of second driver declaration
383 Node for driver 1
384 """
385 driver1 = '''
386static const struct udevice_id test_ids[] = {
387 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
388 { }
389};
390
391U_BOOT_DRIVER(%s) = {
392 .name = "testing",
393 .id = UCLASS_I2C,
394 .of_match = test_ids,
395 %s
396};
397''' % (name, 'DM_PHASE(%s)' % phase if phase else '')
398 driver2 = '''
399static const struct udevice_id test_ids[] = {
400 { .compatible = "nvidia,tegra114-dvc" },
401 { }
402};
403
404U_BOOT_DRIVER(%s) = {
405 .name = "testing",
406 .id = UCLASS_RAM,
407 .of_match = test_ids,
408};
409''' % name
410 scan = src_scan.Scanner(None, False, None, phase)
411 scan._parse_driver('file1.c', driver1)
412 self.assertIn(name, scan._drivers)
413 drv1 = scan._drivers[name]
414
415 prop = FakeProp()
416 prop.name = 'compatible'
417 prop.value = 'nvidia,tegra114-i2c'
418 node = FakeNode()
419 node.name = 'testing'
420 node.props = {'compatible': prop}
421
422 return scan, drv1, driver2, node
423
424 def test_dup_drivers(self):
425 """Test handling of duplicate drivers"""
426 name = 'nvidia_tegra114_i2c'
427 scan, drv1, driver2, node = self.setup_dup_drivers(name)
428 self.assertEqual('', drv1.phase)
429
430 # The driver should not have a duplicate yet
431 self.assertEqual([], drv1.dups)
432
433 scan._parse_driver('file2.c', driver2)
434
435 # The first driver should now be a duplicate of the second
436 drv2 = scan._drivers[name]
437 self.assertEqual('', drv2.phase)
438 self.assertEqual(1, len(drv2.dups))
439 self.assertEqual([drv1], drv2.dups)
440
441 # There is no way to distinguish them, so we should expect a warning
442 self.assertTrue(drv2.warn_dups)
443
444 # We should see a warning
445 with test_util.capture_sys_output() as (stdout, _):
446 scan.mark_used([node])
447 self.assertEqual(
448 "Warning: Duplicate driver name 'nvidia_tegra114_i2c' (orig=file2.c, dups=file1.c)",
449 stdout.getvalue().strip())
450
451 def test_dup_drivers_phase(self):
452 """Test handling of duplicate drivers but with different phases"""
453 name = 'nvidia_tegra114_i2c'
454 scan, drv1, driver2, node = self.setup_dup_drivers(name, 'spl')
455 scan._parse_driver('file2.c', driver2)
456 self.assertEqual('spl', drv1.phase)
457
458 # The second driver should now be a duplicate of the second
459 self.assertEqual(1, len(drv1.dups))
460 drv2 = drv1.dups[0]
461
462 # The phase is different, so we should not warn of dups
463 self.assertFalse(drv1.warn_dups)
464
465 # We should not see a warning
466 with test_util.capture_sys_output() as (stdout, _):
467 scan.mark_used([node])
468 self.assertEqual('', stdout.getvalue().strip())
Simon Glass80d782c42021-02-03 06:01:10 -0700469
470 def test_sequence(self):
471 """Test assignment of sequence numnbers"""
472 scan = src_scan.Scanner(None, False, None, '')
473 node = FakeNode()
474 uc = src_scan.UclassDriver('UCLASS_I2C')
475 node.uclass = uc
476 node.driver = True
477 node.seq = -1
478 node.path = 'mypath'
479 uc.alias_num_to_node[2] = node
480
481 # This should assign 3 (after the 2 that exists)
482 seq = scan.assign_seq(node)
483 self.assertEqual(3, seq)
484 self.assertEqual({'mypath': 3}, uc.alias_path_to_num)
485 self.assertEqual({2: node, 3: node}, uc.alias_num_to_node)