1# 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 10import copy 11import os 12import shutil 13import tempfile 14import unittest 15from unittest import mock 16 17from dtoc import src_scan 18from u_boot_pylib import test_util 19from u_boot_pylib import tools 20 21OUR_PATH = os.path.dirname(os.path.realpath(__file__)) 22 23EXPECT_WARN = {'rockchip_rk3288_grf': 24 {'WARNING: the driver rockchip_rk3288_grf was not found in the driver list'}} 25 26class FakeNode: 27 """Fake Node object for testing""" 28 def __init__(self): 29 self.name = None 30 self.props = {} 31 32class FakeProp: 33 """Fake Prop object for testing""" 34 def __init__(self): 35 self.name = None 36 self.value = None 37 38# This is a test so is allowed to access private things in the module it is 39# testing 40# pylint: disable=W0212 41 42class TestSrcScan(unittest.TestCase): 43 """Tests for src_scan""" 44 @classmethod 45 def setUpClass(cls): 46 tools.prepare_output_dir(None) 47 48 @classmethod 49 def tearDownClass(cls): 50 tools.finalise_output_dir() 51 52 def test_simple(self): 53 """Simple test of scanning drivers""" 54 scan = src_scan.Scanner(None, None) 55 scan.scan_drivers() 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) 61 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']) 66 scan.scan_drivers() 67 self.assertIn('sandbox_gpio_alias2', scan._driver_aliases) 68 self.assertEqual('sandbox_gpio', 69 scan._driver_aliases['sandbox_gpio_alias2']) 70 71 def test_unicode_error(self): 72 """Test running dtoc with an invalid unicode file 73 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. 77 """ 78 driver_fn = '/tmp/' + next(tempfile._get_candidate_names()) 79 with open(driver_fn, 'wb+') as fout: 80 fout.write(b'\x81') 81 82 scan = src_scan.Scanner(None, [driver_fn]) 83 with test_util.capture_sys_output() as (stdout, _): 84 scan.scan_drivers() 85 self.assertRegex(stdout.getvalue(), 86 r"Skipping file '.*' due to unicode error\s*") 87 88 def test_driver(self): 89 """Test the Driver class""" 90 i2c = 'I2C_UCLASS' 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') 96 drv1.uclass_id = i2c 97 drv1.compat = compat 98 drv2.uclass_id = i2c 99 drv2.compat = compat 100 drv3.uclass_id = i2c 101 drv3.compat = compat 102 self.assertEqual( 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) 109 110 def test_scan_dirs(self): 111 """Test scanning of source directories""" 112 def add_file(fname): 113 pathname = os.path.join(indir, fname) 114 dirname = os.path.dirname(pathname) 115 os.makedirs(dirname, exist_ok=True) 116 tools.write_file(pathname, '', binary=False) 117 fname_list.append(pathname) 118 119 try: 120 indir = tempfile.mkdtemp(prefix='dtoc.') 121 122 fname_list = [] 123 add_file('fname.c') 124 add_file('.git/ignoreme.c') 125 add_file('dir/fname2.c') 126 add_file('build-sandbox/ignoreme2.c') 127 128 # Mock out scan_driver and check that it is called with the 129 # expected files 130 with mock.patch.object(src_scan.Scanner, "scan_driver") as mocked: 131 scan = src_scan.Scanner(indir, None) 132 scan.scan_drivers() 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]) 139 finally: 140 shutil.rmtree(indir) 141 142 def test_scan(self): 143 """Test scanning of a driver""" 144 fname = os.path.join(OUR_PATH, '..', '..', 'drivers/i2c/tegra_i2c.c') 145 buff = tools.read_file(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) 152 self.assertEqual( 153 {'nvidia,tegra114-i2c': 'TYPE_114', 154 'nvidia,tegra124-i2c': 'TYPE_114', 155 'nvidia,tegra20-i2c': 'TYPE_STD', 156 'nvidia,tegra20-i2c-dvc': 'TYPE_DVC'}, drv.compat) 157 self.assertEqual('i2c_bus', drv.priv) 158 self.assertEqual(1, len(scan._drivers)) 159 self.assertEqual({}, scan._warnings) 160 161 def test_normalized_name(self): 162 """Test operation of get_normalized_compat_name()""" 163 prop = FakeProp() 164 prop.name = 'compatible' 165 prop.value = 'rockchip,rk3288-grf' 166 node = FakeNode() 167 node.props = {'compatible': prop} 168 169 # get_normalized_compat_name() uses this to check for root node 170 node.parent = FakeNode() 171 172 scan = src_scan.Scanner(None, None) 173 with test_util.capture_sys_output() as (stdout, _): 174 name, aliases = scan.get_normalized_compat_name(node) 175 self.assertEqual('rockchip_rk3288_grf', name) 176 self.assertEqual([], aliases) 177 self.assertEqual(1, len(scan._missing_drivers)) 178 self.assertEqual({'rockchip_rk3288_grf'}, scan._missing_drivers) 179 self.assertEqual('', stdout.getvalue().strip()) 180 self.assertEqual(EXPECT_WARN, scan._warnings) 181 182 i2c = 'I2C_UCLASS' 183 compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', 184 'rockchip,rk3288-srf': None} 185 drv = src_scan.Driver('fred', 'fred.c') 186 drv.uclass_id = i2c 187 drv.compat = compat 188 scan._drivers['rockchip_rk3288_grf'] = drv 189 190 scan._driver_aliases['rockchip_rk3288_srf'] = 'rockchip_rk3288_grf' 191 192 with test_util.capture_sys_output() as (stdout, _): 193 name, aliases = scan.get_normalized_compat_name(node) 194 self.assertEqual('', stdout.getvalue().strip()) 195 self.assertEqual('rockchip_rk3288_grf', name) 196 self.assertEqual([], aliases) 197 self.assertEqual(EXPECT_WARN, scan._warnings) 198 199 prop.value = 'rockchip,rk3288-srf' 200 with test_util.capture_sys_output() as (stdout, _): 201 name, aliases = scan.get_normalized_compat_name(node) 202 self.assertEqual('', stdout.getvalue().strip()) 203 self.assertEqual('rockchip_rk3288_grf', name) 204 self.assertEqual(['rockchip_rk3288_srf'], aliases) 205 self.assertEqual(EXPECT_WARN, scan._warnings) 206 207 def test_scan_errors(self): 208 """Test detection of scanning errors""" 209 buff = ''' 210static const struct udevice_id tegra_i2c_ids2[] = { 211 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 212 { } 213}; 214 215U_BOOT_DRIVER(i2c_tegra) = { 216 .name = "i2c_tegra", 217 .id = UCLASS_I2C, 218 .of_match = tegra_i2c_ids, 219}; 220''' 221 scan = src_scan.Scanner(None, None) 222 with self.assertRaises(ValueError) as exc: 223 scan._parse_driver('file.c', buff) 224 self.assertIn( 225 "file.c: Unknown compatible var 'tegra_i2c_ids' (found: tegra_i2c_ids2)", 226 str(exc.exception)) 227 228 def test_of_match(self): 229 """Test detection of of_match_ptr() member""" 230 buff = ''' 231static const struct udevice_id tegra_i2c_ids[] = { 232 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 233 { } 234}; 235 236U_BOOT_DRIVER(i2c_tegra) = { 237 .name = "i2c_tegra", 238 .id = UCLASS_I2C, 239 .of_match = of_match_ptr(tegra_i2c_ids), 240}; 241''' 242 scan = src_scan.Scanner(None, None) 243 scan._parse_driver('file.c', buff) 244 self.assertIn('i2c_tegra', scan._drivers) 245 drv = scan._drivers['i2c_tegra'] 246 self.assertEqual('i2c_tegra', drv.name) 247 self.assertEqual('', drv.phase) 248 self.assertEqual([], drv.headers) 249 250 def test_priv(self): 251 """Test collection of struct info from drivers""" 252 buff = ''' 253static const struct udevice_id test_ids[] = { 254 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 255 { } 256}; 257 258U_BOOT_DRIVER(testing) = { 259 .name = "testing", 260 .id = UCLASS_I2C, 261 .of_match = test_ids, 262 .priv_auto = sizeof(struct some_priv), 263 .plat_auto = sizeof(struct some_plat), 264 .per_child_auto = sizeof(struct some_cpriv), 265 .per_child_plat_auto = sizeof(struct some_cplat), 266 DM_PHASE(tpl) 267 DM_HEADER(<i2c.h>) 268 DM_HEADER(<asm/clk.h>) 269}; 270''' 271 scan = src_scan.Scanner(None, None) 272 scan._parse_driver('file.c', buff) 273 self.assertIn('testing', scan._drivers) 274 drv = scan._drivers['testing'] 275 self.assertEqual('testing', drv.name) 276 self.assertEqual('UCLASS_I2C', drv.uclass_id) 277 self.assertEqual( 278 {'nvidia,tegra114-i2c': 'TYPE_114'}, drv.compat) 279 self.assertEqual('some_priv', drv.priv) 280 self.assertEqual('some_plat', drv.plat) 281 self.assertEqual('some_cpriv', drv.child_priv) 282 self.assertEqual('some_cplat', drv.child_plat) 283 self.assertEqual('tpl', drv.phase) 284 self.assertEqual(['<i2c.h>', '<asm/clk.h>'], drv.headers) 285 self.assertEqual(1, len(scan._drivers)) 286 287 def test_uclass_scan(self): 288 """Test collection of uclass-driver info""" 289 buff = ''' 290UCLASS_DRIVER(i2c) = { 291 .id = UCLASS_I2C, 292 .name = "i2c", 293 .flags = DM_UC_FLAG_SEQ_ALIAS, 294 .priv_auto = sizeof(struct some_priv), 295 .per_device_auto = sizeof(struct per_dev_priv), 296 .per_device_plat_auto = sizeof(struct per_dev_plat), 297 .per_child_auto = sizeof(struct per_child_priv), 298 .per_child_plat_auto = sizeof(struct per_child_plat), 299 .child_post_bind = i2c_child_post_bind, 300}; 301 302''' 303 scan = src_scan.Scanner(None, None) 304 scan._parse_uclass_driver('file.c', buff) 305 self.assertIn('UCLASS_I2C', scan._uclass) 306 drv = scan._uclass['UCLASS_I2C'] 307 self.assertEqual('i2c', drv.name) 308 self.assertEqual('UCLASS_I2C', drv.uclass_id) 309 self.assertEqual('some_priv', drv.priv) 310 self.assertEqual('per_dev_priv', drv.per_dev_priv) 311 self.assertEqual('per_dev_plat', drv.per_dev_plat) 312 self.assertEqual('per_child_priv', drv.per_child_priv) 313 self.assertEqual('per_child_plat', drv.per_child_plat) 314 self.assertEqual(1, len(scan._uclass)) 315 316 drv2 = copy.deepcopy(drv) 317 self.assertEqual(drv, drv2) 318 drv2.priv = 'other_priv' 319 self.assertNotEqual(drv, drv2) 320 321 # The hashes only depend on the uclass ID, so should be equal 322 self.assertEqual(drv.__hash__(), drv2.__hash__()) 323 324 self.assertEqual("UclassDriver(name='i2c', uclass_id='UCLASS_I2C')", 325 str(drv)) 326 327 def test_uclass_scan_errors(self): 328 """Test detection of uclass scanning errors""" 329 buff = ''' 330UCLASS_DRIVER(i2c) = { 331 .name = "i2c", 332}; 333 334''' 335 scan = src_scan.Scanner(None, None) 336 with self.assertRaises(ValueError) as exc: 337 scan._parse_uclass_driver('file.c', buff) 338 self.assertIn("file.c: Cannot parse uclass ID in driver 'i2c'", 339 str(exc.exception)) 340 341 def test_struct_scan(self): 342 """Test collection of struct info""" 343 buff = ''' 344/* some comment */ 345struct some_struct1 { 346 struct i2c_msg *msgs; 347 uint nmsgs; 348}; 349''' 350 scan = src_scan.Scanner(None, None) 351 scan._basedir = os.path.join(OUR_PATH, '..', '..') 352 scan._parse_structs('arch/arm/include/asm/file.h', buff) 353 self.assertIn('some_struct1', scan._structs) 354 struc = scan._structs['some_struct1'] 355 self.assertEqual('some_struct1', struc.name) 356 self.assertEqual('asm/file.h', struc.fname) 357 358 buff = ''' 359/* another comment */ 360struct another_struct { 361 int speed_hz; 362 int max_transaction_bytes; 363}; 364''' 365 scan._parse_structs('include/file2.h', buff) 366 self.assertIn('another_struct', scan._structs) 367 struc = scan._structs['another_struct'] 368 self.assertEqual('another_struct', struc.name) 369 self.assertEqual('file2.h', struc.fname) 370 371 self.assertEqual(2, len(scan._structs)) 372 373 self.assertEqual("Struct(name='another_struct', fname='file2.h')", 374 str(struc)) 375 376 def test_struct_scan_errors(self): 377 """Test scanning a header file with an invalid unicode file""" 378 output = tools.get_output_filename('output.h') 379 tools.write_file(output, b'struct this is a test \x81 of bad unicode') 380 381 scan = src_scan.Scanner(None, None) 382 with test_util.capture_sys_output() as (stdout, _): 383 scan.scan_header(output) 384 self.assertIn('due to unicode error', stdout.getvalue()) 385 386 def setup_dup_drivers(self, name, phase=''): 387 """Set up for a duplcate test 388 389 Returns: 390 tuple: 391 Scanner to use 392 Driver record for first driver 393 Text of second driver declaration 394 Node for driver 1 395 """ 396 driver1 = ''' 397static const struct udevice_id test_ids[] = { 398 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 399 { } 400}; 401 402U_BOOT_DRIVER(%s) = { 403 .name = "testing", 404 .id = UCLASS_I2C, 405 .of_match = test_ids, 406 %s 407}; 408''' % (name, 'DM_PHASE(%s)' % phase if phase else '') 409 driver2 = ''' 410static const struct udevice_id test_ids[] = { 411 { .compatible = "nvidia,tegra114-dvc" }, 412 { } 413}; 414 415U_BOOT_DRIVER(%s) = { 416 .name = "testing", 417 .id = UCLASS_RAM, 418 .of_match = test_ids, 419}; 420''' % name 421 scan = src_scan.Scanner(None, None, phase) 422 scan._parse_driver('file1.c', driver1) 423 self.assertIn(name, scan._drivers) 424 drv1 = scan._drivers[name] 425 426 prop = FakeProp() 427 prop.name = 'compatible' 428 prop.value = 'nvidia,tegra114-i2c' 429 node = FakeNode() 430 node.name = 'testing' 431 node.props = {'compatible': prop} 432 433 # get_normalized_compat_name() uses this to check for root node 434 node.parent = FakeNode() 435 436 return scan, drv1, driver2, node 437 438 def test_dup_drivers(self): 439 """Test handling of duplicate drivers""" 440 name = 'nvidia_tegra114_i2c' 441 scan, drv1, driver2, node = self.setup_dup_drivers(name) 442 self.assertEqual('', drv1.phase) 443 444 # The driver should not have a duplicate yet 445 self.assertEqual([], drv1.dups) 446 447 scan._parse_driver('file2.c', driver2) 448 449 # The first driver should now be a duplicate of the second 450 drv2 = scan._drivers[name] 451 self.assertEqual('', drv2.phase) 452 self.assertEqual(1, len(drv2.dups)) 453 self.assertEqual([drv1], drv2.dups) 454 455 # There is no way to distinguish them, so we should expect a warning 456 self.assertTrue(drv2.warn_dups) 457 458 # We should see a warning 459 with test_util.capture_sys_output() as (stdout, _): 460 scan.mark_used([node]) 461 self.assertEqual( 462 "Warning: Duplicate driver name 'nvidia_tegra114_i2c' (orig=file2.c, dups=file1.c)", 463 stdout.getvalue().strip()) 464 465 def test_dup_drivers_phase(self): 466 """Test handling of duplicate drivers but with different phases""" 467 name = 'nvidia_tegra114_i2c' 468 scan, drv1, driver2, node = self.setup_dup_drivers(name, 'spl') 469 scan._parse_driver('file2.c', driver2) 470 self.assertEqual('spl', drv1.phase) 471 472 # The second driver should now be a duplicate of the second 473 self.assertEqual(1, len(drv1.dups)) 474 drv2 = drv1.dups[0] 475 476 # The phase is different, so we should not warn of dups 477 self.assertFalse(drv1.warn_dups) 478 479 # We should not see a warning 480 with test_util.capture_sys_output() as (stdout, _): 481 scan.mark_used([node]) 482 self.assertEqual('', stdout.getvalue().strip()) 483 484 def test_sequence(self): 485 """Test assignment of sequence numnbers""" 486 scan = src_scan.Scanner(None, None, '') 487 node = FakeNode() 488 uc = src_scan.UclassDriver('UCLASS_I2C') 489 node.uclass = uc 490 node.driver = True 491 node.seq = -1 492 node.path = 'mypath' 493 uc.alias_num_to_node[2] = node 494 495 # This should assign 3 (after the 2 that exists) 496 seq = scan.assign_seq(node) 497 self.assertEqual(3, seq) 498 self.assertEqual({'mypath': 3}, uc.alias_path_to_num) 499 self.assertEqual({2: node, 3: node}, uc.alias_num_to_node) 500 501 def test_scan_warnings(self): 502 """Test detection of scanning warnings""" 503 buff = ''' 504static const struct udevice_id tegra_i2c_ids[] = { 505 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 506 { } 507}; 508 509U_BOOT_DRIVER(i2c_tegra) = { 510 .name = "i2c_tegra", 511 .id = UCLASS_I2C, 512 .of_match = tegra_i2c_ids + 1, 513}; 514''' 515 # The '+ 1' above should generate a warning 516 517 prop = FakeProp() 518 prop.name = 'compatible' 519 prop.value = 'rockchip,rk3288-grf' 520 node = FakeNode() 521 node.props = {'compatible': prop} 522 523 # get_normalized_compat_name() uses this to check for root node 524 node.parent = FakeNode() 525 526 scan = src_scan.Scanner(None, None) 527 scan._parse_driver('file.c', buff) 528 self.assertEqual( 529 {'i2c_tegra': 530 {"file.c: Warning: unexpected suffix ' + 1' on .of_match line for compat 'tegra_i2c_ids'"}}, 531 scan._warnings) 532 533 tprop = FakeProp() 534 tprop.name = 'compatible' 535 tprop.value = 'nvidia,tegra114-i2c' 536 tnode = FakeNode() 537 tnode.props = {'compatible': tprop} 538 539 # get_normalized_compat_name() uses this to check for root node 540 tnode.parent = FakeNode() 541 542 with test_util.capture_sys_output() as (stdout, _): 543 scan.get_normalized_compat_name(node) 544 scan.get_normalized_compat_name(tnode) 545 self.assertEqual('', stdout.getvalue().strip()) 546 547 self.assertEqual(2, len(scan._missing_drivers)) 548 self.assertEqual({'rockchip_rk3288_grf', 'nvidia_tegra114_i2c'}, 549 scan._missing_drivers) 550 with test_util.capture_sys_output() as (stdout, _): 551 scan.show_warnings() 552 self.assertIn('rockchip_rk3288_grf', stdout.getvalue()) 553 554 # This should show just the rockchip warning, since the tegra driver 555 # is not in self._missing_drivers 556 scan._missing_drivers.remove('nvidia_tegra114_i2c') 557 with test_util.capture_sys_output() as (stdout, _): 558 scan.show_warnings() 559 self.assertIn('rockchip_rk3288_grf', stdout.getvalue()) 560 self.assertNotIn('tegra_i2c_ids', stdout.getvalue()) 561 562 # Do a similar thing with used drivers. By marking the tegra driver as 563 # used, the warning related to that driver will be shown 564 drv = scan._drivers['i2c_tegra'] 565 drv.used = True 566 with test_util.capture_sys_output() as (stdout, _): 567 scan.show_warnings() 568 self.assertIn('rockchip_rk3288_grf', stdout.getvalue()) 569 self.assertIn('tegra_i2c_ids', stdout.getvalue()) 570 571 # Add a warning to make sure multiple warnings are shown 572 scan._warnings['i2c_tegra'].update( 573 scan._warnings['nvidia_tegra114_i2c']) 574 del scan._warnings['nvidia_tegra114_i2c'] 575 with test_util.capture_sys_output() as (stdout, _): 576 scan.show_warnings() 577 self.assertEqual('''i2c_tegra: WARNING: the driver nvidia_tegra114_i2c was not found in the driver list 578 : file.c: Warning: unexpected suffix ' + 1' on .of_match line for compat 'tegra_i2c_ids' 579 580rockchip_rk3288_grf: WARNING: the driver rockchip_rk3288_grf was not found in the driver list 581 582''', 583 stdout.getvalue()) 584 self.assertIn('tegra_i2c_ids', stdout.getvalue()) 585 586 def scan_uclass_warning(self): 587 """Test a missing .uclass in the driver""" 588 buff = ''' 589static const struct udevice_id tegra_i2c_ids[] = { 590 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 591 { } 592}; 593 594U_BOOT_DRIVER(i2c_tegra) = { 595 .name = "i2c_tegra", 596 .of_match = tegra_i2c_ids, 597}; 598''' 599 scan = src_scan.Scanner(None, None) 600 scan._parse_driver('file.c', buff) 601 self.assertEqual( 602 {'i2c_tegra': {'Missing .uclass in file.c'}}, 603 scan._warnings) 604 605 def scan_compat_warning(self): 606 """Test a missing .compatible in the driver""" 607 buff = ''' 608static const struct udevice_id tegra_i2c_ids[] = { 609 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 610 { } 611}; 612 613U_BOOT_DRIVER(i2c_tegra) = { 614 .name = "i2c_tegra", 615 .id = UCLASS_I2C, 616}; 617''' 618 scan = src_scan.Scanner(None, None) 619 scan._parse_driver('file.c', buff) 620 self.assertEqual( 621 {'i2c_tegra': {'Missing .compatible in file.c'}}, 622 scan._warnings) 623