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