1#!/usr/bin/env python3 2# Copyright (c) 2023 Intel Corporation 3# 4# SPDX-License-Identifier: Apache-2.0 5""" 6Tests for hardwaremap.py classes' methods 7""" 8 9import mock 10import pytest 11import sys 12 13from pathlib import Path 14 15from twisterlib.hardwaremap import( 16 DUT, 17 HardwareMap 18) 19 20 21@pytest.fixture 22def mocked_hm(): 23 duts = [ 24 DUT(platform='p1', id=1, serial='s1', product='pr1', connected=True), 25 DUT(platform='p2', id=2, serial='s2', product='pr2', connected=False), 26 DUT(platform='p3', id=3, serial='s3', product='pr3', connected=True), 27 DUT(platform='p4', id=4, serial='s4', product='pr4', connected=False), 28 DUT(platform='p5', id=5, serial='s5', product='pr5', connected=True), 29 DUT(platform='p6', id=6, serial='s6', product='pr6', connected=False), 30 DUT(platform='p7', id=7, serial='s7', product='pr7', connected=True), 31 DUT(platform='p8', id=8, serial='s8', product='pr8', connected=False) 32 ] 33 34 hm = HardwareMap(env=mock.Mock()) 35 hm.duts = duts 36 hm.detected = duts[:5] 37 38 return hm 39 40 41TESTDATA_1 = [ 42 ( 43 {}, 44 {'baud': 115200, 'lock': mock.ANY, 'flash_timeout': 60}, 45 '<None (None) on None>' 46 ), 47 ( 48 { 49 'id': 'dummy id', 50 'serial': 'dummy serial', 51 'serial_baud': 4400, 52 'platform': 'dummy platform', 53 'product': 'dummy product', 54 'serial_pty': 'dummy serial pty', 55 'connected': True, 56 'runner_params': ['dummy', 'runner', 'params'], 57 'pre_script': 'dummy pre script', 58 'post_script': 'dummy post script', 59 'post_flash_script': 'dummy post flash script', 60 'runner': 'dummy runner', 61 'flash_timeout': 30, 62 'flash_with_test': True, 63 'script_param': { 64 'pre_script_timeout' : 30, 65 'post_flash_timeout' : 30, 66 'post_script_timeout' : 30, 67 } 68 }, 69 { 70 'lock': mock.ANY, 71 'id': 'dummy id', 72 'serial': 'dummy serial', 73 'baud': 4400, 74 'platform': 'dummy platform', 75 'product': 'dummy product', 76 'serial_pty': 'dummy serial pty', 77 'connected': True, 78 'runner_params': ['dummy', 'runner', 'params'], 79 'pre_script': 'dummy pre script', 80 'post_script': 'dummy post script', 81 'post_flash_script': 'dummy post flash script', 82 'runner': 'dummy runner', 83 'flash_timeout': 30, 84 'flash_with_test': True, 85 'script_param': { 86 'pre_script_timeout' : 30, 87 'post_flash_timeout' : 30, 88 'post_script_timeout' : 30, 89 } 90 }, 91 '<dummy platform (dummy product) on dummy serial>' 92 ), 93] 94 95 96@pytest.mark.parametrize( 97 'kwargs, expected_dict, expected_repr', 98 TESTDATA_1, 99 ids=['no information', 'full information'] 100) 101def test_dut(kwargs, expected_dict, expected_repr): 102 d = DUT(**kwargs) 103 104 assert d.available 105 assert d.counter == 0 106 107 d.available = False 108 d.counter = 1 109 110 assert not d.available 111 assert d.counter == 1 112 113 assert d.to_dict() == expected_dict 114 assert d.__repr__() == expected_repr 115 116 117TESTDATA_2 = [ 118 ('ghm.yaml', mock.ANY, mock.ANY, [], mock.ANY, mock.ANY, mock.ANY, 0, 119 True, True, False, False, False, False, []), 120 (None, False, 'hm.yaml', [], mock.ANY, mock.ANY, mock.ANY, 0, 121 False, False, True, True, False, False, []), 122 (None, True, 'hm.yaml', [], mock.ANY, mock.ANY, ['fix'], 1, 123 False, False, True, False, False, True, ['p1', 'p3', 'p5', 'p7']), 124 (None, True, 'hm.yaml', ['pX'], mock.ANY, mock.ANY, ['fix'], 1, 125 False, False, True, False, False, True, ['pX']), 126 (None, True, None, ['p'], 's', None, ['fix'], 1, 127 False, False, False, False, True, True, ['p']), 128 (None, True, None, ['p'], None, 'spty', ['fix'], 1, 129 False, False, False, False, True, True, ['p']), 130] 131 132 133@pytest.mark.parametrize( 134 'generate_hardware_map, device_testing, hardware_map, platform,' \ 135 ' device_serial, device_serial_pty, fixtures,' \ 136 ' return_code, expect_scan, expect_save, expect_load,' \ 137 ' expect_dump, expect_add_device, expect_fixtures, expected_platforms', 138 TESTDATA_2, 139 ids=['generate hardware map', 'existing hardware map', 140 'device testing with hardware map, no platform', 141 'device testing with hardware map with platform', 142 'device testing with device serial', 143 'device testing with device serial pty'] 144) 145def test_hardwaremap_discover( 146 caplog, 147 mocked_hm, 148 generate_hardware_map, 149 device_testing, 150 hardware_map, 151 platform, 152 device_serial, 153 device_serial_pty, 154 fixtures, 155 return_code, 156 expect_scan, 157 expect_save, 158 expect_load, 159 expect_dump, 160 expect_add_device, 161 expect_fixtures, 162 expected_platforms 163): 164 def mock_load(*args): 165 mocked_hm.platform = platform 166 167 mocked_hm.scan = mock.Mock() 168 mocked_hm.save = mock.Mock() 169 mocked_hm.load = mock.Mock(side_effect=mock_load) 170 mocked_hm.dump = mock.Mock() 171 mocked_hm.add_device = mock.Mock() 172 173 mocked_hm.options.device_flash_with_test = True 174 mocked_hm.options.device_flash_timeout = 15 175 mocked_hm.options.pre_script = 'dummy pre script' 176 mocked_hm.options.platform = platform 177 mocked_hm.options.device_serial = device_serial 178 mocked_hm.options.device_serial_pty = device_serial_pty 179 mocked_hm.options.device_testing = device_testing 180 mocked_hm.options.hardware_map = hardware_map 181 mocked_hm.options.persistent_hardware_map = mock.Mock() 182 mocked_hm.options.generate_hardware_map = generate_hardware_map 183 mocked_hm.options.fixture = fixtures 184 185 returncode = mocked_hm.discover() 186 187 assert returncode == return_code 188 189 if expect_scan: 190 mocked_hm.scan.assert_called_once_with( 191 persistent=mocked_hm.options.persistent_hardware_map 192 ) 193 if expect_save: 194 mocked_hm.save.assert_called_once_with( 195 mocked_hm.options.generate_hardware_map 196 ) 197 if expect_load: 198 mocked_hm.load.assert_called_once_with( 199 mocked_hm.options.hardware_map 200 ) 201 if expect_dump: 202 mocked_hm.dump.assert_called_once_with( 203 connected_only=True 204 ) 205 if expect_add_device: 206 mocked_hm.add_device.assert_called_once() 207 208 if expect_fixtures: 209 assert all( 210 [all( 211 [fixture in dut.fixtures for fixture in fixtures] 212 ) for dut in mocked_hm.duts] 213 ) 214 215 assert sorted(expected_platforms) == sorted(mocked_hm.options.platform) 216 217 218def test_hardwaremap_summary(capfd, mocked_hm): 219 selected_platforms = ['p0', 'p1', 'p6', 'p7'] 220 221 mocked_hm.summary(selected_platforms) 222 223 expected = """ 224Hardware distribution summary: 225 226| Board | ID | Counter | Failures | 227|---------|------|-----------|------------| 228| p1 | 1 | 0 | 0 | 229| p7 | 7 | 0 | 0 | 230""" 231 232 out, err = capfd.readouterr() 233 sys.stdout.write(out) 234 sys.stderr.write(err) 235 236 assert expected in out 237 238 239TESTDATA_3 = [ 240 (True), 241 (False) 242] 243 244 245@pytest.mark.parametrize( 246 'is_pty', 247 TESTDATA_3, 248 ids=['pty', 'not pty'] 249) 250def test_hardwaremap_add_device(is_pty): 251 hm = HardwareMap(env=mock.Mock()) 252 253 serial = 'dummy' 254 platform = 'p0' 255 pre_script = 'dummy pre script' 256 hm.add_device(serial, platform, pre_script, is_pty) 257 258 assert len(hm.duts) == 1 259 if is_pty: 260 assert hm.duts[0].serial_pty == 'dummy' if is_pty else None 261 assert hm.duts[0].serial is None 262 else: 263 assert hm.duts[0].serial_pty is None 264 assert hm.duts[0].serial == 'dummy' 265 266 267def test_hardwaremap_load(): 268 map_file = \ 269""" 270- id: id0 271 platform: p0 272 product: pr0 273 runner: r0 274 flash_with_test: True 275 flash_timeout: 15 276 baud: 14400 277 fixtures: 278 - dummy fixture 1 279 - dummy fixture 2 280 connected: True 281 serial: 'dummy' 282- id: id1 283 platform: p1 284 product: pr1 285 runner: r1 286 connected: True 287 serial_pty: 'dummy' 288- id: id2 289 platform: p2 290 product: pr2 291 runner: r2 292 connected: True 293""" 294 map_filename = 'map-file.yaml' 295 296 builtin_open = open 297 298 def mock_open(*args, **kwargs): 299 if args[0] == map_filename: 300 return mock.mock_open(read_data=map_file)(*args, **kwargs) 301 return builtin_open(*args, **kwargs) 302 303 hm = HardwareMap(env=mock.Mock()) 304 hm.options.device_flash_timeout = 30 305 hm.options.device_flash_with_test = False 306 307 with mock.patch('builtins.open', mock_open): 308 hm.load(map_filename) 309 310 expected = { 311 'id0': { 312 'platform': 'p0', 313 'product': 'pr0', 314 'runner': 'r0', 315 'flash_timeout': 15, 316 'flash_with_test': True, 317 'baud': 14400, 318 'fixtures': ['dummy fixture 1', 'dummy fixture 2'], 319 'connected': True, 320 'serial': 'dummy', 321 'serial_pty': None, 322 }, 323 'id1': { 324 'platform': 'p1', 325 'product': 'pr1', 326 'runner': 'r1', 327 'flash_timeout': 30, 328 'flash_with_test': False, 329 'baud': 115200, 330 'fixtures': [], 331 'connected': True, 332 'serial': None, 333 'serial_pty': 'dummy', 334 }, 335 } 336 337 for dut in hm.duts: 338 assert dut.id in expected 339 assert all([getattr(dut, k) == v for k, v in expected[dut.id].items()]) 340 341 342TESTDATA_4 = [ 343 ( 344 True, 345 'Linux', 346 ['<p1 (pr1) on s1>', '<p2 (pr2) on s2>', '<p3 (pr3) on s3>', 347 '<p4 (pr4) on s4>', '<p5 (pr5) on s5>', 348 '<unknown (TI product) on /dev/serial/by-id/basic-file1>', 349 '<unknown (product123) on dummy device>', 350 '<unknown (unknown) on /dev/serial/by-id/basic-file2-link>'] 351 ), 352 ( 353 True, 354 'nt', 355 ['<p1 (pr1) on s1>', '<p2 (pr2) on s2>', '<p3 (pr3) on s3>', 356 '<p4 (pr4) on s4>', '<p5 (pr5) on s5>', 357 '<unknown (TI product) on /dev/serial/by-id/basic-file1>', 358 '<unknown (product123) on dummy device>', 359 '<unknown (unknown) on /dev/serial/by-id/basic-file2>'] 360 ), 361 ( 362 False, 363 'Linux', 364 ['<p1 (pr1) on s1>', '<p2 (pr2) on s2>', '<p3 (pr3) on s3>', 365 '<p4 (pr4) on s4>', '<p5 (pr5) on s5>', 366 '<unknown (TI product) on /dev/serial/by-id/basic-file1>', 367 '<unknown (product123) on dummy device>', 368 '<unknown (unknown) on /dev/serial/by-id/basic-file2>'] 369 ) 370] 371 372 373@pytest.mark.parametrize( 374 'persistent, system, expected_reprs', 375 TESTDATA_4, 376 ids=['linux persistent map', 'no map (not linux)', 'no map (nonpersistent)'] 377) 378def test_hardwaremap_scan( 379 caplog, 380 mocked_hm, 381 persistent, 382 system, 383 expected_reprs 384): 385 def mock_resolve(path): 386 if str(path).endswith('-link'): 387 return Path(str(path)[:-5]) 388 return path 389 390 def mock_iterdir(path): 391 return [ 392 Path(path / 'basic-file1'), 393 Path(path / 'basic-file2-link') 394 ] 395 396 def mock_exists(path): 397 return True 398 399 mocked_hm.manufacturer = ['dummy manufacturer', 'Texas Instruments'] 400 mocked_hm.runner_mapping = { 401 'dummy runner': ['product[0-9]+',], 402 'other runner': ['other TI product', 'TI product'] 403 } 404 405 comports_mock = [ 406 mock.Mock( 407 manufacturer='wrong manufacturer', 408 location='wrong location', 409 serial_number='wrong number', 410 product='wrong product', 411 device='wrong device' 412 ), 413 mock.Mock( 414 manufacturer='dummy manufacturer', 415 location='dummy location', 416 serial_number='dummy number', 417 product=None, 418 device='/dev/serial/by-id/basic-file2' 419 ), 420 mock.Mock( 421 manufacturer='dummy manufacturer', 422 location='dummy location', 423 serial_number='dummy number', 424 product='product123', 425 device='dummy device' 426 ), 427 mock.Mock( 428 manufacturer='Texas Instruments', 429 location='serial1', 430 serial_number='TI1', 431 product='TI product', 432 device='TI device1' 433 ), 434 mock.Mock( 435 manufacturer='Texas Instruments', 436 location='serial0', 437 serial_number='TI0', 438 product='TI product', 439 device='/dev/serial/by-id/basic-file1' 440 ), 441 ] 442 443 with mock.patch('platform.system', return_value=system), \ 444 mock.patch('serial.tools.list_ports.comports', 445 return_value=comports_mock), \ 446 mock.patch('twisterlib.hardwaremap.Path.resolve', 447 autospec=True, side_effect=mock_resolve), \ 448 mock.patch('twisterlib.hardwaremap.Path.iterdir', 449 autospec=True, side_effect=mock_iterdir), \ 450 mock.patch('twisterlib.hardwaremap.Path.exists', 451 autospec=True, side_effect=mock_exists): 452 mocked_hm.scan(persistent) 453 454 assert sorted([d.__repr__() for d in mocked_hm.detected]) == \ 455 sorted(expected_reprs) 456 457 assert 'Scanning connected hardware...' in caplog.text 458 assert 'Unsupported device (wrong manufacturer): %s' % comports_mock[0] \ 459 in caplog.text 460 461 462TESTDATA_5 = [ 463 ( 464 None, 465 [{ 466 'platform': 'p1', 467 'id': 1, 468 'runner': mock.ANY, 469 'serial': 's1', 470 'product': 'pr1', 471 'connected': True 472 }, 473 { 474 'platform': 'p2', 475 'id': 2, 476 'runner': mock.ANY, 477 'serial': 's2', 478 'product': 'pr2', 479 'connected': False 480 }, 481 { 482 'platform': 'p3', 483 'id': 3, 484 'runner': mock.ANY, 485 'serial': 's3', 486 'product': 'pr3', 487 'connected': True 488 }, 489 { 490 'platform': 'p4', 491 'id': 4, 492 'runner': mock.ANY, 493 'serial': 's4', 494 'product': 'pr4', 495 'connected': False 496 }, 497 { 498 'platform': 'p5', 499 'id': 5, 500 'runner': mock.ANY, 501 'serial': 's5', 502 'product': 'pr5', 503 'connected': True 504 }] 505 ), 506 ( 507 '', 508 [{ 509 'serial': 's1', 510 'baud': 115200, 511 'platform': 'p1', 512 'connected': True, 513 'id': 1, 514 'product': 'pr1', 515 'lock': mock.ANY, 516 'flash_timeout': 60 517 }, 518 { 519 'serial': 's2', 520 'baud': 115200, 521 'platform': 'p2', 522 'id': 2, 523 'product': 'pr2', 524 'lock': mock.ANY, 525 'flash_timeout': 60 526 }, 527 { 528 'serial': 's3', 529 'baud': 115200, 530 'platform': 'p3', 531 'connected': True, 532 'id': 3, 533 'product': 'pr3', 534 'lock': mock.ANY, 535 'flash_timeout': 60 536 }, 537 { 538 'serial': 's4', 539 'baud': 115200, 540 'platform': 'p4', 541 'id': 4, 542 'product': 'pr4', 543 'lock': mock.ANY, 544 'flash_timeout': 60 545 }, 546 { 547 'serial': 's5', 548 'baud': 115200, 549 'platform': 'p5', 550 'connected': True, 551 'id': 5, 552 'product': 'pr5', 553 'lock': mock.ANY, 554 'flash_timeout': 60 555 }] 556 ), 557 ( 558""" 559- id: 4 560 platform: p4 561 product: pr4 562 connected: True 563 serial: s4 564- id: 0 565 platform: p0 566 product: pr0 567 connected: True 568 serial: s0 569- id: 10 570 platform: p10 571 product: pr10 572 connected: False 573 serial: s10 574- id: 5 575 platform: p5-5 576 product: pr5-5 577 connected: True 578 serial: s5-5 579""", 580 [{ 581 'id': 0, 582 'platform': 'p0', 583 'product': 'pr0', 584 'connected': False, 585 'serial': None 586 }, 587 { 588 'id': 4, 589 'platform': 'p4', 590 'product': 'pr4', 591 'connected': True, 592 'serial': 's4' 593 }, 594 { 595 'id': 5, 596 'platform': 'p5-5', 597 'product': 'pr5-5', 598 'connected': False, 599 'serial': None 600 }, 601 { 602 'id': 10, 603 'platform': 'p10', 604 'product': 'pr10', 605 'connected': False, 606 'serial': None 607 }, 608 { 609 'serial': 's1', 610 'baud': 115200, 611 'platform': 'p1', 612 'connected': True, 613 'id': 1, 614 'product': 'pr1', 615 'lock': mock.ANY, 616 'flash_timeout': 60 617 }, 618 { 619 'serial': 's2', 620 'baud': 115200, 621 'platform': 'p2', 622 'id': 2, 623 'product': 'pr2', 624 'lock': mock.ANY, 625 'flash_timeout': 60 626 }, 627 { 628 'serial': 's3', 629 'baud': 115200, 630 'platform': 'p3', 631 'connected': True, 632 'id': 3, 633 'product': 'pr3', 634 'lock': mock.ANY, 635 'flash_timeout': 60 636 }, 637 { 638 'serial': 's5', 639 'baud': 115200, 640 'platform': 'p5', 641 'connected': True, 642 'id': 5, 643 'product': 'pr5', 644 'lock': mock.ANY, 645 'flash_timeout': 60 646 }] 647 ), 648] 649 650 651@pytest.mark.parametrize( 652 'hwm, expected_dump', 653 TESTDATA_5, 654 ids=['no map', 'empty map', 'map exists'] 655) 656def test_hardwaremap_save(mocked_hm, hwm, expected_dump): 657 read_mock = mock.mock_open(read_data=hwm) 658 write_mock = mock.mock_open() 659 660 def mock_open(filename, mode='r'): 661 if mode == 'r': 662 return read_mock() 663 elif mode == 'w': 664 return write_mock() 665 666 667 mocked_hm.load = mock.Mock() 668 mocked_hm.dump = mock.Mock() 669 670 open_mock = mock.Mock(side_effect=mock_open) 671 dump_mock = mock.Mock() 672 673 with mock.patch('os.path.exists', return_value=hwm is not None), \ 674 mock.patch('builtins.open', open_mock), \ 675 mock.patch('twisterlib.hardwaremap.yaml.dump', dump_mock): 676 mocked_hm.save('hwm.yaml') 677 678 dump_mock.assert_called_once_with(expected_dump, mock.ANY, Dumper=mock.ANY, 679 default_flow_style=mock.ANY) 680 681 682TESTDATA_6 = [ 683 ( 684 ['p1', 'p3', 'p5', 'p7'], 685 [], 686 True, 687 True, 688""" 689| Platform | ID | Serial device | 690|------------|------|-----------------| 691| p1 | 1 | s1 | 692| p3 | 3 | s3 | 693| p5 | 5 | s5 | 694""" 695 ), 696 ( 697 [], 698 ['?', '??', '???'], 699 False, 700 False, 701""" 702| ? | ?? | ??? | 703|-----|------|-------| 704| p1 | 1 | s1 | 705| p2 | 2 | s2 | 706| p3 | 3 | s3 | 707| p4 | 4 | s4 | 708| p5 | 5 | s5 | 709| p6 | 6 | s6 | 710| p7 | 7 | s7 | 711| p8 | 8 | s8 | 712""" 713 ), 714] 715 716 717@pytest.mark.parametrize( 718 'filtered, header, connected_only, detected, expected_out', 719 TESTDATA_6, 720 ids=['detected no header', 'all with header'] 721) 722def test_hardwaremap_dump( 723 capfd, 724 mocked_hm, 725 filtered, 726 header, 727 connected_only, 728 detected, 729 expected_out 730): 731 mocked_hm.dump(filtered, header, connected_only, detected) 732 733 out, err = capfd.readouterr() 734 sys.stdout.write(out) 735 sys.stderr.write(err) 736 737 assert out.strip() == expected_out.strip() 738