Coverage for lobster/common/policy_builder.py: 87%

43 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-09-22 04:43 +0000

1#!/usr/bin/env python3 

2# 

3# LOBSTER - Lightweight Open BMW Software Traceability Evidence Report 

4# Copyright (C) 2026 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) 

5# 

6# This program is free software: you can redistribute it and/or modify 

7# it under the terms of the GNU Affero General Public License as 

8# published by the Free Software Foundation, either version 3 of the 

9# License, or (at your option) any later version. 

10# 

11# This program is distributed in the hope that it will be useful, but 

12# WITHOUT ANY WARRANTY; without even the implied warranty of 

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 

14# Affero General Public License for more details. 

15# 

16# You should have received a copy of the GNU Affero General Public 

17# License along with this program. If not, see 

18# <https://www.gnu.org/licenses/>. 

19 

20# Format-agnostic validation + construction layer: turns a RawPolicy (built 

21# by a format-specific reader) into the OrderedDict[str, LevelDefinition] 

22# consumed by lobster/common/report.py. This is where all semantic checks 

23# (duplicate names, missing source files, self-trace, unknown trace 

24# targets/requires levels) live, so they apply identically regardless of 

25# which config file format was parsed. 

26 

27import os.path 

28import collections 

29from typing import OrderedDict 

30 

31from lobster.common.errors import LOBSTER_Error 

32from lobster.common.level_definition import LevelDefinition 

33from lobster.common.raw_policy import RawPolicy 

34 

35 

36def build_tracing_policy( 

37 raw_policy: RawPolicy, 

38) -> OrderedDict[str, LevelDefinition]: 

39 levels = collections.OrderedDict() 

40 

41 # First pass: create every level up front, so later passes can refer to 

42 # a level regardless of whether it is declared before or after the 

43 # level referencing it. 

44 for raw_level in raw_policy.levels: 

45 if raw_level.name in levels: 45 ↛ 46line 45 didn't jump to line 46 because the condition on line 45 was never true

46 raise LOBSTER_Error(raw_level.name_loc, "duplicate declaration") 

47 levels[raw_level.name] = LevelDefinition( 

48 name=raw_level.name, 

49 kind=raw_level.kind, 

50 ) 

51 

52 # Second pass: validate and populate source files and trace-to targets. 

53 for raw_level in raw_policy.levels: 

54 item = levels[raw_level.name] 

55 

56 for raw_source in raw_level.source: 

57 if not os.path.isfile(raw_source.file): 

58 raise LOBSTER_Error(raw_source.loc, 

59 f"cannot find file {raw_source.file}") 

60 item.source.append({"file": raw_source.file}) 

61 

62 for raw_trace in raw_level.trace_to: 

63 if raw_trace.target == raw_level.name: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

64 raise LOBSTER_Error(raw_trace.loc, "cannot trace to yourself") 

65 if raw_trace.target not in levels: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true

66 raise LOBSTER_Error(raw_trace.loc, 

67 f"unknown item {raw_trace.target}") 

68 levels[raw_trace.target].needs_tracing_down = True 

69 item.traces.append(raw_trace.target) 

70 item.needs_tracing_up = True 

71 

72 # Third pass: resolve "requires" links now that every level's `traces` 

73 # list (populated above) is complete. 

74 for raw_level in raw_policy.levels: 

75 item = levels[raw_level.name] 

76 item.breakdown_requirements = [] 

77 if raw_level.requires: 

78 for or_group in raw_level.requires: 

79 new_or_group = [] 

80 for alt in or_group: 

81 if alt.name not in levels: 81 ↛ 82line 81 didn't jump to line 82 because the condition on line 81 was never true

82 raise LOBSTER_Error(alt.loc, f"unknown level {alt.name}") 

83 if item.name not in levels[alt.name].traces: 83 ↛ 84line 83 didn't jump to line 84 because the condition on line 83 was never true

84 raise LOBSTER_Error( 

85 alt.loc, 

86 f"{alt.name} cannot trace to {item.name} items", 

87 ) 

88 new_or_group.append(alt.name) 

89 item.breakdown_requirements.append(new_or_group) 

90 else: 

91 for src in levels.values(): 

92 if item.name in src.traces: 

93 item.breakdown_requirements.append([src.name]) 

94 

95 return levels