source: rtems-central/rtemsspec/validation.py @ e49c759

Last change on this file since e49c759 was e49c759, checked in by Sebastian Huber <sebastian.huber@…>, on 07/15/20 at 08:04:25

Rename "rtemsqual" in "rtemsspec"

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