1#!/usr/bin/env python3
2# Copyright (c) 2023 Intel Corporation
3#
4# SPDX-License-Identifier: Apache-2.0
5
6'''
7This test file contains tests for platform.py module of twister
8'''
9import sys
10import os
11from unittest import mock
12import pytest
13
14from contextlib import nullcontext
15from pykwalify.errors import SchemaError
16
17ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
18sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/twister"))
19
20from twisterlib.platform import Platform, Simulator, generate_platforms
21
22
23TESTDATA_1 = [
24    (
25"""\
26identifier: dummy empty
27arch: arc
28""",
29        {
30            'name': 'dummy empty',
31            'arch': 'arc',
32            'twister': True,
33            'ram': 128,
34            'timeout_multiplier': 1.0,
35            'ignore_tags': [],
36            'only_tags': [],
37            'default': False,
38            'binaries': [],
39            'flash': 512,
40            'supported': set(),
41            'vendor': '',
42            'tier': -1,
43            'type': 'na',
44            'simulators': [],
45            'supported_toolchains': [],
46            'env': [],
47            'env_satisfied': True
48        },
49        '<dummy empty on arc>'
50    ),
51    (
52"""\
53identifier: dummy full
54arch: riscv
55twister: true
56ram: 1024
57testing:
58  timeout_multiplier: 2.0
59  ignore_tags:
60    - tag1
61    - tag2
62  only_tags:
63    - tag3
64  default: true
65  binaries:
66    - dummy.exe
67    - dummy.bin
68flash: 4096
69supported:
70  - ble
71  - netif:openthread
72  - gpio
73vendor: vendor1
74tier: 1
75type: unit
76simulation:
77- name: nsim
78  exec: nsimdrv
79toolchain:
80  - zephyr
81  - llvm
82env:
83  - dummynonexistentvar
84""",
85        {
86            'name': 'dummy full',
87            'arch': 'riscv',
88            'twister': True,
89            'ram': 1024,
90            'timeout_multiplier': 2.0,
91            'ignore_tags': ['tag1', 'tag2'],
92            'only_tags': ['tag3'],
93            'default': True,
94            'binaries': ['dummy.exe', 'dummy.bin'],
95            'flash': 4096,
96            'supported': set(['ble', 'netif', 'openthread', 'gpio']),
97            'vendor': 'vendor1',
98            'tier': 1,
99            'type': 'unit',
100            'simulators': [Simulator({'name': 'nsim', 'exec': 'nsimdrv'})],
101            'supported_toolchains': ['zephyr', 'llvm', 'cross-compile'],
102            'env': ['dummynonexistentvar'],
103            'env_satisfied': False
104        },
105        '<dummy full on riscv>'
106    ),
107]
108
109# This test is disabled because the Platform loading was changed significantly.
110# The test should be updated to reflect the new implementation.
111
112@pytest.mark.parametrize(
113    'platform_text, expected_data, expected_repr',
114    TESTDATA_1,
115    ids=['almost empty specification', 'full specification']
116)
117def xtest_platform_load(platform_text, expected_data, expected_repr):
118    platform = Platform()
119
120    with mock.patch('builtins.open', mock.mock_open(read_data=platform_text)):
121        platform.load('dummy.yaml')
122
123    for k, v in expected_data.items():
124        if not hasattr(platform, k):
125            assert False, f'No key {k} in platform {platform}'
126        att = getattr(platform, k)
127        if isinstance(v, list) and not isinstance(att, list):
128            assert False, f'Value mismatch in key {k} in platform {platform}'
129        if isinstance(v, list):
130            assert sorted(att) == sorted(v)
131        else:
132            assert att == v
133
134    assert platform.__repr__() == expected_repr
135
136
137TESTDATA_2 = [
138    (
139        ['m0'],
140        None,
141        {
142            'p1e1/s1', 'p1e2/s1', 'p2/s1', 'p3@A/s2/c1', 'p3@B/s2/c1',
143        },
144    ),
145    (
146        ['m0', 'm1'],
147        None,
148        {
149            'p1e1/s1', 'p1e2/s1', 'p2/s1', 'p3@A/s2/c1', 'p3@B/s2/c1',
150            'p1e1/s1/v1', 'p1e1/s1/v2', 'p1e2/s1/v1', 'p2/s1/v1',
151        },
152    ),
153    (
154        ['m0', 'm1', 'm2'],
155        None,
156        {
157            'p1e1/s1', 'p1e2/s1', 'p2/s1', 'p3@A/s2/c1', 'p3@B/s2/c1',
158            'p1e1/s1/v1', 'p1e1/s1/v2', 'p1e2/s1/v1', 'p2/s1/v1',
159            'p3@A/s2/c2', 'p3@B/s2/c2', 'p4/s1',
160        },
161    ),
162    (
163        ['m0', 'm3'],
164        Exception("Duplicate platform identifier p1e1/s1 found"),
165        None,
166    ),
167    (
168        ['m0', 'm1', 'm4'],
169        Exception("Duplicate platform identifier p1e2/s1/v1 found"),
170        None,
171    ),
172    (
173        ['m0', 'm5'],
174        SchemaError(), # Unknown message as this is raised externally
175        None,
176    ),
177]
178
179@pytest.mark.parametrize(
180    'roots, expected_exception, expected_platform_names',
181    TESTDATA_2,
182    ids=[
183        'default board root',
184        '1 extra board root',
185        '2 extra board roots',
186        '1 extra board root, duplicate platform',
187        '2 extra board roots, duplicate platform',
188        '1 extra board root, malformed yaml',
189    ]
190)
191def test_generate_platforms(
192    tmp_path,
193    roots,
194    expected_exception,
195    expected_platform_names,
196):
197    tmp_files = {
198        'm0/boards/zephyr/p1/board.yml': """\
199boards:
200  - name: p1e1
201    vendor: zephyr
202    socs:
203      - name: s1
204  - name: p1e2
205    vendor: zephyr
206    socs:
207      - name: s1
208""",
209        'm0/boards/zephyr/p1/twister.yaml': """\
210type: native
211arch: x86
212variants:
213  p1e1:
214    twister: False
215  p1e2:
216    sysbuild: True
217""",
218        'm0/boards/zephyr/p2/board.yml': """\
219boards:
220  - name: p2
221    vendor: zephyr
222    socs:
223      - name: s1
224""",
225        'm0/boards/zephyr/p2/p2.yaml': """\
226identifier: p2/s1
227type: sim
228arch: x86
229vendor: vendor2
230testing:
231  default: True
232""",
233        'm0/boards/arm/p3/board.yml': """\
234board:
235  name: p3
236  vendor: arm
237  revision:
238    format: letter
239    default: "A"
240    revisions:
241      - name: "A"
242      - name: "B"
243  socs:
244    - name: s2
245""",
246        'm0/boards/arm/p3/twister.yaml': """\
247type: unit
248arch: arm
249vendor: vendor3
250sysbuild: True
251variants:
252  p3/s2/c1:
253    testing:
254      timeout_multiplier: 2.71828
255  p3@B/s2/c1:
256    testing:
257      timeout_multiplier: 3.14159
258""",
259        'm0/soc/zephyr/soc.yml': """\
260family:
261  - name: zephyr
262    series:
263      - name: zephyr_testing
264        socs:
265          - name: s1
266          - name: s2
267            cpuclusters:
268              - name: c1
269""",
270        'm1/boards/zephyr/p1e1/board.yml': """\
271board:
272  extend: p1e1
273  variants:
274    - name: v1
275      qualifier: s1
276    - name: v2
277      qualifier: s1
278""",
279        'm1/boards/zephyr/p1e1/twister.yaml': """\
280variants:
281  p1e1/s1/v1:
282    testing:
283      default: True
284""",
285        'm1/boards/zephyr/p1e2/board.yml': """\
286board:
287  extend: p1e2
288  variants:
289    - name: v1
290      qualifier: s1
291""",
292        'm1/boards/zephyr/p2/board.yml': """\
293board:
294  extend: p2
295  variants:
296    - name: v1
297      qualifier: s1
298""",
299        'm1/boards/zephyr/p2/p2_s1_v1.yaml': """\
300identifier: p2/s1/v1
301""",
302        'm2/boards/misc/board.yml': """\
303boards:
304  - extend: p3
305  - name: p4
306    vendor: misc
307    socs:
308      - name: s1
309""",
310        'm2/boards/misc/twister.yaml': """\
311type: qemu
312arch: riscv
313vendor: vendor4
314simulation:
315  - name: qemu
316variants:
317  p3@A/s2/c2:
318    sysbuild: False
319""",
320        'm2/soc/zephyr/soc.yml': """\
321socs:
322  - extend: s2
323    cpuclusters:
324      - name: c2
325""",
326        'm3/boards/zephyr/p1e1/board.yml': """\
327board:
328  extend: p1e1
329""",
330        'm3/boards/zephyr/p1e1/twister.yaml': """\
331variants:
332  p1e1/s1:
333    name: Duplicate Platform
334""",
335        'm4/boards/zephyr/p1e2/board.yml': """\
336board:
337  extend: p2
338""",
339        'm4/boards/zephyr/p1e2/p1e2_s1_v1.yaml': """\
340identifier: p1e2/s1/v1
341""",
342        'm5/boards/zephyr/p2/p2-2.yaml': """\
343testing:
344  ć#@%!#!#^#@%@:1.0
345identifier: p2_2
346type: sim
347arch: x86
348vendor: vendor2
349""",
350        'm5/boards/zephyr/p2/board.yml': """\
351board:
352  extend: p2
353""",
354    }
355
356    for filename, content in tmp_files.items():
357        (tmp_path / filename).parent.mkdir(parents=True, exist_ok=True)
358        (tmp_path / filename).write_text(content)
359
360    roots = list(map(tmp_path.joinpath, roots))
361    with pytest.raises(type(expected_exception)) if \
362          expected_exception else nullcontext() as exception:
363        platforms = list(generate_platforms(board_roots=roots, soc_roots=roots, arch_roots=roots))
364
365    if expected_exception:
366        if expected_exception.args:
367            assert str(expected_exception) == str(exception.value)
368        return
369
370    platform_names = {platform.name for platform in platforms}
371    assert len(platforms) == len(platform_names)
372    assert platform_names == expected_platform_names
373
374    expected_data = {
375        'p1e1/s1': {
376            'aliases': ['p1e1/s1', 'p1e1'],
377            # m0/boards/zephyr/p1/board.yml
378            'vendor': 'zephyr',
379            # m0/boards/zephyr/p1/twister.yaml (base + variant)
380            'twister': False,
381            'arch': 'x86',
382            'type': 'native',
383        },
384        'p1e2/s1': {
385            'aliases': ['p1e2/s1', 'p1e2'],
386            # m0/boards/zephyr/p1/board.yml
387            'vendor': 'zephyr',
388            # m0/boards/zephyr/p1/twister.yaml (base + variant)
389            'sysbuild': True,
390            'arch': 'x86',
391            'type': 'native',
392        },
393        'p1e1/s1/v1': {
394            'aliases': ['p1e1/s1/v1'],
395            # m0/boards/zephyr/p1/board.yml
396            'vendor': 'zephyr',
397            # m0/boards/zephyr/p1/twister.yaml (base)
398            # m1/boards/zephyr/p1e1/twister.yaml (variant)
399            'default': True,
400            'arch': 'x86',
401            'type': 'native',
402        },
403        'p1e1/s1/v2': {
404            'aliases': ['p1e1/s1/v2'],
405            # m0/boards/zephyr/p1/board.yml
406            'vendor': 'zephyr',
407            # m0/boards/zephyr/p1/twister.yaml (base)
408            'arch': 'x86',
409            'type': 'native',
410        },
411        'p1e2/s1/v1': {
412            'aliases': ['p1e2/s1/v1'],
413            # m0/boards/zephyr/p1/board.yml
414            'vendor': 'zephyr',
415            # m0/boards/zephyr/p1/twister.yaml (base)
416            'arch': 'x86',
417            'type': 'native',
418        },
419        'p2/s1': {
420            'aliases': ['p2/s1', 'p2'],
421            # m0/boards/zephyr/p2/board.yml
422            'vendor': 'zephyr',
423            # m0/boards/zephyr/p2/p2.yaml
424            'default': True,
425            'arch': 'x86',
426            'type': 'sim',
427        },
428        'p2/s1/v1': {
429            'aliases': ['p2/s1/v1'],
430            # m0/boards/zephyr/p2/board.yml
431            'vendor': 'zephyr',
432            # m1/boards/zephyr/p2/p2_s1_v1.yaml
433        },
434        'p3@A/s2/c1': {
435            'aliases': ['p3@A/s2/c1', 'p3/s2/c1'],
436            # m0/boards/arm/p3/board.yml
437            'vendor': 'arm',
438            # m0/boards/arm/p3/twister.yaml (base + variant)
439            'sysbuild': True,
440            'timeout_multiplier': 2.71828,
441            'arch': 'arm',
442            'type': 'unit',
443        },
444        'p3@B/s2/c1': {
445            'aliases': ['p3@B/s2/c1'],
446            # m0/boards/arm/p3/board.yml
447            'vendor': 'arm',
448            # m0/boards/arm/p3/twister.yaml (base + variant)
449            'sysbuild': True,
450            'timeout_multiplier': 3.14159,
451            'arch': 'arm',
452            'type': 'unit',
453        },
454        'p3@A/s2/c2': {
455            'aliases': ['p3@A/s2/c2', 'p3/s2/c2'],
456            # m0/boards/arm/p3/board.yml
457            'vendor': 'arm',
458            # m0/boards/arm/p3/twister.yaml (base)
459            # m2/boards/misc/twister.yaml (variant)
460            'sysbuild': False,
461            'arch': 'arm',
462            'type': 'unit',
463        },
464        'p3@B/s2/c2': {
465            'aliases': ['p3@B/s2/c2'],
466            # m0/boards/arm/p3/board.yml
467            'vendor': 'arm',
468            # m0/boards/arm/p3/twister.yaml (base)
469            'sysbuild': True,
470            'arch': 'arm',
471            'type': 'unit',
472        },
473        'p4/s1': {
474            'aliases': ['p4/s1', 'p4'],
475            # m2/boards/misc/board.yml
476            'vendor': 'misc',
477            # m2/boards/misc/twister.yaml (base)
478            'arch': 'riscv',
479            'type': 'qemu',
480            'simulators': [Simulator({'name': 'qemu'})],
481            'simulation': 'qemu',
482        },
483    }
484
485    init_platform = Platform()
486    for platform in platforms:
487        expected_platform_data = expected_data[platform.name]
488        for attr, default in vars(init_platform).items():
489            if attr in {'name', 'normalized_name', 'supported_toolchains'}:
490                continue
491            expected = expected_platform_data.get(attr, default)
492            actual = getattr(platform, attr, None)
493            assert expected == actual, \
494                f"expected '{platform}.{attr}' to be '{expected}', was '{actual}'"
495