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 patman import test_util 19from patman 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.PrepareOutputDir(None) 47 48 @classmethod 49 def tearDownClass(cls): 50 tools.FinaliseOutputDir() 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.WriteFile(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.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) 152 self.assertEqual( 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) 159 160 def test_normalized_name(self): 161 """Test operation of get_normalized_compat_name()""" 162 prop = FakeProp() 163 prop.name = 'compatible' 164 prop.value = 'rockchip,rk3288-grf' 165 node = FakeNode() 166 node.props = {'compatible': prop} 167 168 # get_normalized_compat_name() uses this to check for root node 169 node.parent = FakeNode() 170 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) 180 181 i2c = 'I2C_UCLASS' 182 compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', 183 'rockchip,rk3288-srf': None} 184 drv = src_scan.Driver('fred', 'fred.c') 185 drv.uclass_id = i2c 186 drv.compat = compat 187 scan._drivers['rockchip_rk3288_grf'] = drv 188 189 scan._driver_aliases['rockchip_rk3288_srf'] = 'rockchip_rk3288_grf' 190 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) 197 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) 205 206 def test_scan_errors(self): 207 """Test detection of scanning errors""" 208 buff = ''' 209static const struct udevice_id tegra_i2c_ids2[] = { 210 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 211 { } 212}; 213 214U_BOOT_DRIVER(i2c_tegra) = { 215 .name = "i2c_tegra", 216 .id = UCLASS_I2C, 217 .of_match = tegra_i2c_ids, 218}; 219''' 220 scan = src_scan.Scanner(None, None) 221 with self.assertRaises(ValueError) as exc: 222 scan._parse_driver('file.c', buff) 223 self.assertIn( 224 "file.c: Unknown compatible var 'tegra_i2c_ids' (found: tegra_i2c_ids2)", 225 str(exc.exception)) 226 227 def test_of_match(self): 228 """Test detection of of_match_ptr() member""" 229 buff = ''' 230static const struct udevice_id tegra_i2c_ids[] = { 231 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 232 { } 233}; 234 235U_BOOT_DRIVER(i2c_tegra) = { 236 .name = "i2c_tegra", 237 .id = UCLASS_I2C, 238 .of_match = of_match_ptr(tegra_i2c_ids), 239}; 240''' 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) 248 249 def test_priv(self): 250 """Test collection of struct info from drivers""" 251 buff = ''' 252static const struct udevice_id test_ids[] = { 253 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 254 { } 255}; 256 257U_BOOT_DRIVER(testing) = { 258 .name = "testing", 259 .id = UCLASS_I2C, 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), 265 DM_PHASE(tpl) 266 DM_HEADER(<i2c.h>) 267 DM_HEADER(<asm/clk.h>) 268}; 269''' 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) 276 self.assertEqual( 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)) 285 286 def test_uclass_scan(self): 287 """Test collection of uclass-driver info""" 288 buff = ''' 289UCLASS_DRIVER(i2c) = { 290 .id = UCLASS_I2C, 291 .name = "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, 299}; 300 301''' 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)) 314 315 drv2 = copy.deepcopy(drv) 316 self.assertEqual(drv, drv2) 317 drv2.priv = 'other_priv' 318 self.assertNotEqual(drv, drv2) 319 320 # The hashes only depend on the uclass ID, so should be equal 321 self.assertEqual(drv.__hash__(), drv2.__hash__()) 322 323 self.assertEqual("UclassDriver(name='i2c', uclass_id='UCLASS_I2C')", 324 str(drv)) 325 326 def test_uclass_scan_errors(self): 327 """Test detection of uclass scanning errors""" 328 buff = ''' 329UCLASS_DRIVER(i2c) = { 330 .name = "i2c", 331}; 332 333''' 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'", 338 str(exc.exception)) 339 340 def test_struct_scan(self): 341 """Test collection of struct info""" 342 buff = ''' 343/* some comment */ 344struct some_struct1 { 345 struct i2c_msg *msgs; 346 uint nmsgs; 347}; 348''' 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) 356 357 buff = ''' 358/* another comment */ 359struct another_struct { 360 int speed_hz; 361 int max_transaction_bytes; 362}; 363''' 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) 369 370 self.assertEqual(2, len(scan._structs)) 371 372 self.assertEqual("Struct(name='another_struct', fname='file2.h')", 373 str(struc)) 374 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') 379 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()) 384 385 def setup_dup_drivers(self, name, phase=''): 386 """Set up for a duplcate test 387 388 Returns: 389 tuple: 390 Scanner to use 391 Driver record for first driver 392 Text of second driver declaration 393 Node for driver 1 394 """ 395 driver1 = ''' 396static const struct udevice_id test_ids[] = { 397 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 398 { } 399}; 400 401U_BOOT_DRIVER(%s) = { 402 .name = "testing", 403 .id = UCLASS_I2C, 404 .of_match = test_ids, 405 %s 406}; 407''' % (name, 'DM_PHASE(%s)' % phase if phase else '') 408 driver2 = ''' 409static const struct udevice_id test_ids[] = { 410 { .compatible = "nvidia,tegra114-dvc" }, 411 { } 412}; 413 414U_BOOT_DRIVER(%s) = { 415 .name = "testing", 416 .id = UCLASS_RAM, 417 .of_match = test_ids, 418}; 419''' % name 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] 424 425 prop = FakeProp() 426 prop.name = 'compatible' 427 prop.value = 'nvidia,tegra114-i2c' 428 node = FakeNode() 429 node.name = 'testing' 430 node.props = {'compatible': prop} 431 432 # get_normalized_compat_name() uses this to check for root node 433 node.parent = FakeNode() 434 435 return scan, drv1, driver2, node 436 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) 442 443 # The driver should not have a duplicate yet 444 self.assertEqual([], drv1.dups) 445 446 scan._parse_driver('file2.c', driver2) 447 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) 453 454 # There is no way to distinguish them, so we should expect a warning 455 self.assertTrue(drv2.warn_dups) 456 457 # We should see a warning 458 with test_util.capture_sys_output() as (stdout, _): 459 scan.mark_used([node]) 460 self.assertEqual( 461 "Warning: Duplicate driver name 'nvidia_tegra114_i2c' (orig=file2.c, dups=file1.c)", 462 stdout.getvalue().strip()) 463 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) 470 471 # The second driver should now be a duplicate of the second 472 self.assertEqual(1, len(drv1.dups)) 473 drv2 = drv1.dups[0] 474 475 # The phase is different, so we should not warn of dups 476 self.assertFalse(drv1.warn_dups) 477 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()) 482 483 def test_sequence(self): 484 """Test assignment of sequence numnbers""" 485 scan = src_scan.Scanner(None, None, '') 486 node = FakeNode() 487 uc = src_scan.UclassDriver('UCLASS_I2C') 488 node.uclass = uc 489 node.driver = True 490 node.seq = -1 491 node.path = 'mypath' 492 uc.alias_num_to_node[2] = node 493 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) 499 500 def test_scan_warnings(self): 501 """Test detection of scanning warnings""" 502 buff = ''' 503static const struct udevice_id tegra_i2c_ids[] = { 504 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 505 { } 506}; 507 508U_BOOT_DRIVER(i2c_tegra) = { 509 .name = "i2c_tegra", 510 .id = UCLASS_I2C, 511 .of_match = tegra_i2c_ids + 1, 512}; 513''' 514 # The '+ 1' above should generate a warning 515 516 prop = FakeProp() 517 prop.name = 'compatible' 518 prop.value = 'rockchip,rk3288-grf' 519 node = FakeNode() 520 node.props = {'compatible': prop} 521 522 # get_normalized_compat_name() uses this to check for root node 523 node.parent = FakeNode() 524 525 scan = src_scan.Scanner(None, None) 526 scan._parse_driver('file.c', buff) 527 self.assertEqual( 528 {'i2c_tegra': 529 {"file.c: Warning: unexpected suffix ' + 1' on .of_match line for compat 'tegra_i2c_ids'"}}, 530 scan._warnings) 531 532 tprop = FakeProp() 533 tprop.name = 'compatible' 534 tprop.value = 'nvidia,tegra114-i2c' 535 tnode = FakeNode() 536 tnode.props = {'compatible': tprop} 537 538 # get_normalized_compat_name() uses this to check for root node 539 tnode.parent = FakeNode() 540 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()) 545 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, _): 550 scan.show_warnings() 551 self.assertIn('rockchip_rk3288_grf', stdout.getvalue()) 552 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, _): 557 scan.show_warnings() 558 self.assertIn('rockchip_rk3288_grf', stdout.getvalue()) 559 self.assertNotIn('tegra_i2c_ids', stdout.getvalue()) 560 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'] 564 drv.used = True 565 with test_util.capture_sys_output() as (stdout, _): 566 scan.show_warnings() 567 self.assertIn('rockchip_rk3288_grf', stdout.getvalue()) 568 self.assertIn('tegra_i2c_ids', stdout.getvalue()) 569 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, _): 575 scan.show_warnings() 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' 578 579rockchip_rk3288_grf: WARNING: the driver rockchip_rk3288_grf was not found in the driver list 580 581''', 582 stdout.getvalue()) 583 self.assertIn('tegra_i2c_ids', stdout.getvalue()) 584 585 def scan_uclass_warning(self): 586 """Test a missing .uclass in the driver""" 587 buff = ''' 588static const struct udevice_id tegra_i2c_ids[] = { 589 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 590 { } 591}; 592 593U_BOOT_DRIVER(i2c_tegra) = { 594 .name = "i2c_tegra", 595 .of_match = tegra_i2c_ids, 596}; 597''' 598 scan = src_scan.Scanner(None, None) 599 scan._parse_driver('file.c', buff) 600 self.assertEqual( 601 {'i2c_tegra': {'Missing .uclass in file.c'}}, 602 scan._warnings) 603 604 def scan_compat_warning(self): 605 """Test a missing .compatible in the driver""" 606 buff = ''' 607static const struct udevice_id tegra_i2c_ids[] = { 608 { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, 609 { } 610}; 611 612U_BOOT_DRIVER(i2c_tegra) = { 613 .name = "i2c_tegra", 614 .id = UCLASS_I2C, 615}; 616''' 617 scan = src_scan.Scanner(None, None) 618 scan._parse_driver('file.c', buff) 619 self.assertEqual( 620 {'i2c_tegra': {'Missing .compatible in file.c'}}, 621 scan._warnings) 622