source: rtems-central/specview.py @ 2f4118f

Last change on this file since 2f4118f was 2f4118f, checked in by Sebastian Huber <sebastian.huber@…>, on 11/25/21 at 06:42:48

specview.py: Fix validation traversal

  • Property mode set to 100755
File size: 13.7 KB
Line 
1#!/usr/bin/env python
2# SPDX-License-Identifier: BSD-2-Clause
3""" Views the specification. """
4
5# Copyright (C) 2021 embedded brains GmbH (http://www.embedded-brains.de)
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27
28import argparse
29import itertools
30import sys
31from typing import Any, List, Optional, Set, Tuple
32
33from rtemsspec.items import EmptyItem, Item, ItemCache, ItemMapper, \
34    ItemGetValueContext, Link
35from rtemsspec.sphinxcontent import SphinxContent
36from rtemsspec.util import load_config
37from rtemsspec.transitionmap import Transition, TransitionMap
38
39_CHILD_ROLES = [
40    "requirement-refinement", "interface-ingroup", "interface-ingroup-hidden",
41    "interface-function", "appl-config-group-member", "glossary-member"
42]
43
44_PARENT_ROLES = ["function-implementation", "interface-enumerator"]
45
46
47def _get_value_dummy(_ctx: ItemGetValueContext) -> Any:
48    return ""
49
50
51_MAPPER = ItemMapper(EmptyItem())
52_MAPPER.add_get_value("requirement/functional/action:/text-template",
53                      _get_value_dummy)
54_MAPPER.add_get_value("glossary/term:/plural", _get_value_dummy)
55_MAPPER.add_get_value(
56    "requirement/non-functional/performance-runtime:/limit-kind",
57    _get_value_dummy)
58_MAPPER.add_get_value(
59    "requirement/non-functional/performance-runtime:/limit-condition",
60    _get_value_dummy)
61_MAPPER.add_get_value(
62    "requirement/non-functional/performance-runtime:/environment",
63    _get_value_dummy)
64
65
66def _visit_action_conditions(item: Item, name: str) -> None:
67    for index, condition in enumerate(item[name]):
68        for index_2, state in enumerate(condition["states"]):
69            _MAPPER.substitute(state["text"], item,
70                               f"{name}[{index}]/states[{index_2}]/text")
71
72
73def _visit_action(item: Item) -> None:
74    _visit_action_conditions(item, "pre-conditions")
75    _visit_action_conditions(item, "post-conditions")
76
77
78_VISITORS = {
79    "requirement/functional/action": _visit_action,
80}
81
82
83def _info(item: Item) -> str:
84    if not item.get("_pre_qualified", True):
85        return ", not-pre-qualified"
86    try:
87        if item["_validated"]:
88            return ""
89        return ", not-validated"
90    except KeyError:
91        return ""
92
93
94_TEXT_ATTRIBUTES = [
95    "brief",
96    "description",
97    "notes",
98    "rationale",
99    "test-brief",
100    "test-description",
101    "text",
102]
103
104
105def _visit_item(item: Item, level: int, role: Optional[str],
106                validated_filter: str) -> bool:
107    validated = item.get("_validated", True)
108    if validated_filter == "yes" and not validated:
109        return False
110    if validated_filter == "no" and validated:
111        return False
112    role_info = "" if role is None else f", role={role}"
113    print(
114        f"{'  ' * level}{item.uid} (type={item.type}{role_info}{_info(item)})")
115    for name in _TEXT_ATTRIBUTES:
116        if name in item:
117            _MAPPER.substitute(item[name], item)
118    try:
119        visitor = _VISITORS[item.type]
120    except KeyError:
121        pass
122    else:
123        visitor(item)
124    return True
125
126
127def _view_interface_placment(item: Item, level: int,
128                             validated_filter: str) -> None:
129    for link in item.links_to_children("interface-placement"):
130        if _visit_item(link.item, level, link.role, validated_filter):
131            _view_interface_placment(link.item, level + 1, validated_filter)
132
133
134def _view(item: Item, level: int, role: Optional[str], validated_filter: str,
135          enabled: List[str]) -> None:
136    if not item.is_enabled(enabled):
137        return
138    if not _visit_item(item, level, role, validated_filter):
139        return
140    for child in item.children("validation"):
141        if child.is_enabled(enabled):
142            _visit_item(child, level + 1, "validation", validated_filter)
143    _view_interface_placment(item, level + 1, validated_filter)
144    for link in item.links_to_children(_CHILD_ROLES):
145        _view(link.item, level + 1, link.role, validated_filter, enabled)
146    for link in item.links_to_parents(_PARENT_ROLES):
147        _view(link.item, level + 1, link.role, validated_filter, enabled)
148
149
150_VALIDATION_LEAF = [
151    "constraint",
152    "glossary/group",
153    "glossary/term",
154    "interface/appl-config-group",
155    "interface/container",
156    "interface/domain",
157    "interface/enum",
158    "interface/enumerator",
159    "interface/header-file",
160    "interface/register-block",
161    "interface/struct",
162    "interface/typedef",
163    "interface/union",
164    "interface/unspecified-define",
165    "interface/unspecified-function",
166    "requirement/functional/action",
167    "requirement/non-functional/performance-runtime",
168    "runtime-measurement-test",
169    "test-case",
170    "test-suite",
171    "validation",
172]
173
174_NOT_PRE_QUALIFIED = set([
175    "/acfg/constraint/option-not-pre-qualified",
176    "/constraint/constant-not-pre-qualified",
177    "/constraint/directive-not-pre-qualified",
178])
179
180
181def _is_pre_qualified(item: Item) -> bool:
182    return not bool(
183        set(parent.uid for parent in item.parents("constraint")).intersection(
184            _NOT_PRE_QUALIFIED))
185
186
187def _validation_count(item: Item, enabled: List[str]) -> int:
188    return len(
189        list(child for child in item.children("validation")
190             if child.is_enabled(enabled)))
191
192
193def _validate(item: Item, enabled: List[str]) -> bool:
194    count = _validation_count(item, enabled)
195    validated = True
196    for child in item.children(_CHILD_ROLES):
197        if child.is_enabled(enabled):
198            validated = _validate(child, enabled) and validated
199            count += 1
200    for parent in item.parents(_PARENT_ROLES):
201        if parent.is_enabled(enabled):
202            validated = _validate(parent, enabled) and validated
203            count += 1
204    pre_qualified = _is_pre_qualified(item)
205    item["_pre_qualified"] = pre_qualified
206    if count == 0:
207        if not pre_qualified:
208            validated = True
209        else:
210            validated = item.type in _VALIDATION_LEAF
211    item["_validated"] = validated
212    return validated
213
214
215def _no_validation(item: Item, path: List[str],
216                   enabled: List[str]) -> List[str]:
217    path_2 = path + [item.uid]
218    if not item.is_enabled(enabled):
219        return path_2[:-1]
220    leaf = _validation_count(item, enabled) == 0
221    for child in item.children(_CHILD_ROLES):
222        path_2 = _no_validation(child, path_2, enabled)
223        leaf = False
224    for parent in item.parents(_PARENT_ROLES):
225        path_2 = _no_validation(parent, path_2, enabled)
226        leaf = False
227    if leaf and not item.get("_validated", True):
228        for index, component in enumerate(path_2):
229            if component:
230                print(f"{'  ' * index}{component}")
231            path_2[index] = ""
232    return path_2[:-1]
233
234
235def _gather_interface_placement(item: Item, spec: Set) -> None:
236    for child in item.children("interface-placement"):
237        spec.add(child)
238        _gather_interface_placement(child, spec)
239
240
241def _gather(item: Item, spec: Set) -> None:
242    spec.add(item)
243    _gather_interface_placement(item, spec)
244    for child in item.children("validation"):
245        spec.add(child)
246    for child in item.children(_CHILD_ROLES):
247        _gather(child, spec)
248    for parent in item.parents(_PARENT_ROLES):
249        _gather(parent, spec)
250
251
252def _add_link(item_cache: ItemCache, child: Item, link: Link) -> None:
253    parent = item_cache[child.to_abs_uid(link["uid"])]
254    parent.add_link_to_child(Link(child, link))
255
256
257def _process_test_cases(item_cache: ItemCache) -> None:
258    for item in item_cache.all.values():
259        if item.type == "test-case":
260            for actions in item["test-actions"]:
261                for checks in actions["checks"]:
262                    for link in checks["links"]:
263                        _add_link(item_cache, item, link)
264                for link in actions["links"]:
265                    _add_link(item_cache, item, link)
266
267
268def _make_row(transition_map: TransitionMap, map_idx: int,
269              variant: Transition) -> Tuple[str, ...]:
270    return tuple(
271        itertools.chain(
272            [str(map_idx), str(variant.desc_idx)],
273            (transition_map.pre_co_idx_st_idx_to_st_name(co_idx, st_idx)
274             for co_idx, st_idx in enumerate(
275                 transition_map.map_idx_to_pre_co_states(
276                     map_idx, variant.pre_cond_na))),
277            (transition_map.post_co_idx_st_idx_to_st_name(co_idx, st_idx)
278             for co_idx, st_idx in enumerate(variant.post_cond))))
279
280
281def _action_table(enabled: List[str], item: Item) -> None:
282    rows = [
283        tuple(
284            itertools.chain(["Entry", "Descriptor"],
285                            (condition["name"]
286                             for condition in item["pre-conditions"]),
287                            (condition["name"]
288                             for condition in item["post-conditions"])))
289    ]
290    transition_map = TransitionMap(item)
291    for map_idx, variant in transition_map.get_variants(enabled):
292        rows.append(_make_row(transition_map, map_idx, variant))
293    content = SphinxContent()
294    content.add_simple_table(rows)
295    print(str(content))
296
297
298def _to_name(transition_map, co_idx: int, st_idx: int) -> str:
299    return (f"{transition_map.post_co_idx_to_co_name(co_idx)} = "
300            f"{transition_map.post_co_idx_st_idx_to_st_name(co_idx, st_idx)}")
301
302
303def _action_list(enabled: List[str], item: Item) -> None:
304    transition_map = TransitionMap(item)
305    for post_cond, pre_conds in transition_map.get_post_conditions(enabled):
306        print("")
307        if post_cond[0]:
308            print(transition_map.skip_idx_to_name(post_cond[0]))
309        else:
310            names = []  # type: List[str]
311            for co_idx, st_idx in enumerate(post_cond[1:]):
312                st_name = transition_map.post_co_idx_st_idx_to_st_name(
313                    co_idx, st_idx)
314                if st_name != "NA":
315                    co_name = transition_map.post_co_idx_to_co_name(co_idx)
316                    names.append(f"{co_name} = {st_name}")
317            print(", ".join(names))
318        for row in pre_conds:
319            entries = []
320            for co_idx, co_states in enumerate(row):
321                co_name = transition_map.pre_co_idx_to_co_name(co_idx)
322                states = [
323                    transition_map.pre_co_idx_st_idx_to_st_name(
324                        co_idx, st_idx) for st_idx in set(co_states)
325                ]
326                if len(states) == 1:
327                    if states[0] != "NA":
328                        entries.append(f"{co_name} = {states[0]}")
329                else:
330                    entries.append(f"{co_name} = {{ " + ", ".join(states) +
331                                   " }")
332            print("")
333            print("    * " + ", ".join(entries))
334
335
336def main() -> None:
337    """ Views the specification. """
338    parser = argparse.ArgumentParser()
339    parser.add_argument('--filter',
340                        choices=[
341                            "none", "orphan", "no-validation", "action-table",
342                            "action-list"
343                        ],
344                        type=str.lower,
345                        default="none",
346                        help="filter the items")
347    parser.add_argument('--validated',
348                        choices=["all", "yes", "no"],
349                        type=str.lower,
350                        default="all",
351                        help="filter the items by the validated status")
352    parser.add_argument(
353        "--enabled",
354        help=("a comma separated list of enabled options used to evaluate "
355              "enabled-by expressions"))
356    parser.add_argument("UIDs",
357                        metavar="UID",
358                        nargs="*",
359                        help="an UID of a specification item")
360    args = parser.parse_args(sys.argv[1:])
361    enabled = args.enabled.split(",") if args.enabled else []
362    config = load_config("config.yml")
363    item_cache = ItemCache(config["spec"])
364    _process_test_cases(item_cache)
365    root = item_cache["/req/root"]
366
367    if args.filter == "none":
368        _validate(root, enabled)
369        _view(root, 0, None, args.validated, enabled)
370    elif args.filter == "action-table":
371        for uid in args.UIDs:
372            _action_table(enabled, item_cache[uid])
373    elif args.filter == "action-list":
374        for uid in args.UIDs:
375            _action_list(enabled, item_cache[uid])
376    elif args.filter == "orphan":
377        spec = set()  # type: Set[Item]
378        _gather(root, spec)
379        for item in item_cache.all.values():
380            if item["type"] in ["build", "glossary", "spec"]:
381                continue
382            if item not in spec:
383                print(item.uid)
384    elif args.filter == "no-validation":
385        _validate(root, enabled)
386        _no_validation(root, [], enabled)
387
388
389if __name__ == "__main__":
390    main()
Note: See TracBrowser for help on using the repository browser.