1#!/usr/bin/env python3
2# Copyright (c) 2023 Intel Corporation
3#
4# SPDX-License-Identifier: Apache-2.0
5"""
6Blackbox tests for twister's command line functions
7"""
8import importlib
9from unittest import mock
10import os
11import pytest
12import sys
13
14# pylint: disable=no-name-in-module
15from conftest import ZEPHYR_BASE, testsuite_filename_mock, clear_log_in_test
16from twisterlib.testplan import TestPlan
17
18sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/twister/twisterlib"))
19
20@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', testsuite_filename_mock)
21class TestHardwaremap:
22    TESTDATA_1 = [
23        (
24            [
25                'ARM',
26                'SEGGER',
27                'MBED'
28            ],
29            [
30                'DAPLink CMSIS-DAP',
31                'MBED CMSIS-DAP'
32            ],
33            [1234, 'abcd'],
34            'pyocd'
35        ),
36        (
37            [
38                'STMicroelectronics',
39                'Atmel Corp.'
40            ],
41            [
42                'J-Link',
43                'J-Link OB'
44            ],
45            [1234, 'abcd'],
46            'jlink'
47        ),
48        (
49            [
50                'Silicon Labs',
51                'NXP Semiconductors',
52                'Microchip Technology Inc.'
53            ],
54            [
55                'STM32 STLink',
56                '^XDS110.*',
57                'STLINK-V3'
58            ],
59            [1234, 'abcd'],
60            'openocd'
61        ),
62        (
63            [
64                'FTDI',
65                'Digilent',
66                'Microsoft'
67            ],
68            [
69                'TTL232R-3V3',
70                'MCP2200 USB Serial Port Emulator'
71            ],
72            [1234, 'abcd'],
73            'dediprog'
74        )
75    ]
76    TESTDATA_2 = [
77        (
78            'FTDI',
79            'DAPLink CMSIS-DAP',
80            1234,
81            'pyocd'
82        )
83    ]
84    TESTDATA_3 = [
85        (
86            'Texas Instruments',
87            'DAPLink CMSIS-DAP',
88            'abcd', 'las'
89        ),
90        (
91            'Texas Instruments',
92            'DAPLink CMSIS-DAP',
93            'abcd', 'dse0'
94        )
95    ]
96
97    @classmethod
98    def setup_class(cls):
99        apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
100        cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
101        cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
102        cls.twister_module = importlib.util.module_from_spec(cls.spec)
103
104    @classmethod
105    def teardown_class(cls):
106        pass
107
108    @pytest.mark.parametrize(
109        ('manufacturer', 'product', 'serial', 'runner'),
110        TESTDATA_1,
111    )
112    def test_generate(self, capfd, out_path, manufacturer, product, serial, runner):
113        file_name = "test-map.yaml"
114        path = os.path.join(ZEPHYR_BASE, file_name)
115        args = ['--outdir', out_path, '--generate-hardware-map', file_name]
116
117        if os.path.exists(path):
118            os.remove(path)
119
120        def mocked_comports():
121            return [
122                mock.Mock(device='/dev/ttyUSB23',
123                          manufacturer=id_man,
124                          product=id_pro,
125                          serial_number=id_serial
126                          )
127            ]
128
129        for id_man in manufacturer:
130            for id_pro in product:
131                for id_serial in serial:
132                    with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
133                            mock.patch('serial.tools.list_ports.comports',
134                                       side_effect=mocked_comports), \
135                            pytest.raises(SystemExit) as sys_exit:
136                        self.loader.exec_module(self.twister_module)
137
138                    out, err = capfd.readouterr()
139                    sys.stdout.write(out)
140                    sys.stderr.write(err)
141
142                    assert os.path.exists(path)
143
144                    expected_data = '- connected: true\n' \
145                                    f'  id: {id_serial}\n' \
146                                    '  platform: unknown\n' \
147                                    f'  product: {id_pro}\n' \
148                                    f'  runner: {runner}\n' \
149                                    '  serial: /dev/ttyUSB23\n'
150
151                    load_data = open(path).read()
152                    assert load_data == expected_data
153
154                    if os.path.exists(path):
155                        os.remove(path)
156
157                    assert str(sys_exit.value) == '0'
158                    clear_log_in_test()
159
160    @pytest.mark.parametrize(
161        ('manufacturer', 'product', 'serial', 'runner'),
162        TESTDATA_2,
163    )
164    def test_few_generate(self, capfd, out_path, manufacturer, product, serial, runner):
165        file_name = "test-map.yaml"
166        path = os.path.join(ZEPHYR_BASE, file_name)
167        args = ['--outdir', out_path, '--generate-hardware-map', file_name]
168
169        if os.path.exists(path):
170            os.remove(path)
171
172        def mocked_comports():
173            return [
174                mock.Mock(device='/dev/ttyUSB23',
175                          manufacturer=manufacturer,
176                          product=product,
177                          serial_number=serial
178                          ),
179                mock.Mock(device='/dev/ttyUSB24',
180                          manufacturer=manufacturer,
181                          product=product,
182                          serial_number=serial + 1
183                          ),
184                mock.Mock(device='/dev/ttyUSB24',
185                          manufacturer=manufacturer,
186                          product=product,
187                          serial_number=serial + 2
188                          ),
189                mock.Mock(device='/dev/ttyUSB25',
190                          manufacturer=manufacturer,
191                          product=product,
192                          serial_number=serial + 3
193                          )
194            ]
195
196        with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
197                mock.patch('serial.tools.list_ports.comports',
198                           side_effect=mocked_comports), \
199                pytest.raises(SystemExit) as sys_exit:
200            self.loader.exec_module(self.twister_module)
201
202        out, err = capfd.readouterr()
203        sys.stdout.write(out)
204        sys.stderr.write(err)
205
206        assert os.path.exists(path)
207
208        expected_data = '- connected: true\n' \
209                        f'  id: {serial}\n' \
210                        '  platform: unknown\n' \
211                        f'  product: {product}\n' \
212                        f'  runner: {runner}\n' \
213                        '  serial: /dev/ttyUSB23\n' \
214                        '- connected: true\n' \
215                        f'  id: {serial + 1}\n' \
216                        '  platform: unknown\n' \
217                        f'  product: {product}\n' \
218                        f'  runner: {runner}\n' \
219                        '  serial: /dev/ttyUSB24\n' \
220                        '- connected: true\n' \
221                        f'  id: {serial + 2}\n' \
222                        '  platform: unknown\n' \
223                        f'  product: {product}\n' \
224                        f'  runner: {runner}\n' \
225                        '  serial: /dev/ttyUSB24\n' \
226                        '- connected: true\n' \
227                        f'  id: {serial + 3}\n' \
228                        '  platform: unknown\n' \
229                        f'  product: {product}\n' \
230                        f'  runner: {runner}\n' \
231                        '  serial: /dev/ttyUSB25\n'
232
233        load_data = open(path).read()
234        assert load_data == expected_data
235
236        if os.path.exists(path):
237            os.remove(path)
238
239        assert str(sys_exit.value) == '0'
240
241    @pytest.mark.parametrize(
242        ('manufacturer', 'product', 'serial', 'location'),
243        TESTDATA_3,
244    )
245    def test_texas_exeption(self, capfd, out_path, manufacturer, product, serial, location):
246        file_name = "test-map.yaml"
247        path = os.path.join(ZEPHYR_BASE, file_name)
248        args = ['--outdir', out_path, '--generate-hardware-map', file_name]
249
250        if os.path.exists(path):
251            os.remove(path)
252
253        def mocked_comports():
254            return [
255                mock.Mock(device='/dev/ttyUSB23',
256                          manufacturer=manufacturer,
257                          product=product,
258                          serial_number=serial,
259                          location=location
260                          )
261            ]
262
263        with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
264                mock.patch('serial.tools.list_ports.comports',
265                           side_effect=mocked_comports), \
266                pytest.raises(SystemExit) as sys_exit:
267            self.loader.exec_module(self.twister_module)
268
269        out, err = capfd.readouterr()
270        sys.stdout.write(out)
271        sys.stderr.write(err)
272
273        assert os.path.exists(path)
274
275        expected_data = '- connected: true\n' \
276                        f'  id: {serial}\n' \
277                        '  platform: unknown\n' \
278                        f'  product: {product}\n' \
279                        '  runner: pyocd\n' \
280                        '  serial: /dev/ttyUSB23\n'
281        expected_data2 = '[]\n'
282
283        load_data = open(path).read()
284        if location.endswith('0'):
285            assert load_data == expected_data
286        else:
287            assert load_data == expected_data2
288        if os.path.exists(path):
289            os.remove(path)
290
291        assert str(sys_exit.value) == '0'
292