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