source: rtems-central/rtemsqual/validation.py @ 05246b3

Last change on this file since 05246b3 was 99c6449, checked in by Sebastian Huber <sebastian.huber@…>, on 05/09/20 at 15:45:46

content: Change gap after indent

  • Property mode set to 100644
File size: 10.1 KB
Line 
1# SPDX-License-Identifier: BSD-2-Clause
2""" This module provides functions for the generation of validation tests. """
3
4# Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26
27import itertools
28import os
29import string
30from typing import Dict, List, Mapping
31
32from rtemsqual.content import CContent
33from rtemsqual.items import Item, ItemCache
34
35ItemMap = Dict[str, Item]
36
37
38class StepWrapper(Mapping[str, object]):
39    """ Test step wrapper. """
40    def __init__(self):
41        self._step = 0
42
43    @property
44    def steps(self):
45        """ The count of test steps. """
46        return self._step
47
48    def __getitem__(self, name):
49        if name == "step":
50            step = self._step
51            self._step = step + 1
52            return step
53        raise KeyError
54
55    def __iter__(self):
56        raise StopIteration
57
58    def __len__(self):
59        return 1
60
61
62def _designator(name: str) -> str:
63    return name.replace(" ", "")
64
65
66def _add_ingroup(content: CContent, items: List[Item], prefix: str,
67                 key: str) -> None:
68    content.add_ingroup(
69        [f"{prefix}{_designator(item[key])}" for item in items])
70
71
72def _add_test_case_description(content: CContent, item: Item,
73                               test_case_to_suites: Dict[str, List[Item]],
74                               identifier: str, name: str) -> None:
75    with content.defgroup_block(f"RTEMSTestCase{identifier}", name):
76        _add_ingroup(content, test_case_to_suites[item.uid], "RTEMSTestSuite",
77                     "test-suite-name")
78        content.add(["@brief Test Case", "", "@{"])
79
80
81def _add_test_case_action_description(content: CContent, item: Item) -> None:
82    actions = item["test-case-actions"]
83    if actions:
84        content.add("This test case performs the following actions:")
85        for action in actions:
86            content.add(content.wrap(action["description"], intro="- "))
87            for check in action["checks"]:
88                content.add(content.wrap(check["description"], intro="  - "))
89
90
91def _generate_test_case_actions(item: Item, steps: StepWrapper) -> CContent:
92    content = CContent()
93    for action in item["test-case-actions"]:
94        content.add(action["action"])
95        for check in action["checks"]:
96            content.append(string.Template(check["check"]).substitute(steps))
97    return content
98
99
100def _generate_test_case(content: CContent, item: Item,
101                        test_case_to_suites: Dict[str, List[Item]]) -> None:
102    name = item["test-case-name"]
103    desi = _designator(name)
104    _add_test_case_description(content, item, test_case_to_suites, desi, name)
105    content.add(item["test-case-support"])
106    with content.function_block(f"void T_case_body_{desi}(void)"):
107        content.add_brief_description(item["test-case-brief"])
108        content.add(content.wrap(item["test-case-description"]))
109        _add_test_case_action_description(content, item)
110    fixture = item["test-case-fixture"]
111    if fixture:
112        content.append(f"T_TEST_CASE_FIXTURE({desi}, &{fixture})")
113    else:
114        content.append(f"T_TEST_CASE({desi})")
115    content.append("{")
116    content.gap = False
117    with content.indent():
118        content.add(item["test-case-prologue"])
119        steps = StepWrapper()
120        action_content = _generate_test_case_actions(item, steps)
121        if steps.steps > 0:
122            content.add(f"T_plan({steps.steps});")
123        content.add(action_content)
124        content.add(item["test-case-epilogue"])
125    content.add(["}", "", "/** @} */"])
126
127
128def _generate_test_suite(content: CContent, item: Item) -> None:
129    name = item["test-suite-name"]
130    with content.defgroup_block(f"RTEMSTestSuite{_designator(name)}", name):
131        content.add(["@ingroup RTEMSTestSuites", "", "@brief Test Suite"])
132        content.add(content.wrap(item["test-suite-description"]))
133        content.add("@{")
134    content.add(item["test-suite-code"])
135    content.add("/** @} */")
136
137
138class SourceFile:
139    """ A test source file. """
140    def __init__(self, filename: str):
141        """ Initializes a test source file. """
142        self._file = filename
143        self._test_suites = []  # type: List[Item]
144        self._test_cases = []  # type: List[Item]
145
146    @property
147    def test_suites(self) -> List[Item]:
148        """ The test suites of the source file. """
149        return self._test_suites
150
151    @property
152    def test_cases(self) -> List[Item]:
153        """ The test cases of the source file. """
154        return self._test_cases
155
156    def add_test_suite(self, test_suite: Item) -> None:
157        """ Adds a test suite to the source file. """
158        self._test_suites.append(test_suite)
159
160    def add_test_case(self, test_case: Item) -> None:
161        """ Adds a test case to the source file. """
162        self._test_cases.append(test_case)
163
164    def generate(self, base_directory: str,
165                 test_case_to_suites: Dict[str, List[Item]]) -> None:
166        """
167        Generates the source file and the corresponding build specification.
168        """
169        content = CContent()
170        includes = []  # type: List[str]
171        local_includes = []  # type: List[str]
172        for item in itertools.chain(self._test_suites, self._test_cases):
173            includes.extend(item["includes"])
174            local_includes.extend(item["local-includes"])
175            content.register_license_and_copyrights_of_item(item)
176        content.add_spdx_license_identifier()
177        with content.file_block():
178            _add_ingroup(content, self._test_suites, "RTEMSTestSuite",
179                         "test-suite-name")
180            _add_ingroup(content, self._test_cases, "RTEMSTestCase",
181                         "test-case-name")
182        content.add_copyrights_and_licenses()
183        content.add_have_config()
184        content.add_includes(includes)
185        content.add_includes(local_includes, local=True)
186        content.add_includes(["t.h"])
187        for item in sorted(self._test_cases,
188                           key=lambda x: x["test-case-name"]):
189            _generate_test_case(content, item, test_case_to_suites)
190        for item in sorted(self._test_suites,
191                           key=lambda x: x["test-suite-name"]):
192            _generate_test_suite(content, item)
193        content.write(os.path.join(base_directory, self._file))
194
195
196class TestProgram:
197    """ A test program. """
198    def __init__(self, item: Item):
199        """ Initializes a test program. """
200        self._item = item
201        self._source_files = []  # type: List[SourceFile]
202
203    @property
204    def source_files(self) -> List[SourceFile]:
205        """ The source files of the test program. """
206        return self._source_files
207
208    def add_source_files(self, source_files: Dict[str, SourceFile]) -> None:
209        """
210        Adds the source files of the test program which are present in the
211        source file map.
212        """
213        for filename in self._item["source"]:
214            source_file = source_files.get(filename, None)
215            if source_file is not None:
216                self._source_files.append(source_file)
217
218
219def _get_source_file(filename: str,
220                     source_files: Dict[str, SourceFile]) -> SourceFile:
221    return source_files.setdefault(filename, SourceFile(filename))
222
223
224def _gather_items(item: Item, source_files: Dict[str, SourceFile],
225                  test_programs: List[TestProgram]) -> None:
226    for child in item.children():
227        _gather_items(child, source_files, test_programs)
228    if item["type"] == "test-suite":
229        src = _get_source_file(item["source"], source_files)
230        src.add_test_suite(item)
231    elif item["type"] == "test-case":
232        src = _get_source_file(item["source"], source_files)
233        src.add_test_case(item)
234    elif item["type"] == "build" and item["build-type"] == "test-program":
235        test_programs.append(TestProgram(item))
236
237
238def generate(config: dict, item_cache: ItemCache) -> None:
239    """
240    Generates source files and build specification items for validation test
241    suites and test cases according to the configuration.
242
243    :param config: A dictionary with configuration entries.
244    :param item_cache: The specification item cache containing the validation
245                       test suites and test cases.
246    """
247    source_files = {}  # type: Dict[str, SourceFile]
248    test_programs = []  # type: List[TestProgram]
249    for item in item_cache.top_level.values():
250        _gather_items(item, source_files, test_programs)
251
252    test_case_to_suites = {}  # type: Dict[str, List[Item]]
253    for test_program in test_programs:
254        test_program.add_source_files(source_files)
255        test_suites = []  # type: List[Item]
256        for source_file in test_program.source_files:
257            test_suites.extend(source_file.test_suites)
258        for source_file in test_program.source_files:
259            for test_case in source_file.test_cases:
260                test_case_to_suites.setdefault(test_case.uid,
261                                               []).extend(test_suites)
262
263    for src in source_files.values():
264        src.generate(config["base-directory"], test_case_to_suites)
Note: See TracBrowser for help on using the repository browser.