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