1#!/usr/bin/env python3
2# Copyright (c) 2023 Intel Corporation
3#
4# SPDX-License-Identifier: Apache-2.0
5"""
6Tests for cmakecache.py classes' methods
7"""
8
9from contextlib import nullcontext
10from unittest import mock
11
12import pytest
13from twisterlib.cmakecache import CMakeCache, CMakeCacheEntry
14
15TESTDATA_1 = [
16    ('ON', True),
17    ('YES', True),
18    ('TRUE', True),
19    ('Y', True),
20    ('OFF', False),
21    ('NO', False),
22    ('FALSE', False),
23    ('N', False),
24    ('IGNORE', False),
25    ('NOTFOUND', False),
26    ('', False),
27    ('DUMMY-NOTFOUND', False),
28    ('1', True),
29    ('0', False),
30    ('I AM NOT A PROPER VALUE', ValueError),
31]
32
33
34@pytest.mark.parametrize(
35    'cmake_bool, expected_bool',
36    TESTDATA_1,
37    ids=[t[0] for t in TESTDATA_1]
38)
39def test_cmakecacheentry_to_bool(cmake_bool, expected_bool):
40    with pytest.raises(expected_bool) if \
41     not isinstance(expected_bool, bool) else nullcontext() as exception:
42        b = CMakeCacheEntry._to_bool(cmake_bool)
43
44    if exception is None:
45        assert b == expected_bool
46    else:
47        assert str(exception.value) == f'invalid bool {cmake_bool}'
48
49
50TESTDATA_2 = [
51    (
52        '// I am a comment',
53        None
54    ),
55    (
56        '# I am a comment too',
57        None
58    ),
59    (
60        '  	\r\n	     ',
61        None
62    ),
63    (
64        'DUMMY:WRONG_TYPE=???',
65        None
66    ),
67    (
68        'DUMMY_VALUE_NAME1:STRING=I am a dummy string',
69        CMakeCacheEntry('DUMMY_VALUE_NAME1', 'I am a dummy string')
70    ),
71    (
72       'DUMMY_VALUE_NAME2:STRING=list_el1;list_el2;list_el3',
73        CMakeCacheEntry(
74            'DUMMY_VALUE_NAME2',
75            ['list_el1', 'list_el2', 'list_el3']
76        )
77    ),
78    (
79        'DUMMY_VALUE_NAME3:INTERNAL=I am a dummy internal string',
80        CMakeCacheEntry('DUMMY_VALUE_NAME3', 'I am a dummy internal string')),
81    (
82        'DUMMY_VALUE_NAME4:INTERNAL=list_el1;list_el2',
83        CMakeCacheEntry('DUMMY_VALUE_NAME4', ['list_el1', 'list_el2'])
84    ),
85    (
86        'DUMMY_VALUE_NAME5:FILEPATH=/path/to/dir',
87        CMakeCacheEntry('DUMMY_VALUE_NAME5', '/path/to/dir')
88    ),
89    (
90        'DUMMY_VALUE_NAME6:PATH=/path/to/dir/file.txt',
91        CMakeCacheEntry('DUMMY_VALUE_NAME6', '/path/to/dir/file.txt')),
92    (
93        'DUMMY_VALUE_NAME7:BOOL=Y',
94        CMakeCacheEntry('DUMMY_VALUE_NAME7', True)
95    ),
96    (
97        'DUMMY_VALUE_NAME8:BOOL=FALSE',
98        CMakeCacheEntry('DUMMY_VALUE_NAME8', False)
99    ),
100    (
101        'DUMMY_VALUE_NAME9:BOOL=NOT_A_BOOL',
102        ValueError(
103          (
104              'invalid bool NOT_A_BOOL',
105              'on line 7: DUMMY_VALUE_NAME9:BOOL=NOT_A_BOOL'
106          )
107        )
108    ),
109]
110
111
112@pytest.mark.parametrize(
113    'cmake_line, expected',
114    TESTDATA_2,
115    ids=[
116        '// comment',
117        '# comment',
118        'whitespace',
119        'unrecognised type',
120        'string',
121        'string list',
122        'internal string',
123        'internal list',
124        'filepath',
125        'path',
126        'true bool',
127        'false bool',
128        'not a bool'
129    ]
130)
131def test_cmakecacheentry_from_line(cmake_line, expected):
132    cmake_line_number = 7
133
134    with pytest.raises(type(expected)) if \
135     isinstance(expected, Exception) else nullcontext() as exception:
136        entry = CMakeCacheEntry.from_line(cmake_line, cmake_line_number)
137
138    if exception is not None:
139        assert repr(exception.value) == repr(expected)
140        return
141
142    if expected is None:
143        assert entry is None
144        return
145
146    assert entry.name == expected.name
147    assert entry.value == expected.value
148
149
150TESTDATA_3 = [
151    (
152        CMakeCacheEntry('DUMMY_NAME1', 'dummy value'),
153        'CMakeCacheEntry(name=DUMMY_NAME1, value=dummy value)'
154    ),
155    (
156        CMakeCacheEntry('DUMMY_NAME2', False),
157        'CMakeCacheEntry(name=DUMMY_NAME2, value=False)'
158    )
159]
160
161
162@pytest.mark.parametrize(
163    'cmake_cache_entry, expected',
164    TESTDATA_3,
165    ids=['string value', 'bool value']
166)
167def test_cmakecacheentry_str(cmake_cache_entry, expected):
168    assert str(cmake_cache_entry) == expected
169
170
171def test_cmakecache_load():
172    file_data = (
173        'DUMMY_NAME1:STRING=First line\n'
174        '//Comment on the second line\n'
175        'DUMMY_NAME2:STRING=Third line\n'
176        'DUMMY_NAME3:STRING=Fourth line\n'
177    )
178
179    with mock.patch('builtins.open', mock.mock_open(read_data=file_data)):
180        cache = CMakeCache.from_file('dummy/path/CMakeCache.txt')
181
182    assert cache.cache_file == 'dummy/path/CMakeCache.txt'
183
184    expected = [
185        ('DUMMY_NAME1', 'First line'),
186        ('DUMMY_NAME2', 'Third line'),
187        ('DUMMY_NAME3', 'Fourth line')
188    ]
189
190    for expect in reversed(expected):
191        item = cache._entries.popitem()
192
193        assert item[0] == expect[0]
194        assert item[1].name == expect[0]
195        assert item[1].value == expect[1]
196
197
198def test_cmakecache_get():
199    file_data = 'DUMMY_NAME:STRING=Dummy value'
200
201    with mock.patch('builtins.open', mock.mock_open(read_data=file_data)):
202        cache = CMakeCache.from_file('dummy/path/CMakeCache.txt')
203
204    good_val = cache.get('DUMMY_NAME')
205
206    assert good_val == 'Dummy value'
207
208    bad_val = cache.get('ANOTHER_NAME')
209
210    assert bad_val is None
211
212    bad_val = cache.get('ANOTHER_NAME', default='No such value')
213
214    assert bad_val == 'No such value'
215
216
217TESTDATA_4 = [
218    ('STRING=el1;el2;el3;el4', True, ['el1', 'el2', 'el3', 'el4']),
219    ('STRING=dummy value', True, ['dummy value']),
220    ('STRING=', True, []),
221    ('BOOL=True', True, RuntimeError),
222    ('STRING=dummy value', False, []),
223]
224
225
226@pytest.mark.parametrize(
227    'value, correct_get, expected',
228    TESTDATA_4,
229    ids=['list', 'single value', 'empty', 'exception', 'get failure']
230)
231def test_cmakecache_get_list(value, correct_get, expected):
232    file_data = f'DUMMY_NAME:{value}'
233
234    with mock.patch('builtins.open', mock.mock_open(read_data=file_data)):
235        cache = CMakeCache.from_file('dummy/path/CMakeCache.txt')
236
237    with pytest.raises(expected) if \
238     isinstance(expected, type) and issubclass(expected, Exception) else \
239     nullcontext() as exception:
240        res = cache.get_list('DUMMY_NAME') if \
241         correct_get else cache.get_list('ANOTHER_NAME')
242
243    if exception is None:
244        assert res == expected
245
246
247def test_cmakecache_dunders():
248    file_data = f'DUMMY_NAME:STRING=dummy value'
249
250    with mock.patch('builtins.open', mock.mock_open(read_data=file_data)):
251        cache = CMakeCache.from_file('dummy/path/CMakeCache.txt')
252
253    assert len(list(cache.__iter__())) == 1
254
255    cache.__setitem__(
256        'ANOTHER_NAME',
257        CMakeCacheEntry('ANOTHER_NAME', 'another value')
258    )
259
260    assert cache.__contains__('ANOTHER_NAME')
261    assert cache.__getitem__('ANOTHER_NAME') == 'another value'
262
263    cache.__delitem__('ANOTHER_NAME')
264
265    assert not cache.__contains__('ANOTHER_NAME')
266
267    assert len(list(cache.__iter__())) == 1
268
269    with pytest.raises(TypeError):
270        cache.__setitem__('WRONG_TYPE', 'Yet Another Dummy Value')
271