1#!/usr/bin/env python3
2# Copyright (c) 2024 Intel Corporation
3# Copyright (c) 2024 Arm Limited (or its affiliates). All rights reserved.
4#
5# SPDX-License-Identifier: Apache-2.0
6"""
7Tests for config_parser.py
8"""
9
10import os
11import pytest
12import mock
13import scl
14
15from twisterlib.config_parser import TwisterConfigParser, extract_fields_from_arg_list, ConfigurationError
16from contextlib import nullcontext
17
18def test_extract_single_field_from_string_argument():
19    target_fields = {"FIELD1"}
20    arg_list = "FIELD1=value1 FIELD2=value2 FIELD3=value3"
21    extracted_fields, other_fields = extract_fields_from_arg_list(
22        target_fields, arg_list)
23
24    assert extracted_fields == {"FIELD1": ["value1"]}
25    assert other_fields == ["FIELD2=value2", "FIELD3=value3"]
26
27
28def test_no_fields_to_extract():
29    target_fields = set()
30    arg_list = "arg1 arg2 arg3"
31
32    extracted_fields, other_fields = extract_fields_from_arg_list(
33        target_fields, arg_list)
34
35    assert extracted_fields == {}
36    assert other_fields == ["arg1", "arg2", "arg3" ]
37
38
39def test_missing_fields():
40    target_fields = {"CONF_FILE", "OVERLAY_CONFIG", "DTC_OVERLAY_FILE"}
41    arg_list = "arg1 arg2 arg3"
42    extracted_fields, other_fields = extract_fields_from_arg_list(
43        target_fields, arg_list)
44
45    assert extracted_fields == {"CONF_FILE": [], "OVERLAY_CONFIG": [], "DTC_OVERLAY_FILE": []}
46    assert other_fields == ["arg1", "arg2", "arg3" ]
47
48def test_load_yaml_with_extra_args_and_retrieve_scenario_data(zephyr_base):
49    filename = "test_data.yaml"
50
51    yaml_data = '''
52    tests:
53      scenario1:
54        tags: ['tag1', 'tag2']
55        extra_args:
56        - '--CONF_FILE=file1.conf'
57        - '--OVERLAY_CONFIG=config1.conf'
58        filter: 'filter1'
59    common:
60      filter: 'filter2'
61    '''
62
63    loaded_schema = scl.yaml_load(
64        os.path.join(zephyr_base, 'scripts', 'schemas','twister', 'testsuite-schema.yaml')
65    )
66
67    with mock.patch('builtins.open', mock.mock_open(read_data=yaml_data)):
68        parser = TwisterConfigParser(filename, loaded_schema)
69        parser.load()
70
71    scenario_data = parser.get_scenario('scenario1')
72    scenario_common = parser.common
73
74    assert scenario_data['tags'] == {'tag1', 'tag2'}
75    assert scenario_data['extra_args'] == ['--CONF_FILE=file1.conf', '--OVERLAY_CONFIG=config1.conf']
76    assert scenario_common == {'filter': 'filter2'}
77
78
79def test_default_values(zephyr_base):
80    filename = "test_data.yaml"
81
82    yaml_data = '''
83    tests:
84      scenario1:
85        tags: 'tag1'
86        extra_args: ''
87    '''
88
89    loaded_schema = scl.yaml_load(
90        os.path.join(zephyr_base, 'scripts', 'schemas', 'twister','testsuite-schema.yaml')
91    )
92
93    with mock.patch('builtins.open', mock.mock_open(read_data=yaml_data)):
94        parser = TwisterConfigParser(filename, loaded_schema)
95        parser.load()
96
97    expected_scenario_data = { 'type': 'integration',
98        'extra_args': [],
99        'extra_configs': [],
100        'extra_conf_files': [],
101        'extra_overlay_confs': [],
102        'extra_dtc_overlay_files': [],
103        'required_snippets': [],
104        'build_only': False,
105        'build_on_all': False,
106        'skip': False, 'slow': False,
107        'timeout': 60,
108        'min_ram': 8,
109        'modules': [],
110        'depends_on': set(),
111        'min_flash': 32,
112        'arch_allow': set(),
113        'arch_exclude': set(),
114        'extra_sections': [],
115        'integration_platforms': [],
116        'ignore_faults': False,
117        'ignore_qemu_crash': False,
118        'testcases': [],
119        'platform_type': [],
120        'platform_exclude': set(),
121        'platform_allow': set(),
122        'platform_key': [],
123        'toolchain_exclude': set(),
124        'toolchain_allow': set(),
125        'filter': '',
126        'levels': [],
127        'harness': 'test',
128        'harness_config': {},
129        'seed': 0, 'sysbuild': False
130        }
131
132    assert expected_scenario_data.items() <= expected_scenario_data.items()
133
134@pytest.mark.parametrize(
135    'value, typestr, expected, expected_warning',
136    [
137        (' hello ', 'str', 'hello', None),
138        ('3.14', 'float', 3.14, None),
139        ('10', 'int', 10, None),
140        ('True', 'bool', 'True', None),          # do-nothing cast
141        ('key: val', 'map', 'key: val', None),   # do-nothing cast
142        ('test', 'int', ValueError, None),
143        ('test', 'unknown', ConfigurationError, None),
144        ([ '1', '2', '3' ], 'set', { '1', '2', '2','3' }, None),
145    ],
146    ids=['str to str', 'str to float', 'str to int', 'str to bool', 'str to map',
147         'invalid', 'to unknown', "list to set"]
148)
149
150def test_cast_value(zephyr_base, value, typestr, expected, expected_warning):
151    loaded_schema = scl.yaml_load(
152        os.path.join(zephyr_base, 'scripts', 'schemas', 'twister','testsuite-schema.yaml')
153    )
154
155    parser = TwisterConfigParser("config.yaml", loaded_schema)
156    with mock.patch('warnings.warn') as warn_mock:
157        with pytest.raises(expected) if isinstance(expected, type) and issubclass(expected, Exception) else nullcontext():
158            result = parser._cast_value(value, typestr)
159            assert result == expected
160            if expected_warning:
161                warn_mock.assert_called_once_with(*expected_warning, stacklevel=mock.ANY)
162            else:
163                warn_mock.assert_not_called()
164
165def test_load_invalid_test_config_yaml(zephyr_base):
166    filename = "test_data.yaml"
167
168    yaml_data = '''
169    gibberish data
170    '''
171
172    loaded_schema = scl.yaml_load(
173        os.path.join(zephyr_base, 'scripts', 'schemas','twister', 'test-config-schema.yaml')
174    )
175
176    with mock.patch('builtins.open', mock.mock_open(read_data=yaml_data)):
177        parser = TwisterConfigParser(filename, loaded_schema)
178        with pytest.raises(Exception):
179            parser.load()
180
181
182def test_load_yaml_with_no_scenario_data(zephyr_base):
183    filename = "test_data.yaml"
184
185    yaml_data = '''
186    tests:
187      common:
188        extra_args: '--CONF_FILE=file2.conf --OVERLAY_CONFIG=config2.conf'
189    '''
190
191    loaded_schema = scl.yaml_load(
192        os.path.join(zephyr_base, 'scripts', 'schemas','twister', 'testsuite-schema.yaml')
193    )
194
195    with mock.patch('builtins.open', mock.mock_open(read_data=yaml_data)):
196        parser = TwisterConfigParser(filename, loaded_schema)
197        parser.load()
198
199    with pytest.raises(KeyError):
200        scenario_data = parser.get_scenario('scenario1')
201        assert scenario_data is None
202