diff --git a/scripts/tests/twister/test_hardwaremap.py b/scripts/tests/twister/test_hardwaremap.py new file mode 100644 index 00000000000..57c028bfea6 --- /dev/null +++ b/scripts/tests/twister/test_hardwaremap.py @@ -0,0 +1,722 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +""" +Tests for hardwaremap.py classes' methods +""" + +import mock +import pytest +import sys + +from pathlib import Path + +from twisterlib.hardwaremap import( + DUT, + HardwareMap +) + + +@pytest.fixture +def mocked_hm(): + duts = [ + DUT(platform='p1', id=1, serial='s1', product='pr1', connected=True), + DUT(platform='p2', id=2, serial='s2', product='pr2', connected=False), + DUT(platform='p3', id=3, serial='s3', product='pr3', connected=True), + DUT(platform='p4', id=4, serial='s4', product='pr4', connected=False), + DUT(platform='p5', id=5, serial='s5', product='pr5', connected=True), + DUT(platform='p6', id=6, serial='s6', product='pr6', connected=False), + DUT(platform='p7', id=7, serial='s7', product='pr7', connected=True), + DUT(platform='p8', id=8, serial='s8', product='pr8', connected=False) + ] + + hm = HardwareMap(env=mock.Mock()) + hm.duts = duts + hm.detected = duts[:5] + + return hm + + +TESTDATA_1 = [ + ( + {}, + {'baud': 115200, 'lock': mock.ANY, 'flash_timeout': 60}, + '' + ), + ( + { + 'id': 'dummy id', + 'serial': 'dummy serial', + 'serial_baud': 4400, + 'platform': 'dummy platform', + 'product': 'dummy product', + 'serial_pty': 'dummy serial pty', + 'connected': True, + 'runner_params': ['dummy', 'runner', 'params'], + 'pre_script': 'dummy pre script', + 'post_script': 'dummy post script', + 'post_flash_script': 'dummy post flash script', + 'runner': 'dummy runner', + 'flash_timeout': 30, + 'flash_with_test': True + }, + { + 'lock': mock.ANY, + 'id': 'dummy id', + 'serial': 'dummy serial', + 'baud': 4400, + 'platform': 'dummy platform', + 'product': 'dummy product', + 'serial_pty': 'dummy serial pty', + 'connected': True, + 'runner_params': ['dummy', 'runner', 'params'], + 'pre_script': 'dummy pre script', + 'post_script': 'dummy post script', + 'post_flash_script': 'dummy post flash script', + 'runner': 'dummy runner', + 'flash_timeout': 30, + 'flash_with_test': True + }, + '' + ), +] + + +@pytest.mark.parametrize( + 'kwargs, expected_dict, expected_repr', + TESTDATA_1, + ids=['no information', 'full information'] +) +def test_dut(kwargs, expected_dict, expected_repr): + d = DUT(**kwargs) + + assert d.available + assert d.counter == 0 + + d.available = False + d.counter = 1 + + assert not d.available + assert d.counter == 1 + + assert d.to_dict() == expected_dict + assert d.__repr__() == expected_repr + + +TESTDATA_2 = [ + ('ghm.yaml', mock.ANY, mock.ANY, [], mock.ANY, mock.ANY, mock.ANY, 0, + True, True, False, False, False, False, []), + (None, False, 'hm.yaml', [], mock.ANY, mock.ANY, mock.ANY, 0, + False, False, True, True, False, False, []), + (None, True, 'hm.yaml', [], mock.ANY, mock.ANY, ['fix'], 1, + False, False, True, False, False, True, ['p1', 'p3', 'p5', 'p7']), + (None, True, 'hm.yaml', ['pX'], mock.ANY, mock.ANY, ['fix'], 1, + False, False, True, False, False, True, ['pX']), + (None, True, None, ['p'], 's', None, ['fix'], 1, + False, False, False, False, True, True, ['p']), + (None, True, None, ['p'], None, 'spty', ['fix'], 1, + False, False, False, False, True, True, ['p']), +] + + +@pytest.mark.parametrize( + 'generate_hardware_map, device_testing, hardware_map, platform,' \ + ' device_serial, device_serial_pty, fixtures,' \ + ' return_code, expect_scan, expect_save, expect_load,' \ + ' expect_dump, expect_add_device, expect_fixtures, expected_platforms', + TESTDATA_2, + ids=['generate hardware map', 'existing hardware map', + 'device testing with hardware map, no platform', + 'device testing with hardware map with platform', + 'device testing with device serial', + 'device testing with device serial pty'] +) +def test_hardwaremap_discover( + caplog, + mocked_hm, + generate_hardware_map, + device_testing, + hardware_map, + platform, + device_serial, + device_serial_pty, + fixtures, + return_code, + expect_scan, + expect_save, + expect_load, + expect_dump, + expect_add_device, + expect_fixtures, + expected_platforms +): + def mock_load(*args): + mocked_hm.platform = platform + + mocked_hm.scan = mock.Mock() + mocked_hm.save = mock.Mock() + mocked_hm.load = mock.Mock(side_effect=mock_load) + mocked_hm.dump = mock.Mock() + mocked_hm.add_device = mock.Mock() + + mocked_hm.options.device_flash_with_test = True + mocked_hm.options.device_flash_timeout = 15 + mocked_hm.options.pre_script = 'dummy pre script' + mocked_hm.options.platform = platform + mocked_hm.options.device_serial = device_serial + mocked_hm.options.device_serial_pty = device_serial_pty + mocked_hm.options.device_testing = device_testing + mocked_hm.options.hardware_map = hardware_map + mocked_hm.options.persistent_hardware_map = mock.Mock() + mocked_hm.options.generate_hardware_map = generate_hardware_map + mocked_hm.options.fixture = fixtures + + returncode = mocked_hm.discover() + + assert returncode == return_code + + if expect_scan: + mocked_hm.scan.assert_called_once_with( + persistent=mocked_hm.options.persistent_hardware_map + ) + if expect_save: + mocked_hm.save.assert_called_once_with( + mocked_hm.options.generate_hardware_map + ) + if expect_load: + mocked_hm.load.assert_called_once_with( + mocked_hm.options.hardware_map + ) + if expect_dump: + mocked_hm.dump.assert_called_once_with( + connected_only=True + ) + if expect_add_device: + mocked_hm.add_device.assert_called_once() + + if expect_fixtures: + assert all( + [all( + [fixture in dut.fixtures for fixture in fixtures] + ) for dut in mocked_hm.duts] + ) + + assert sorted(expected_platforms) == sorted(mocked_hm.options.platform) + + +def test_hardwaremap_summary(capfd, mocked_hm): + selected_platforms = ['p0', 'p1', 'p6', 'p7'] + + mocked_hm.summary(selected_platforms) + + expected = """ +Hardware distribution summary: + +| Board | ID | Counter | +|---------|------|-----------| +| p1 | 1 | 0 | +| p7 | 7 | 0 | +""" + + out, err = capfd.readouterr() + sys.stdout.write(out) + sys.stderr.write(err) + + assert expected in out + + +TESTDATA_3 = [ + (True), + (False) +] + + +@pytest.mark.parametrize( + 'is_pty', + TESTDATA_3, + ids=['pty', 'not pty'] +) +def test_hardwaremap_add_device(is_pty): + hm = HardwareMap(env=mock.Mock()) + + serial = 'dummy' + platform = 'p0' + pre_script = 'dummy pre script' + hm.add_device(serial, platform, pre_script, is_pty) + + assert len(hm.duts) == 1 + if is_pty: + assert hm.duts[0].serial_pty == 'dummy' if is_pty else None + assert hm.duts[0].serial is None + else: + assert hm.duts[0].serial_pty is None + assert hm.duts[0].serial == 'dummy' + + +def test_hardwaremap_load(): + map_file = \ +""" +- id: id0 + platform: p0 + product: pr0 + runner: r0 + flash_with_test: True + flash_timeout: 15 + baud: 14400 + fixtures: + - dummy fixture 1 + - dummy fixture 2 + connected: True + serial: 'dummy' +- id: id1 + platform: p1 + product: pr1 + runner: r1 + connected: True + serial_pty: 'dummy' +- id: id2 + platform: p2 + product: pr2 + runner: r2 + connected: True +""" + map_filename = 'map-file.yaml' + + builtin_open = open + + def mock_open(*args, **kwargs): + if args[0] == map_filename: + return mock.mock_open(read_data=map_file)(*args, **kwargs) + return builtin_open(*args, **kwargs) + + hm = HardwareMap(env=mock.Mock()) + hm.options.device_flash_timeout = 30 + hm.options.device_flash_with_test = False + + with mock.patch('builtins.open', mock_open): + hm.load(map_filename) + + expected = { + 'id0': { + 'platform': 'p0', + 'product': 'pr0', + 'runner': 'r0', + 'flash_timeout': 15, + 'flash_with_test': True, + 'baud': 14400, + 'fixtures': ['dummy fixture 1', 'dummy fixture 2'], + 'connected': True, + 'serial': 'dummy', + 'serial_pty': None, + }, + 'id1': { + 'platform': 'p1', + 'product': 'pr1', + 'runner': 'r1', + 'flash_timeout': 30, + 'flash_with_test': False, + 'baud': 115200, + 'fixtures': [], + 'connected': True, + 'serial': None, + 'serial_pty': 'dummy', + }, + } + + for dut in hm.duts: + assert dut.id in expected + assert all([getattr(dut, k) == v for k, v in expected[dut.id].items()]) + + +TESTDATA_4 = [ + ( + True, + 'Linux', + ['', '', '', + '', '', + '', + '', + ''] + ), + ( + True, + 'nt', + ['', '', '', + '', '', + '', + '', + ''] + ), + ( + False, + 'Linux', + ['', '', '', + '', '', + '', + '', + ''] + ) +] + + +@pytest.mark.parametrize( + 'persistent, system, expected_reprs', + TESTDATA_4, + ids=['linux persistent map', 'no map (not linux)', 'no map (nonpersistent)'] +) +def test_hardwaremap_scan( + caplog, + mocked_hm, + persistent, + system, + expected_reprs +): + def mock_resolve(path): + if str(path).endswith('-link'): + return Path(str(path)[:-5]) + return path + + def mock_iterdir(path): + return [ + Path(path / 'basic-file1'), + Path(path / 'basic-file2-link') + ] + + mocked_hm.manufacturer = ['dummy manufacturer', 'Texas Instruments'] + mocked_hm.runner_mapping = { + 'dummy runner': ['product[0-9]+',], + 'other runner': ['other TI product', 'TI product'] + } + + comports_mock = [ + mock.Mock( + manufacturer='wrong manufacturer', + location='wrong location', + serial_number='wrong number', + product='wrong product', + device='wrong device' + ), + mock.Mock( + manufacturer='dummy manufacturer', + location='dummy location', + serial_number='dummy number', + product=None, + device='/dev/serial/by-id/basic-file2' + ), + mock.Mock( + manufacturer='dummy manufacturer', + location='dummy location', + serial_number='dummy number', + product='product123', + device='dummy device' + ), + mock.Mock( + manufacturer='Texas Instruments', + location='serial1', + serial_number='TI1', + product='TI product', + device='TI device1' + ), + mock.Mock( + manufacturer='Texas Instruments', + location='serial0', + serial_number='TI0', + product='TI product', + device='/dev/serial/by-id/basic-file1' + ), + ] + + with mock.patch('platform.system', return_value=system), \ + mock.patch('serial.tools.list_ports.comports', + return_value=comports_mock), \ + mock.patch('twisterlib.hardwaremap.Path.resolve', + autospec=True, side_effect=mock_resolve), \ + mock.patch('twisterlib.hardwaremap.Path.iterdir', + autospec=True, side_effect=mock_iterdir): + mocked_hm.scan(persistent) + + assert sorted([d.__repr__() for d in mocked_hm.detected]) == \ + sorted(expected_reprs) + + assert 'Scanning connected hardware...' in caplog.text + assert 'Unsupported device (wrong manufacturer): %s' % comports_mock[0] \ + in caplog.text + + +TESTDATA_5 = [ + ( + None, + [{ + 'platform': 'p1', + 'id': 1, + 'runner': mock.ANY, + 'serial': 's1', + 'product': 'pr1', + 'connected': True + }, + { + 'platform': 'p2', + 'id': 2, + 'runner': mock.ANY, + 'serial': 's2', + 'product': 'pr2', + 'connected': False + }, + { + 'platform': 'p3', + 'id': 3, + 'runner': mock.ANY, + 'serial': 's3', + 'product': 'pr3', + 'connected': True + }, + { + 'platform': 'p4', + 'id': 4, + 'runner': mock.ANY, + 'serial': 's4', + 'product': 'pr4', + 'connected': False + }, + { + 'platform': 'p5', + 'id': 5, + 'runner': mock.ANY, + 'serial': 's5', + 'product': 'pr5', + 'connected': True + }] + ), + ( + '', + [{ + 'serial': 's1', + 'baud': 115200, + 'platform': 'p1', + 'connected': True, + 'id': 1, + 'product': 'pr1', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's2', + 'baud': 115200, + 'platform': 'p2', + 'id': 2, + 'product': 'pr2', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's3', + 'baud': 115200, + 'platform': 'p3', + 'connected': True, + 'id': 3, + 'product': 'pr3', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's4', + 'baud': 115200, + 'platform': 'p4', + 'id': 4, + 'product': 'pr4', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's5', + 'baud': 115200, + 'platform': 'p5', + 'connected': True, + 'id': 5, + 'product': 'pr5', + 'lock': mock.ANY, + 'flash_timeout': 60 + }] + ), + ( +""" +- id: 4 + platform: p4 + product: pr4 + connected: True + serial: s4 +- id: 0 + platform: p0 + product: pr0 + connected: True + serial: s0 +- id: 10 + platform: p10 + product: pr10 + connected: False + serial: s10 +- id: 5 + platform: p5-5 + product: pr5-5 + connected: True + serial: s5-5 +""", + [{ + 'id': 0, + 'platform': 'p0', + 'product': 'pr0', + 'connected': False, + 'serial': None + }, + { + 'id': 4, + 'platform': 'p4', + 'product': 'pr4', + 'connected': True, + 'serial': 's4' + }, + { + 'id': 5, + 'platform': 'p5-5', + 'product': 'pr5-5', + 'connected': False, + 'serial': None + }, + { + 'id': 10, + 'platform': 'p10', + 'product': 'pr10', + 'connected': False, + 'serial': None + }, + { + 'serial': 's1', + 'baud': 115200, + 'platform': 'p1', + 'connected': True, + 'id': 1, + 'product': 'pr1', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's2', + 'baud': 115200, + 'platform': 'p2', + 'id': 2, + 'product': 'pr2', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's3', + 'baud': 115200, + 'platform': 'p3', + 'connected': True, + 'id': 3, + 'product': 'pr3', + 'lock': mock.ANY, + 'flash_timeout': 60 + }, + { + 'serial': 's5', + 'baud': 115200, + 'platform': 'p5', + 'connected': True, + 'id': 5, + 'product': 'pr5', + 'lock': mock.ANY, + 'flash_timeout': 60 + }] + ), +] + + +@pytest.mark.parametrize( + 'hwm, expected_dump', + TESTDATA_5, + ids=['no map', 'empty map', 'map exists'] +) +def test_hardwaremap_save(mocked_hm, hwm, expected_dump): + read_mock = mock.mock_open(read_data=hwm) + write_mock = mock.mock_open() + + def mock_open(filename, mode): + if mode == 'r': + return read_mock() + elif mode == 'w': + return write_mock() + + + mocked_hm.load = mock.Mock() + mocked_hm.dump = mock.Mock() + + open_mock = mock.Mock(side_effect=mock_open) + dump_mock = mock.Mock() + + with mock.patch('os.path.exists', return_value=hwm is not None), \ + mock.patch('builtins.open', open_mock), \ + mock.patch('twisterlib.hardwaremap.yaml.dump', dump_mock): + mocked_hm.save('hwm.yaml') + + dump_mock.assert_called_once_with(expected_dump, mock.ANY, Dumper=mock.ANY, + default_flow_style=mock.ANY) + + +TESTDATA_6 = [ + ( + ['p1', 'p3', 'p5', 'p7'], + [], + True, + True, +""" +| Platform | ID | Serial device | +|------------|------|-----------------| +| p1 | 1 | s1 | +| p3 | 3 | s3 | +| p5 | 5 | s5 | +""" + ), + ( + [], + ['?', '??', '???'], + False, + False, +""" +| ? | ?? | ??? | +|-----|------|-------| +| p1 | 1 | s1 | +| p2 | 2 | s2 | +| p3 | 3 | s3 | +| p4 | 4 | s4 | +| p5 | 5 | s5 | +| p6 | 6 | s6 | +| p7 | 7 | s7 | +| p8 | 8 | s8 | +""" + ), +] + + +@pytest.mark.parametrize( + 'filtered, header, connected_only, detected, expected_out', + TESTDATA_6, + ids=['detected no header', 'all with header'] +) +def test_hardwaremap_dump( + capfd, + mocked_hm, + filtered, + header, + connected_only, + detected, + expected_out +): + mocked_hm.dump(filtered, header, connected_only, detected) + + out, err = capfd.readouterr() + sys.stdout.write(out) + sys.stderr.write(err) + + assert out.strip() == expected_out.strip()