source: rtems-central/rtemsspec/validation.py @ 55e17d2

Last change on this file since 55e17d2 was 55e17d2, checked in by Sebastian Huber <sebastian.huber@…>, on 07/27/20 at 12:54:13

validation: Add ability to skip action transitions

  • Property mode set to 100644
File size: 32.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 math
29import os
30from typing import Any, Dict, List, NamedTuple, Optional, Tuple
31
32from rtemsspec.content import CContent, CInclude, enabled_by_to_exp, \
33    ExpressionMapper
34from rtemsspec.items import Item, ItemCache, ItemGetValueContext, ItemMapper
35
36ItemMap = Dict[str, Item]
37
38
39class _CodeMapper(ItemMapper):
40    def get_value(self, ctx: ItemGetValueContext) -> Any:
41        if ctx.type_path_key == "requirement/functional/action:/test-run":
42            return f"{ctx.item['test-name'].replace(' ', '')}_Run"
43        raise KeyError
44
45
46class _TextMapper(ItemMapper):
47    def __init__(self, item: Item):
48        super().__init__(item)
49        self._step = 0
50
51    @property
52    def steps(self):
53        """ The count of test steps. """
54        return self._step
55
56    def reset_step(self):
57        """ Resets the test step counter. """
58        self._step = 0
59
60    def map(self, identifier: str) -> Tuple[Item, Any]:
61        if identifier == "step":
62            step = self._step
63            self._step = step + 1
64            return self._item, str(step)
65        return super().map(identifier)
66
67
68def _add_ingroup(content: CContent, items: List["_TestItem"]) -> None:
69    content.add_ingroup([item.group_identifier for item in items])
70
71
72class _TestItem:
73    """ A test item with a defaul implementation for test cases. """
74    def __init__(self, item: Item):
75        self._item = item
76        self._ident = self.name.replace(" ", "")
77        self._code_mapper = _CodeMapper(self._item)
78        self._text_mapper = _TextMapper(self._item)
79
80    def __getitem__(self, key: str):
81        return self._item[key]
82
83    @property
84    def item(self) -> Item:
85        """ Returns the item. """
86        return self._item
87
88    @property
89    def uid(self) -> str:
90        """ Returns the item UID. """
91        return self._item.uid
92
93    @property
94    def ident(self) -> str:
95        """ Returns the test identifier. """
96        return self._ident
97
98    @property
99    def name(self) -> str:
100        """ Returns the name. """
101        return self._item["name"]
102
103    @property
104    def includes(self) -> List[str]:
105        """ Returns the list of includes. """
106        return self._item["includes"]
107
108    @property
109    def local_includes(self) -> List[str]:
110        """ Returns the list of local includes. """
111        return self._item["local-includes"]
112
113    @property
114    def group_identifier(self) -> str:
115        """ Returns the group identifier. """
116        return f"RTEMSTestCase{self.ident}"
117
118    def substitute_code(self, text: Optional[str]) -> str:
119        """ Performs a variable substitution for code. """
120        return self._code_mapper.substitute(text)
121
122    def substitute_text(self,
123                        text: Optional[str],
124                        prefix: Optional[str] = None) -> str:
125        """
126        Performs a variable substitution for text with an optinal prefix.
127        """
128        if prefix:
129            return self._text_mapper.substitute_with_prefix(text, prefix)
130        return self._text_mapper.substitute(text)
131
132    def add_test_case_description(
133            self, content: CContent,
134            test_case_to_suites: Dict[str, List["_TestItem"]]) -> None:
135        """ Adds the test case description. """
136        with content.defgroup_block(self.group_identifier, self.name):
137            _add_ingroup(content, test_case_to_suites[self.uid])
138            content.add(["@brief Test Case", "", "@{"])
139
140    def _add_test_case_action_description(self, content: CContent) -> None:
141        actions = self["actions"]
142        if actions:
143            content.add("This test case performs the following actions:")
144            for action in actions:
145                content.wrap(self.substitute_text(action["description"]),
146                             initial_indent="- ")
147                for check in action["checks"]:
148                    content.wrap(self.substitute_text(check["description"]),
149                                 initial_indent="  - ",
150                                 subsequent_indent="    ")
151
152    def _generate_test_case_actions(self) -> CContent:
153        content = CContent()
154        for action in self["actions"]:
155            content.add(self.substitute_code(action["action"]))
156            for check in action["checks"]:
157                content.append(self.substitute_text(check["check"]))
158        return content
159
160    def generate(self, content: CContent, _base_directory: str,
161                 test_case_to_suites: Dict[str, List["_TestItem"]]) -> None:
162        """ Generates the content. """
163        self.add_test_case_description(content, test_case_to_suites)
164        content.add(self.substitute_code(self["support"]))
165        with content.function_block(f"void T_case_body_{self.ident}( void )"):
166            content.add_brief_description(self.substitute_text(self["brief"]))
167            content.wrap(self.substitute_text(self["description"]))
168            self._add_test_case_action_description(content)
169        content.gap = False
170        params = [f"{self.ident}"]
171        fixture = self["fixture"]
172        if fixture:
173            params.append(f"&{fixture}")
174            name = "T_TEST_CASE_FIXTURE"
175        else:
176            name = "T_TEST_CASE"
177        with content.function("", name, params):
178            content.add(self.substitute_code(self["prologue"]))
179            self._text_mapper.reset_step()
180            action_content = self._generate_test_case_actions()
181            if self._text_mapper.steps > 0:
182                content.add(f"T_plan({self._text_mapper.steps});")
183            content.add(action_content)
184            content.add(self.substitute_code(self["epilogue"]))
185        content.add("/** @} */")
186
187
188class _TestSuiteItem(_TestItem):
189    """ A test suite item. """
190    @property
191    def group_identifier(self) -> str:
192        return f"RTEMSTestSuite{self.ident}"
193
194    def generate(self, content: CContent, _base_directory: str,
195                 _test_case_to_suites: Dict[str, List[_TestItem]]) -> None:
196        with content.defgroup_block(self.group_identifier, self.name):
197            content.add(["@ingroup RTEMSTestSuites", "", "@brief Test Suite"])
198            content.wrap(self.substitute_text(self["description"]))
199            content.add("@{")
200        content.add(self.substitute_code(self["code"]))
201        content.add("/** @} */")
202
203
204class _Transition(NamedTuple):
205    """
206    A transition to a set of post conditions with an enabled by expression.
207    """
208    enabled_by: str
209    post_conditions: Tuple[int, ...]
210    info: str
211
212
213_DirectiveIndexToEnum = Tuple[Tuple[str, ...], ...]
214_TransitionMap = List[List[_Transition]]
215
216
217def _directive_state_to_index(
218        conditions: List[Any]) -> Tuple[Dict[str, int], ...]:
219    return tuple(
220        dict((state["name"], index)
221             for index, state in enumerate(condition["states"]))
222        for condition in conditions)
223
224
225def _directive_index_to_enum(prefix: str,
226                             conditions: List[Any]) -> _DirectiveIndexToEnum:
227    return tuple(
228        tuple([f"{prefix}_{condition['name']}"] + [
229            f"{prefix}_{condition['name']}_{state['name']}"
230            for state in condition["states"]
231        ] + [f"{prefix}_{condition['name']}_NA"])
232        for index, condition in enumerate(conditions))
233
234
235def _directive_add_enum(content: CContent,
236                        index_to_enum: _DirectiveIndexToEnum) -> None:
237    for enum in index_to_enum:
238        content.add("typedef enum {")
239        with content.indent():
240            content.add(",\n".join(enum[1:]))
241        content.add(f"}} {enum[0]};")
242
243
244class _TestDirectiveItem(_TestItem):
245    """ A test directive item. """
246
247    # pylint: disable=too-many-instance-attributes
248    def __init__(self, item: Item):
249        super().__init__(item)
250        self._pre_condition_count = len(item["pre-conditions"])
251        self._post_condition_count = len(item["post-conditions"])
252        self._pre_index_to_enum = _directive_index_to_enum(
253            f"{self.ident}_Pre", item["pre-conditions"])
254        self._post_index_to_enum = _directive_index_to_enum(
255            f"{self.ident}_Post", item["post-conditions"])
256        self._pre_state_to_index = _directive_state_to_index(
257            item["pre-conditions"])
258        self._post_state_to_index = _directive_state_to_index(
259            item["post-conditions"])
260        self._pre_index_to_condition = dict(
261            (index, condition)
262            for index, condition in enumerate(item["pre-conditions"]))
263        self._post_index_to_name = dict(
264            (index, condition["name"])
265            for index, condition in enumerate(item["post-conditions"]))
266
267    @property
268    def name(self) -> str:
269        return self._item["test-name"]
270
271    @property
272    def context(self) -> str:
273        """ Returns the test case context type. """
274        return f"{self._ident}_Context"
275
276    @property
277    def includes(self) -> List[str]:
278        return self._item["test-includes"]
279
280    @property
281    def local_includes(self) -> List[str]:
282        return self._item["test-local-includes"]
283
284    def _add_pre_condition_descriptions(self, content: CContent) -> None:
285        for condition in self["pre-conditions"]:
286            content.add("static const char * const "
287                        f"{self.ident}_PreDesc_{condition['name']}[] = {{")
288            with content.indent():
289                content.add(",\n".join(f"\"{state['name']}\""
290                                       for state in condition["states"]))
291            content.add("};")
292        content.add("static const char * const * const "
293                    f"{self.ident}_PreDesc[] = {{")
294        with content.indent():
295            content.add(",\n".join([
296                f"{self.ident}_PreDesc_{condition['name']}"
297                for condition in self["pre-conditions"]
298            ] + ["NULL"]))
299        content.add("};")
300
301    def _add_context(self, content: CContent, header: Dict[str, Any]) -> None:
302        with content.doxygen_block():
303            content.add_brief_description(
304                f"Test context for {self.name} test case.")
305        content.append("typedef struct {")
306        with content.indent():
307            for info in self["test-context"]:
308                content.add_description_block(info["brief"],
309                                              info["description"])
310                content.add(f"{info['member'].strip()};")
311            for param in self._get_run_params(header):
312                content.add_description_block(
313                    "This member contains a copy of the corresponding "
314                    f"{self.ident}_Run() parameter.", None)
315                content.add(f"{param};")
316            content.add_description_block(
317                "This member defines the pre-condition states "
318                "for the next action.", None)
319            content.add(f"size_t pcs[ {self._pre_condition_count} ];")
320            content.add_description_block(
321                "This member indicates if the test action loop "
322                "is currently executed.", None)
323            content.add("bool in_action_loop;")
324        content.add([
325            f"}} {self.context};", "", f"static {self.context}",
326            f"  {self.ident}_Instance;"
327        ])
328
329    def _add_scope_body(self, content: CContent) -> None:
330        with content.condition("ctx->in_action_loop"):
331            content.call_function(
332                None, "T_get_scope",
333                [f"{self.ident}_PreDesc", "buf", "n", "ctx->pcs"])
334
335    def _add_fixture_scope(self, content: CContent) -> None:
336        params = ["void *arg", "char *buf", "size_t n"]
337        with content.function("static void", f"{self.ident}_Scope", params):
338            content.add([f"{self.context} *ctx;", "", "ctx = arg;"])
339            self._add_scope_body(content)
340
341    def _add_fixture_method(self, content: CContent,
342                            info: Optional[Dict[str, Optional[str]]],
343                            name: str) -> str:
344        if not info:
345            return "NULL"
346        method = f"{self.ident}_{name}"
347        wrap = f"{method}_Wrap"
348        content.add_description_block(info["brief"], info["description"])
349        with content.function("static void", method, [f"{self.context} *ctx"]):
350            content.add(self.substitute_code(info["code"]))
351        with content.function("static void", wrap, ["void *arg"]):
352            content.add([
353                f"{self.context} *ctx;", "", "ctx = arg;",
354                "ctx->in_action_loop = false;", f"{method}( ctx );"
355            ])
356        return wrap
357
358    def _add_transitions(self, condition_index: int, map_index: int,
359                         transition: Dict[str,
360                                          Any], transition_map: _TransitionMap,
361                         pre_cond_not_applicables: List[str],
362                         post_cond: Tuple[int, ...]) -> None:
363        # pylint: disable=too-many-arguments
364        if condition_index < self._pre_condition_count:
365            condition = self._pre_index_to_condition[condition_index]
366            state_count = len(condition["states"])
367            map_index *= state_count
368            states = transition["pre-conditions"][condition["name"]]
369            if isinstance(states, str):
370                assert states in ["all", "N/A"]
371                for index in range(state_count):
372                    self._add_transitions(
373                        condition_index + 1, map_index + index, transition,
374                        transition_map,
375                        pre_cond_not_applicables + [str(int(states == "N/A"))],
376                        post_cond)
377            else:
378                for state in states:
379                    self._add_transitions(
380                        condition_index + 1, map_index +
381                        self._pre_state_to_index[condition_index][state],
382                        transition, transition_map,
383                        pre_cond_not_applicables + ["0"], post_cond)
384        else:
385            enabled_by = enabled_by_to_exp(transition["enabled-by"],
386                                           ExpressionMapper())
387            assert enabled_by != "1" or not transition_map[map_index]
388            transition_map[map_index].append(
389                _Transition(enabled_by, post_cond,
390                            "    " + ", ".join(pre_cond_not_applicables)))
391
392    def _get_transition_map(self) -> _TransitionMap:
393        transition_count = 1
394        for condition in self["pre-conditions"]:
395            state_count = len(condition["states"])
396            assert state_count > 0
397            transition_count *= state_count
398        transition_map = [list() for _ in range(transition_count)
399                          ]  # type: _TransitionMap
400        for transition in self["transition-map"]:
401            if isinstance(transition["post-conditions"], dict):
402                info = ["0"]
403                post_cond = tuple(
404                    self._post_state_to_index[index][
405                        transition["post-conditions"][
406                            self._post_index_to_name[index]]]
407                    for index in range(self._post_condition_count))
408            else:
409                info = ["1"]
410                post_cond = tuple(
411                    len(self._post_state_to_index[index]) - 1
412                    for index in range(self._post_condition_count))
413            self._add_transitions(0, 0, transition, transition_map, info,
414                                  post_cond)
415        return transition_map
416
417    def _post_condition_enumerators(self, conditions: Any) -> str:
418        return ",\n".join(
419            f"    {self._post_index_to_enum[index][condition + 1]}"
420            for index, condition in enumerate(conditions))
421
422    def _add_transition_map(self, content: CContent) -> None:
423        transition_map = self._get_transition_map()
424        content.add([
425            "static const uint8_t "
426            f"{self.ident}_TransitionMap[][ {self._post_condition_count} ]"
427            " = {", "  {"
428        ])
429        map_elements = []
430        info_elements = []
431        for transistions in transition_map:
432            assert transistions[0].enabled_by == "1"
433            if len(transistions) == 1:
434                map_elements.append(
435                    self._post_condition_enumerators(
436                        transistions[0].post_conditions))
437                info_elements.append(transistions[0].info)
438            else:
439                ifelse = "#if "
440                map_enumerators = []  # type: List[str]
441                info_enumerators = []  # type: List[str]
442                for variant in transistions[1:]:
443                    map_enumerators.append(ifelse + variant.enabled_by)
444                    info_enumerators.append(ifelse + variant.enabled_by)
445                    map_enumerators.append(
446                        self._post_condition_enumerators(
447                            variant.post_conditions))
448                    info_enumerators.append(variant.info)
449                    ifelse = "#elif "
450                map_enumerators.append("#else")
451                info_enumerators.append("#else")
452                map_enumerators.append(
453                    self._post_condition_enumerators(
454                        transistions[0].post_conditions))
455                info_enumerators.append(transistions[0].info)
456                map_enumerators.append("#endif")
457                info_enumerators.append("#endif")
458                map_elements.append("\n".join(map_enumerators))
459                info_elements.append("\n".join(info_enumerators))
460        content.append(["\n  }, {\n".join(map_elements), "  }", "};"])
461        pre_bits = 2**max(math.ceil(math.log2(self._pre_condition_count + 1)),
462                          3)
463        content.add("static const struct {")
464        with content.indent():
465            content.append(f"uint{pre_bits}_t Skip : 1;")
466            for condition in self["pre-conditions"]:
467                content.append(
468                    f"uint{pre_bits}_t Pre_{condition['name']}_NA : 1;")
469        content.add([f"}} {self.ident}_TransitionInfo[] = {{", "  {"])
470        content.append(["\n  }, {\n".join(info_elements), "  }", "};"])
471
472    def _add_action(self, content: CContent) -> None:
473        with content.condition(f"{self.ident}_TransitionInfo[ index ].Skip"):
474            content.append(["++index;", "continue;"])
475        content.add_blank_line()
476        for index, enum in enumerate(self._pre_index_to_enum):
477            content.append(f"{enum[0]}_Prepare( ctx, ctx->pcs[ {index} ] );")
478        content.append(self.substitute_code(self["test-action"]))
479        transition_map = f"{self.ident}_TransitionMap"
480        for index, enum in enumerate(self._post_index_to_enum):
481            content.append([
482                f"{enum[0]}_Check(", "  ctx,",
483                f"  {transition_map}[ index ][ {index} ]", ");"
484            ])
485        content.append("++index;")
486
487    def _add_for_loops(self, content: CContent, index: int) -> None:
488        if index < self._pre_condition_count:
489            var = f"ctx->pcs[ {index} ]"
490            begin = self._pre_index_to_enum[index][1]
491            end = self._pre_index_to_enum[index][-1]
492            with content.for_loop(f"{var} = {begin}", f"{var} < {end}",
493                                  f"++{var}"):
494                name = self._item['pre-conditions'][index]["name"]
495                pre_na = f"{self.ident}_TransitionInfo[ index ].Pre_{name}_NA"
496                with content.condition(pre_na):
497                    content.append(f"{var} = {end};")
498                    content.append(f"index += ( {end} - 1 )")
499                    for index_2 in range(index + 1, self._pre_condition_count):
500                        with content.indent():
501                            content.append(
502                                f"* {self._pre_index_to_enum[index_2][-1]}")
503                    content.lines[-1] += ";"
504                self._add_for_loops(content, index + 1)
505        else:
506            self._add_action(content)
507
508    def _add_test_case(self, content: CContent, header: Dict[str,
509                                                             Any]) -> None:
510        fixture = f"{self.ident}_Fixture"
511        prologue = CContent()
512        epilogue = CContent()
513        if header:
514            content.add(f"static T_fixture_node {self.ident}_Node;")
515            ret = "void"
516            name = f"{self.ident}_Run"
517            params = self._get_run_params(header)
518            prologue.add([f"{self.context} *ctx;", "size_t index;"])
519            prologue.call_function("ctx =", "T_push_fixture",
520                                   [f"&{self.ident}_Node", f"&{fixture}"])
521            prologue.add([
522                f"ctx->{param['name']} = {param['name']};"
523                for param in header["run-params"]
524            ] + ["ctx->in_action_loop = true;", "index = 0;"])
525            epilogue.add("T_pop_fixture();")
526            align = True
527        else:
528            with content.function_block(
529                    f"void T_case_body_{self.ident}( void )"):
530                content.add_brief_description(self["test-brief"])
531                content.wrap(self["test-description"])
532            content.gap = False
533            ret = ""
534            name = "T_TEST_CASE_FIXTURE"
535            params = [f"{self.ident}", f"&{fixture}"]
536            prologue.add([
537                f"{self.context} *ctx;", "size_t index;", "",
538                "ctx = T_fixture_context();", "ctx->in_action_loop = true;",
539                "index = 0;"
540            ])
541            align = False
542        with content.function(ret, name, params, align=align):
543            content.add(prologue)
544            self._add_for_loops(content, 0)
545            content.add(epilogue)
546
547    def _add_handler(self, content: CContent, conditions: List[Any],
548                     index_to_enum: _DirectiveIndexToEnum,
549                     action: str) -> None:
550        for condition_index, condition in enumerate(conditions):
551            enum = index_to_enum[condition_index]
552            handler = f"{enum[0]}_{action}"
553            params = [f"{self.context} *ctx", f"{enum[0]} state"]
554            with content.function("static void", handler, params):
555                content.add(self.substitute_code(condition["test-prologue"]))
556                content.add("switch ( state ) {")
557                with content.indent():
558                    for state_index, state in enumerate(condition["states"]):
559                        content.add(f"case {enum[state_index + 1]}: {{")
560                        with content.indent():
561                            content.add(
562                                self.substitute_code(state["test-code"]))
563                            content.append("break;")
564                        content.add("}")
565                    content.add(f"case {enum[-1]}:")
566                    with content.indent():
567                        content.append("break;")
568                content.add("}")
569                content.add(self.substitute_code(condition["test-epilogue"]))
570
571    def _get_run_params(self, header: Optional[Dict[str, Any]]) -> List[str]:
572        if not header:
573            return []
574        return [
575            self.substitute_text(param["specifier"],
576                                 f"test-header/run-params[{index}]")
577            for index, param in enumerate(header["run-params"])
578        ]
579
580    def _generate_header_body(self, content: CContent,
581                              header: Dict[str, Any]) -> None:
582        _directive_add_enum(content, self._pre_index_to_enum)
583        _directive_add_enum(content, self._post_index_to_enum)
584        content.add(self.substitute_code(header["code"]))
585        with content.doxygen_block():
586            content.add_brief_description(self["test-brief"])
587            content.wrap(self["test-description"])
588            content.add_param_description(header["run-params"])
589        content.gap = False
590        content.declare_function("void", f"{self.ident}_Run",
591                                 self._get_run_params(header))
592
593    def _generate_header(self, base_directory: str, header: Dict[str,
594                                                                 Any]) -> None:
595        content = CContent()
596        content.register_license_and_copyrights_of_item(self._item)
597        content.prepend_spdx_license_identifier()
598        with content.file_block():
599            content.add_ingroup([self.group_identifier])
600        content.add_copyrights_and_licenses()
601        with content.header_guard(os.path.basename(header["target"])):
602            content.add_includes(list(map(CInclude, header["includes"])))
603            content.add_includes(list(map(CInclude, header["local-includes"])),
604                                 local=True)
605            with content.extern_c():
606                with content.add_to_group(self.group_identifier):
607                    self._generate_header_body(content, header)
608        content.write(os.path.join(base_directory, header["target"]))
609
610    def generate(self, content: CContent, base_directory: str,
611                 test_case_to_suites: Dict[str, List[_TestItem]]) -> None:
612        self.add_test_case_description(content, test_case_to_suites)
613        header = self["test-header"]
614        if header:
615            self._generate_header(base_directory, header)
616        else:
617            _directive_add_enum(content, self._pre_index_to_enum)
618            _directive_add_enum(content, self._post_index_to_enum)
619        self._add_context(content, header)
620        self._add_pre_condition_descriptions(content)
621        content.add(self.substitute_code(self["test-support"]))
622        self._add_handler(content, self["pre-conditions"],
623                          self._pre_index_to_enum, "Prepare")
624        self._add_handler(content, self["post-conditions"],
625                          self._post_index_to_enum, "Check")
626        setup = self._add_fixture_method(content, self["test-setup"], "Setup")
627        stop = self._add_fixture_method(content, self["test-stop"], "Stop")
628        teardown = self._add_fixture_method(content, self["test-teardown"],
629                                            "Teardown")
630        self._add_fixture_scope(content)
631        content.add([
632            f"static T_fixture {self.ident}_Fixture = {{",
633            f"  .setup = {setup},", f"  .stop = {stop},",
634            f"  .teardown = {teardown},", f"  .scope = {self.ident}_Scope,",
635            f"  .initial_context = &{self.ident}_Instance", "};"
636        ])
637        self._add_transition_map(content)
638        self._add_test_case(content, header)
639        content.add("/** @} */")
640
641
642class _SourceFile:
643    """ A test source file. """
644    def __init__(self, filename: str):
645        """ Initializes a test source file. """
646        self._file = filename
647        self._test_suites = []  # type: List[_TestItem]
648        self._test_cases = []  # type: List[_TestItem]
649
650    @property
651    def test_suites(self) -> List[_TestItem]:
652        """ The test suites of the source file. """
653        return self._test_suites
654
655    @property
656    def test_cases(self) -> List[_TestItem]:
657        """ The test cases of the source file. """
658        return self._test_cases
659
660    def add_test_suite(self, test_suite: Item) -> None:
661        """ Adds a test suite to the source file. """
662        self._test_suites.append(_TestSuiteItem(test_suite))
663
664    def add_test_case(self, test_case: Item) -> None:
665        """ Adds a test case to the source file. """
666        self._test_cases.append(_TestItem(test_case))
667
668    def add_test_directive(self, test_directive: Item) -> None:
669        """ Adds a test directive to the source file. """
670        self._test_cases.append(_TestDirectiveItem(test_directive))
671
672    def generate(self, base_directory: str,
673                 test_case_to_suites: Dict[str, List[_TestItem]]) -> None:
674        """
675        Generates the source file and the corresponding build specification.
676        """
677        content = CContent()
678        includes = []  # type: List[CInclude]
679        local_includes = []  # type: List[CInclude]
680        for item in itertools.chain(self._test_suites, self._test_cases):
681            includes.extend(map(CInclude, item.includes))
682            local_includes.extend(map(CInclude, item.local_includes))
683            content.register_license_and_copyrights_of_item(item.item)
684        content.prepend_spdx_license_identifier()
685        with content.file_block():
686            _add_ingroup(content, self._test_suites)
687            _add_ingroup(content, self._test_cases)
688        content.add_copyrights_and_licenses()
689        content.add_have_config()
690        content.add_includes(includes)
691        content.add_includes(local_includes, local=True)
692        content.add_includes([CInclude("rtems/test.h")])
693        for item in sorted(self._test_cases, key=lambda x: x.name):
694            item.generate(content, base_directory, test_case_to_suites)
695        for item in sorted(self._test_suites, key=lambda x: x.name):
696            item.generate(content, base_directory, test_case_to_suites)
697        content.write(os.path.join(base_directory, self._file))
698
699
700class _TestProgram:
701    """ A test program. """
702    def __init__(self, item: Item):
703        """ Initializes a test program. """
704        self._item = item
705        self._source_files = []  # type: List[_SourceFile]
706
707    @property
708    def source_files(self) -> List[_SourceFile]:
709        """ The source files of the test program. """
710        return self._source_files
711
712    def add_source_files(self, source_files: Dict[str, _SourceFile]) -> None:
713        """
714        Adds the source files of the test program which are present in the
715        source file map.
716        """
717        for filename in self._item["source"]:
718            source_file = source_files.get(filename, None)
719            if source_file is not None:
720                self._source_files.append(source_file)
721
722
723def _get_source_file(filename: str,
724                     source_files: Dict[str, _SourceFile]) -> _SourceFile:
725    return source_files.setdefault(filename, _SourceFile(filename))
726
727
728def _gather_items(item: Item, source_files: Dict[str, _SourceFile],
729                  test_programs: List[_TestProgram]) -> None:
730    if item["type"] == "test-suite":
731        src = _get_source_file(item["target"], source_files)
732        src.add_test_suite(item)
733    elif item["type"] == "test-case":
734        src = _get_source_file(item["target"], source_files)
735        src.add_test_case(item)
736    elif item["type"] == "requirement" and item[
737            "requirement-type"] == "functional" and item[
738                "functional-type"] == "action":
739        src = _get_source_file(item["test-target"], source_files)
740        src.add_test_directive(item)
741    elif item["type"] == "build" and item["build-type"] == "test-program":
742        test_programs.append(_TestProgram(item))
743
744
745def generate(config: dict, item_cache: ItemCache) -> None:
746    """
747    Generates source files and build specification items for validation test
748    suites and test cases according to the configuration.
749
750    :param config: A dictionary with configuration entries.
751    :param item_cache: The specification item cache containing the validation
752                       test suites and test cases.
753    """
754    source_files = {}  # type: Dict[str, _SourceFile]
755    test_programs = []  # type: List[_TestProgram]
756    for item in item_cache.all.values():
757        _gather_items(item, source_files, test_programs)
758
759    test_case_to_suites = {}  # type: Dict[str, List[_TestItem]]
760    for test_program in test_programs:
761        test_program.add_source_files(source_files)
762        test_suites = []  # type: List[_TestItem]
763        for source_file in test_program.source_files:
764            test_suites.extend(source_file.test_suites)
765        for source_file in test_program.source_files:
766            for test_case in source_file.test_cases:
767                test_case_to_suites.setdefault(test_case.uid,
768                                               []).extend(test_suites)
769
770    for src in source_files.values():
771        src.generate(config["base-directory"], test_case_to_suites)
Note: See TracBrowser for help on using the repository browser.