source: rtems-central/rtemsqual/validation.py @ 9dad293

Last change on this file since 9dad293 was 9dad293, checked in by Sebastian Huber <sebastian.huber@…>, on 04/28/20 at 09:29:48

items: Use generator functions for item links

This enables an easy access to link attributes.

  • Property mode set to 100644
File size: 10.7 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    for desi in sorted(set(_designator(item[key]) for item in items)):
69        content.add(f""" * @ingroup {prefix}{desi}
70""")
71
72
73def _add_test_case_description(content: CContent, item: Item,
74                               test_case_to_suites: Dict[str, List[Item]],
75                               name: str, desi: str) -> None:
76    content.add(f"""
77/**
78 * @defgroup RTEMSTestCase{desi} {name}
79 *
80""")
81    _add_ingroup(content, test_case_to_suites[item.uid], "RTEMSTestSuite",
82                 "test-suite-name")
83    content.add(f""" *
84 * @brief Test Case
85 *
86 * @{{
87 */
88""")
89
90
91def _add_test_case_action_description(content: CContent, item: Item) -> None:
92    actions = item["test-case-actions"]
93    if actions:
94        content.add_comment_content(
95            "This test case performs the following actions:")
96        for action in actions:
97            content.add_comment_content(action["description"], intro="- ")
98            for check in action["checks"]:
99                content.add_comment_content(check["description"], intro="  - ")
100
101
102def _generate_test_case_actions(item: Item, steps: StepWrapper) -> CContent:
103    content = CContent()
104    first = True
105    for action in item["test-case-actions"]:
106        if not first:
107            content.add_blank_line()
108        first = False
109        content.add_lines(action["action"], indent=1)
110        for check in action["checks"]:
111            the_check = string.Template(check["check"]).substitute(steps)
112            content.add_lines(the_check, indent=1)
113    return content
114
115
116def _generate_test_case(content: CContent, item: Item,
117                        test_case_to_suites: Dict[str, List[Item]]) -> None:
118    name = item["test-case-name"]
119    desi = _designator(name)
120    _add_test_case_description(content, item, test_case_to_suites, name, desi)
121    content.add_line_block(item["test-case-support"])
122    content.add(f"""
123/**
124 * @fn void T_case_body_{desi}(void)
125""")
126    content.add_brief_description(item["test-case-brief"])
127    content.add_comment_content(item["test-case-description"])
128    _add_test_case_action_description(content, item)
129    content.add(f""" */
130""")
131    fixture = item["test-case-fixture"]
132    if fixture:
133        content.add_lines(f"T_TEST_CASE_FIXTURE({desi}, &{fixture})")
134    else:
135        content.add_lines(f"T_TEST_CASE({desi})")
136    content.add_lines("{")
137    prologue = item["test-case-prologue"]
138    first = True
139    if prologue:
140        first = False
141        content.add_lines(prologue, indent=1)
142    steps = StepWrapper()
143    action_content = _generate_test_case_actions(item, steps)
144    if steps.steps > 0:
145        if not first:
146            content.add_blank_line()
147        first = False
148        content.add_lines(f"T_plan({steps.steps});", indent=1)
149    if action_content.content:
150        if not first:
151            content.add_blank_line()
152        first = False
153        content.add(action_content.content)
154    epilogue = item["test-case-epilogue"]
155    if epilogue:
156        if not first:
157            content.add_blank_line()
158        content.add_lines(epilogue, indent=1)
159    content.add(f"""}}
160
161/** @}} */
162""")
163
164
165def _generate_test_suite(content: CContent, item: Item) -> None:
166    name = item["test-suite-name"]
167    content.add(f"""
168/**
169 * @defgroup RTEMSTestSuite{_designator(name)} {name}
170 *
171 * @ingroup RTEMSTestSuites
172 *
173 * @brief Test Suite
174""")
175    content.add_comment_content(item["test-suite-description"])
176    content.add(f""" *
177 * @{{
178 */
179
180{item["test-suite-code"]}
181/** @}} */
182""")
183
184
185class SourceFile:
186    """ A test source file. """
187    def __init__(self, filename: str):
188        """ Initializes a test source file. """
189        self._file = filename
190        self._test_suites = []  # type: List[Item]
191        self._test_cases = []  # type: List[Item]
192
193    @property
194    def test_suites(self) -> List[Item]:
195        """ The test suites of the source file. """
196        return self._test_suites
197
198    @property
199    def test_cases(self) -> List[Item]:
200        """ The test cases of the source file. """
201        return self._test_cases
202
203    def add_test_suite(self, test_suite: Item) -> None:
204        """ Adds a test suite to the source file. """
205        self._test_suites.append(test_suite)
206
207    def add_test_case(self, test_case: Item) -> None:
208        """ Adds a test case to the source file. """
209        self._test_cases.append(test_case)
210
211    def generate(self, base_directory: str,
212                 test_case_to_suites: Dict[str, List[Item]]) -> None:
213        """
214        Generates the source file and the corresponding build specification.
215        """
216        content = CContent()
217        includes = []  # type: List[str]
218        local_includes = []  # type: List[str]
219        for item in itertools.chain(self._test_suites, self._test_cases):
220            includes.extend(item["includes"])
221            local_includes.extend(item["local-includes"])
222            item.register_license_and_copyrights(content)
223        content.add_spdx_license_identifier()
224        content.add(f"""
225/**
226 * @file
227 *
228""")
229        _add_ingroup(content, self._test_suites, "RTEMSTestSuite",
230                     "test-suite-name")
231        _add_ingroup(content, self._test_cases, "RTEMSTestCase",
232                     "test-case-name")
233        content.add(f""" */
234""")
235        content.add_copyrights_and_licenses()
236        content.add_have_config()
237        content.add_includes(includes)
238        content.add_includes(local_includes, local=True)
239        content.add_includes(["t.h"])
240        for item in sorted(self._test_cases,
241                           key=lambda x: x["test-case-name"]):
242            _generate_test_case(content, item, test_case_to_suites)
243        for item in sorted(self._test_suites,
244                           key=lambda x: x["test-suite-name"]):
245            _generate_test_suite(content, item)
246        content.write(os.path.join(base_directory, self._file))
247
248
249class TestProgram:
250    """ A test program. """
251    def __init__(self, item: Item):
252        """ Initializes a test program. """
253        self._item = item
254        self._source_files = []  # type: List[SourceFile]
255
256    @property
257    def source_files(self) -> List[SourceFile]:
258        """ The source files of the test program. """
259        return self._source_files
260
261    def add_source_files(self, source_files: Dict[str, SourceFile]) -> None:
262        """
263        Adds the source files of the test program which are present in the
264        source file map.
265        """
266        for filename in self._item["source"]:
267            source_file = source_files.get(filename, None)
268            if source_file is not None:
269                self._source_files.append(source_file)
270
271
272def _get_source_file(filename: str,
273                     source_files: Dict[str, SourceFile]) -> SourceFile:
274    return source_files.setdefault(filename, SourceFile(filename))
275
276
277def _gather_items(item: Item, source_files: Dict[str, SourceFile],
278                  test_programs: List[TestProgram]) -> None:
279    for child in item.children():
280        _gather_items(child, source_files, test_programs)
281    if item["type"] == "test-suite":
282        src = _get_source_file(item["source"], source_files)
283        src.add_test_suite(item)
284    elif item["type"] == "test-case":
285        src = _get_source_file(item["source"], source_files)
286        src.add_test_case(item)
287    elif item["type"] == "build" and item["build-type"] == "test-program":
288        test_programs.append(TestProgram(item))
289
290
291def generate(config: dict, item_cache: ItemCache) -> None:
292    """
293    Generates source files and build specification items for validation test
294    suites and test cases according to the configuration.
295
296    :param config: A dictionary with configuration entries.
297    :param item_cache: The specification item cache containing the validation
298                       test suites and test cases.
299    """
300    source_files = {}  # type: Dict[str, SourceFile]
301    test_programs = []  # type: List[TestProgram]
302    for item in item_cache.top_level.values():
303        _gather_items(item, source_files, test_programs)
304
305    test_case_to_suites = {}  # type: Dict[str, List[Item]]
306    for test_program in test_programs:
307        test_program.add_source_files(source_files)
308        test_suites = []  # type: List[Item]
309        for source_file in test_program.source_files:
310            test_suites.extend(source_file.test_suites)
311        for source_file in test_program.source_files:
312            for test_case in source_file.test_cases:
313                test_case_to_suites.setdefault(test_case.uid,
314                                               []).extend(test_suites)
315
316    for src in source_files.values():
317        src.generate(config["base-directory"], test_case_to_suites)
Note: See TracBrowser for help on using the repository browser.