1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2020 Google LLC
5 """Tests for the src_scan module
7 This includes unit tests for scanning of the source code
15 from unittest import mock
17 from dtoc import src_scan
18 from patman import test_util
19 from patman import tools
21 OUR_PATH = os.path.dirname(os.path.realpath(__file__))
23 EXPECT_WARN = {'rockchip_rk3288_grf':
24 {'WARNING: the driver rockchip_rk3288_grf was not found in the driver list'}}
27 """Fake Node object for testing"""
33 """Fake Prop object for testing"""
38 # This is a test so is allowed to access private things in the module it is
40 # pylint: disable=W0212
42 class TestSrcScan(unittest.TestCase):
43 """Tests for src_scan"""
46 tools.PrepareOutputDir(None)
49 def tearDownClass(cls):
50 tools.FinaliseOutputDir()
52 def test_simple(self):
53 """Simple test of scanning drivers"""
54 scan = src_scan.Scanner(None, None)
56 self.assertIn('sandbox_gpio', scan._drivers)
57 self.assertIn('sandbox_gpio_alias', scan._driver_aliases)
58 self.assertEqual('sandbox_gpio',
59 scan._driver_aliases['sandbox_gpio_alias'])
60 self.assertNotIn('sandbox_gpio_alias2', scan._driver_aliases)
62 def test_additional(self):
63 """Test with additional drivers to scan"""
64 scan = src_scan.Scanner(
65 None, [None, '', 'tools/dtoc/test/dtoc_test_scan_drivers.cxx'])
67 self.assertIn('sandbox_gpio_alias2', scan._driver_aliases)
68 self.assertEqual('sandbox_gpio',
69 scan._driver_aliases['sandbox_gpio_alias2'])
71 def test_unicode_error(self):
72 """Test running dtoc with an invalid unicode file
74 To be able to perform this test without adding a weird text file which
75 would produce issues when using checkpatch.pl or patman, generate the
76 file at runtime and then process it.
78 driver_fn = '/tmp/' + next(tempfile._get_candidate_names())
79 with open(driver_fn, 'wb+') as fout:
82 scan = src_scan.Scanner(None, [driver_fn])
83 with test_util.capture_sys_output() as (stdout, _):
85 self.assertRegex(stdout.getvalue(),
86 r"Skipping file '.*' due to unicode error\s*")
88 def test_driver(self):
89 """Test the Driver class"""
91 compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF',
92 'rockchip,rk3288-srf': None}
93 drv1 = src_scan.Driver('fred', 'fred.c')
94 drv2 = src_scan.Driver('mary', 'mary.c')
95 drv3 = src_scan.Driver('fred', 'fred.c')
103 "Driver(name='fred', used=False, uclass_id='I2C_UCLASS', "
104 "compat={'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', "
105 "'rockchip,rk3288-srf': None}, priv=)", str(drv1))
106 self.assertEqual(drv1, drv3)
107 self.assertNotEqual(drv1, drv2)
108 self.assertNotEqual(drv2, drv3)
110 def test_scan_dirs(self):
111 """Test scanning of source directories"""
113 pathname = os.path.join(indir, fname)
114 dirname = os.path.dirname(pathname)
115 os.makedirs(dirname, exist_ok=True)
116 tools.WriteFile(pathname, '', binary=False)
117 fname_list.append(pathname)
120 indir = tempfile.mkdtemp(prefix='dtoc.')
124 add_file('.git/ignoreme.c')
125 add_file('dir/fname2.c')
126 add_file('build-sandbox/ignoreme2.c')
128 # Mock out scan_driver and check that it is called with the
130 with mock.patch.object(src_scan.Scanner, "scan_driver") as mocked:
131 scan = src_scan.Scanner(indir, None)
133 self.assertEqual(2, len(mocked.mock_calls))
134 self.assertEqual(mock.call(fname_list[0]),
135 mocked.mock_calls[0])
136 # .git file should be ignored
137 self.assertEqual(mock.call(fname_list[2]),
138 mocked.mock_calls[1])
143 """Test scanning of a driver"""
144 fname = os.path.join(OUR_PATH, '..', '..', 'drivers/i2c/tegra_i2c.c')
145 buff = tools.ReadFile(fname, False)
146 scan = src_scan.Scanner(None, None)
147 scan._parse_driver(fname, buff)
148 self.assertIn('i2c_tegra', scan._drivers)
149 drv = scan._drivers['i2c_tegra']
150 self.assertEqual('i2c_tegra', drv.name)
151 self.assertEqual('UCLASS_I2C', drv.uclass_id)
153 {'nvidia,tegra114-i2c': 'TYPE_114',
154 'nvidia,tegra20-i2c': 'TYPE_STD',
155 'nvidia,tegra20-i2c-dvc': 'TYPE_DVC'}, drv.compat)
156 self.assertEqual('i2c_bus', drv.priv)
157 self.assertEqual(1, len(scan._drivers))
158 self.assertEqual({}, scan._warnings)
160 def test_normalized_name(self):
161 """Test operation of get_normalized_compat_name()"""
163 prop.name = 'compatible'
164 prop.value = 'rockchip,rk3288-grf'
166 node.props = {'compatible': prop}
168 # get_normalized_compat_name() uses this to check for root node
169 node.parent = FakeNode()
171 scan = src_scan.Scanner(None, None)
172 with test_util.capture_sys_output() as (stdout, _):
173 name, aliases = scan.get_normalized_compat_name(node)
174 self.assertEqual('rockchip_rk3288_grf', name)
175 self.assertEqual([], aliases)
176 self.assertEqual(1, len(scan._missing_drivers))
177 self.assertEqual({'rockchip_rk3288_grf'}, scan._missing_drivers)
178 self.assertEqual('', stdout.getvalue().strip())
179 self.assertEqual(EXPECT_WARN, scan._warnings)
182 compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF',
183 'rockchip,rk3288-srf': None}
184 drv = src_scan.Driver('fred', 'fred.c')
187 scan._drivers['rockchip_rk3288_grf'] = drv
189 scan._driver_aliases['rockchip_rk3288_srf'] = 'rockchip_rk3288_grf'
191 with test_util.capture_sys_output() as (stdout, _):
192 name, aliases = scan.get_normalized_compat_name(node)
193 self.assertEqual('', stdout.getvalue().strip())
194 self.assertEqual('rockchip_rk3288_grf', name)
195 self.assertEqual([], aliases)
196 self.assertEqual(EXPECT_WARN, scan._warnings)
198 prop.value = 'rockchip,rk3288-srf'
199 with test_util.capture_sys_output() as (stdout, _):
200 name, aliases = scan.get_normalized_compat_name(node)
201 self.assertEqual('', stdout.getvalue().strip())
202 self.assertEqual('rockchip_rk3288_grf', name)
203 self.assertEqual(['rockchip_rk3288_srf'], aliases)
204 self.assertEqual(EXPECT_WARN, scan._warnings)
206 def test_scan_errors(self):
207 """Test detection of scanning errors"""
209 static const struct udevice_id tegra_i2c_ids2[] = {
210 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
214 U_BOOT_DRIVER(i2c_tegra) = {
217 .of_match = tegra_i2c_ids,
220 scan = src_scan.Scanner(None, None)
221 with self.assertRaises(ValueError) as exc:
222 scan._parse_driver('file.c', buff)
224 "file.c: Unknown compatible var 'tegra_i2c_ids' (found: tegra_i2c_ids2)",
227 def test_of_match(self):
228 """Test detection of of_match_ptr() member"""
230 static const struct udevice_id tegra_i2c_ids[] = {
231 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
235 U_BOOT_DRIVER(i2c_tegra) = {
238 .of_match = of_match_ptr(tegra_i2c_ids),
241 scan = src_scan.Scanner(None, None)
242 scan._parse_driver('file.c', buff)
243 self.assertIn('i2c_tegra', scan._drivers)
244 drv = scan._drivers['i2c_tegra']
245 self.assertEqual('i2c_tegra', drv.name)
246 self.assertEqual('', drv.phase)
247 self.assertEqual([], drv.headers)
250 """Test collection of struct info from drivers"""
252 static const struct udevice_id test_ids[] = {
253 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
257 U_BOOT_DRIVER(testing) = {
260 .of_match = test_ids,
261 .priv_auto = sizeof(struct some_priv),
262 .plat_auto = sizeof(struct some_plat),
263 .per_child_auto = sizeof(struct some_cpriv),
264 .per_child_plat_auto = sizeof(struct some_cplat),
267 DM_HEADER(<asm/clk.h>)
270 scan = src_scan.Scanner(None, None)
271 scan._parse_driver('file.c', buff)
272 self.assertIn('testing', scan._drivers)
273 drv = scan._drivers['testing']
274 self.assertEqual('testing', drv.name)
275 self.assertEqual('UCLASS_I2C', drv.uclass_id)
277 {'nvidia,tegra114-i2c': 'TYPE_114'}, drv.compat)
278 self.assertEqual('some_priv', drv.priv)
279 self.assertEqual('some_plat', drv.plat)
280 self.assertEqual('some_cpriv', drv.child_priv)
281 self.assertEqual('some_cplat', drv.child_plat)
282 self.assertEqual('tpl', drv.phase)
283 self.assertEqual(['<i2c.h>', '<asm/clk.h>'], drv.headers)
284 self.assertEqual(1, len(scan._drivers))
286 def test_uclass_scan(self):
287 """Test collection of uclass-driver info"""
289 UCLASS_DRIVER(i2c) = {
292 .flags = DM_UC_FLAG_SEQ_ALIAS,
293 .priv_auto = sizeof(struct some_priv),
294 .per_device_auto = sizeof(struct per_dev_priv),
295 .per_device_plat_auto = sizeof(struct per_dev_plat),
296 .per_child_auto = sizeof(struct per_child_priv),
297 .per_child_plat_auto = sizeof(struct per_child_plat),
298 .child_post_bind = i2c_child_post_bind,
302 scan = src_scan.Scanner(None, None)
303 scan._parse_uclass_driver('file.c', buff)
304 self.assertIn('UCLASS_I2C', scan._uclass)
305 drv = scan._uclass['UCLASS_I2C']
306 self.assertEqual('i2c', drv.name)
307 self.assertEqual('UCLASS_I2C', drv.uclass_id)
308 self.assertEqual('some_priv', drv.priv)
309 self.assertEqual('per_dev_priv', drv.per_dev_priv)
310 self.assertEqual('per_dev_plat', drv.per_dev_plat)
311 self.assertEqual('per_child_priv', drv.per_child_priv)
312 self.assertEqual('per_child_plat', drv.per_child_plat)
313 self.assertEqual(1, len(scan._uclass))
315 drv2 = copy.deepcopy(drv)
316 self.assertEqual(drv, drv2)
317 drv2.priv = 'other_priv'
318 self.assertNotEqual(drv, drv2)
320 # The hashes only depend on the uclass ID, so should be equal
321 self.assertEqual(drv.__hash__(), drv2.__hash__())
323 self.assertEqual("UclassDriver(name='i2c', uclass_id='UCLASS_I2C')",
326 def test_uclass_scan_errors(self):
327 """Test detection of uclass scanning errors"""
329 UCLASS_DRIVER(i2c) = {
334 scan = src_scan.Scanner(None, None)
335 with self.assertRaises(ValueError) as exc:
336 scan._parse_uclass_driver('file.c', buff)
337 self.assertIn("file.c: Cannot parse uclass ID in driver 'i2c'",
340 def test_struct_scan(self):
341 """Test collection of struct info"""
344 struct some_struct1 {
345 struct i2c_msg *msgs;
349 scan = src_scan.Scanner(None, None)
350 scan._basedir = os.path.join(OUR_PATH, '..', '..')
351 scan._parse_structs('arch/arm/include/asm/file.h', buff)
352 self.assertIn('some_struct1', scan._structs)
353 struc = scan._structs['some_struct1']
354 self.assertEqual('some_struct1', struc.name)
355 self.assertEqual('asm/file.h', struc.fname)
358 /* another comment */
359 struct another_struct {
361 int max_transaction_bytes;
364 scan._parse_structs('include/file2.h', buff)
365 self.assertIn('another_struct', scan._structs)
366 struc = scan._structs['another_struct']
367 self.assertEqual('another_struct', struc.name)
368 self.assertEqual('file2.h', struc.fname)
370 self.assertEqual(2, len(scan._structs))
372 self.assertEqual("Struct(name='another_struct', fname='file2.h')",
375 def test_struct_scan_errors(self):
376 """Test scanning a header file with an invalid unicode file"""
377 output = tools.GetOutputFilename('output.h')
378 tools.WriteFile(output, b'struct this is a test \x81 of bad unicode')
380 scan = src_scan.Scanner(None, None)
381 with test_util.capture_sys_output() as (stdout, _):
382 scan.scan_header(output)
383 self.assertIn('due to unicode error', stdout.getvalue())
385 def setup_dup_drivers(self, name, phase=''):
386 """Set up for a duplcate test
391 Driver record for first driver
392 Text of second driver declaration
396 static const struct udevice_id test_ids[] = {
397 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
401 U_BOOT_DRIVER(%s) = {
404 .of_match = test_ids,
407 ''' % (name, 'DM_PHASE(%s)' % phase if phase else '')
409 static const struct udevice_id test_ids[] = {
410 { .compatible = "nvidia,tegra114-dvc" },
414 U_BOOT_DRIVER(%s) = {
417 .of_match = test_ids,
420 scan = src_scan.Scanner(None, None, phase)
421 scan._parse_driver('file1.c', driver1)
422 self.assertIn(name, scan._drivers)
423 drv1 = scan._drivers[name]
426 prop.name = 'compatible'
427 prop.value = 'nvidia,tegra114-i2c'
429 node.name = 'testing'
430 node.props = {'compatible': prop}
432 # get_normalized_compat_name() uses this to check for root node
433 node.parent = FakeNode()
435 return scan, drv1, driver2, node
437 def test_dup_drivers(self):
438 """Test handling of duplicate drivers"""
439 name = 'nvidia_tegra114_i2c'
440 scan, drv1, driver2, node = self.setup_dup_drivers(name)
441 self.assertEqual('', drv1.phase)
443 # The driver should not have a duplicate yet
444 self.assertEqual([], drv1.dups)
446 scan._parse_driver('file2.c', driver2)
448 # The first driver should now be a duplicate of the second
449 drv2 = scan._drivers[name]
450 self.assertEqual('', drv2.phase)
451 self.assertEqual(1, len(drv2.dups))
452 self.assertEqual([drv1], drv2.dups)
454 # There is no way to distinguish them, so we should expect a warning
455 self.assertTrue(drv2.warn_dups)
457 # We should see a warning
458 with test_util.capture_sys_output() as (stdout, _):
459 scan.mark_used([node])
461 "Warning: Duplicate driver name 'nvidia_tegra114_i2c' (orig=file2.c, dups=file1.c)",
462 stdout.getvalue().strip())
464 def test_dup_drivers_phase(self):
465 """Test handling of duplicate drivers but with different phases"""
466 name = 'nvidia_tegra114_i2c'
467 scan, drv1, driver2, node = self.setup_dup_drivers(name, 'spl')
468 scan._parse_driver('file2.c', driver2)
469 self.assertEqual('spl', drv1.phase)
471 # The second driver should now be a duplicate of the second
472 self.assertEqual(1, len(drv1.dups))
475 # The phase is different, so we should not warn of dups
476 self.assertFalse(drv1.warn_dups)
478 # We should not see a warning
479 with test_util.capture_sys_output() as (stdout, _):
480 scan.mark_used([node])
481 self.assertEqual('', stdout.getvalue().strip())
483 def test_sequence(self):
484 """Test assignment of sequence numnbers"""
485 scan = src_scan.Scanner(None, None, '')
487 uc = src_scan.UclassDriver('UCLASS_I2C')
492 uc.alias_num_to_node[2] = node
494 # This should assign 3 (after the 2 that exists)
495 seq = scan.assign_seq(node)
496 self.assertEqual(3, seq)
497 self.assertEqual({'mypath': 3}, uc.alias_path_to_num)
498 self.assertEqual({2: node, 3: node}, uc.alias_num_to_node)
500 def test_scan_warnings(self):
501 """Test detection of scanning warnings"""
503 static const struct udevice_id tegra_i2c_ids[] = {
504 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
508 U_BOOT_DRIVER(i2c_tegra) = {
511 .of_match = tegra_i2c_ids + 1,
514 # The '+ 1' above should generate a warning
517 prop.name = 'compatible'
518 prop.value = 'rockchip,rk3288-grf'
520 node.props = {'compatible': prop}
522 # get_normalized_compat_name() uses this to check for root node
523 node.parent = FakeNode()
525 scan = src_scan.Scanner(None, None)
526 scan._parse_driver('file.c', buff)
529 {"file.c: Warning: unexpected suffix ' + 1' on .of_match line for compat 'tegra_i2c_ids'"}},
533 tprop.name = 'compatible'
534 tprop.value = 'nvidia,tegra114-i2c'
536 tnode.props = {'compatible': tprop}
538 # get_normalized_compat_name() uses this to check for root node
539 tnode.parent = FakeNode()
541 with test_util.capture_sys_output() as (stdout, _):
542 scan.get_normalized_compat_name(node)
543 scan.get_normalized_compat_name(tnode)
544 self.assertEqual('', stdout.getvalue().strip())
546 self.assertEqual(2, len(scan._missing_drivers))
547 self.assertEqual({'rockchip_rk3288_grf', 'nvidia_tegra114_i2c'},
548 scan._missing_drivers)
549 with test_util.capture_sys_output() as (stdout, _):
551 self.assertIn('rockchip_rk3288_grf', stdout.getvalue())
553 # This should show just the rockchip warning, since the tegra driver
554 # is not in self._missing_drivers
555 scan._missing_drivers.remove('nvidia_tegra114_i2c')
556 with test_util.capture_sys_output() as (stdout, _):
558 self.assertIn('rockchip_rk3288_grf', stdout.getvalue())
559 self.assertNotIn('tegra_i2c_ids', stdout.getvalue())
561 # Do a similar thing with used drivers. By marking the tegra driver as
562 # used, the warning related to that driver will be shown
563 drv = scan._drivers['i2c_tegra']
565 with test_util.capture_sys_output() as (stdout, _):
567 self.assertIn('rockchip_rk3288_grf', stdout.getvalue())
568 self.assertIn('tegra_i2c_ids', stdout.getvalue())
570 # Add a warning to make sure multiple warnings are shown
571 scan._warnings['i2c_tegra'].update(
572 scan._warnings['nvidia_tegra114_i2c'])
573 del scan._warnings['nvidia_tegra114_i2c']
574 with test_util.capture_sys_output() as (stdout, _):
576 self.assertEqual('''i2c_tegra: WARNING: the driver nvidia_tegra114_i2c was not found in the driver list
577 : file.c: Warning: unexpected suffix ' + 1' on .of_match line for compat 'tegra_i2c_ids'
579 rockchip_rk3288_grf: WARNING: the driver rockchip_rk3288_grf was not found in the driver list
583 self.assertIn('tegra_i2c_ids', stdout.getvalue())
585 def scan_uclass_warning(self):
586 """Test a missing .uclass in the driver"""
588 static const struct udevice_id tegra_i2c_ids[] = {
589 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
593 U_BOOT_DRIVER(i2c_tegra) = {
595 .of_match = tegra_i2c_ids,
598 scan = src_scan.Scanner(None, None)
599 scan._parse_driver('file.c', buff)
601 {'i2c_tegra': {'Missing .uclass in file.c'}},
604 def scan_compat_warning(self):
605 """Test a missing .compatible in the driver"""
607 static const struct udevice_id tegra_i2c_ids[] = {
608 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 },
612 U_BOOT_DRIVER(i2c_tegra) = {
617 scan = src_scan.Scanner(None, None)
618 scan._parse_driver('file.c', buff)
620 {'i2c_tegra': {'Missing .compatible in file.c'}},