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