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