Skip to content

Commit

Permalink
lobster-report allow optional up-linking in tracing policies
Browse files Browse the repository at this point in the history
lobster.config accepts multiline keywords which considered to be "and"
the "requires" keyword has been changed to "trace from"

Resolves #10
  • Loading branch information
TannazVhdBMWExt committed Nov 29, 2024
1 parent 169f05a commit 3528538
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 78 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Changelog

### 1.0.0-dev

* The `lobster-report` tool now supports optional "OR" in uplinks (`trace to`). In
the `lobster.config` file the keyword `requires` has been changed to `trace from`.

### 0.9.20-dev

Expand Down
24 changes: 17 additions & 7 deletions documentation/config_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ requirements "Requirements" {
implementation "Code" {
source: "python.lobster";
trace to: "Requirements";
trace to: "Requirements" or "Code";
}
```

#### requires
#### trace from

Sometimes you might want alternatives. For example we could have two
possibly ways to verify a requirement: by proof or by test. If we just
Expand Down Expand Up @@ -101,15 +101,15 @@ activity "Formal Proof" {
```

Then we would get lots of errors as the tooling would require a
requirement to be broken down into all three. The `requires`
requirement to be broken down into all three. The `trace from`
configuration can help here:


```
requirements "Requirements" {
source: "trlc.lobster";
requires: "Code";
requires: "Unit Test" or "Formal Proof";
trace from: "Code";
trace from: "Unit Test" or "Formal Proof";
}
```

Expand All @@ -118,7 +118,17 @@ link to code, and either a link to a test or a link to a proof.

**Note:**
Don't forget that the `trace to` configuration is always mandatory.
You cannot build links with a configuration that uses only `requires`.
You cannot build links with a configuration that uses only `trace from`.

**Note:**
Multiple lines of the same rule are treated as if they had an `and` between them.

**Note:**
`or` is supported in each line, to specify that only one target is needed, not
all of them.

**Note:**
Self-references are also allowed.

# Examples

Expand Down Expand Up @@ -148,7 +158,7 @@ our own custom LOBSTER trace tool).
requirements "System Requirements" {
source: "cbtrace.lobster" with
valid_status {"Valid"};
requires: "Integration Tests" or "Analysis";
trace from: "Integration Tests" or "Analysis";
}
requirements "Software Requirements" {
Expand Down
96 changes: 43 additions & 53 deletions lobster/config/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,11 @@ def parse_level_declaration(self):
"duplicate declaration")

item = {
"name" : level_name,
"kind" : level_kind,
"traces" : [],
"source" : [],
"needs_tracing_up" : False,
"needs_tracing_down" : False,
"raw_trace_requirements" : []
"name" : level_name,
"kind" : level_kind,
"source" : [],
"trace_to" : [],
"trace_from" : []
}
self.levels[level_name] = item

Expand Down Expand Up @@ -175,39 +173,40 @@ def parse_level_declaration(self):

elif self.peek("KEYWORD", "trace"):
self.match("KEYWORD", "trace")
self.match("KEYWORD", "to")
self.match("COLON")
self.match("STRING")
if self.ct.value() == level_name:
self.error(self.ct.loc,
"cannot trace to yourself")
elif self.ct.value() not in self.levels:
self.error(self.ct.loc,
"unknown item %s" % self.ct.value())
else:
self.levels[self.ct.value()]["needs_tracing_down"] = True
item["traces"].append(self.ct.value())
item["needs_tracing_up"] = True

self.match("SEMI")
if self.peek("KEYWORD", "to"):
self.match("KEYWORD", "to")
self.match("COLON")

elif self.peek("KEYWORD", "requires"):
self.match("KEYWORD", "requires")
self.match("COLON")
req_list = []
self.match("STRING")
req_list.append(self.ct.value())

req_list = []
while self.peek("KEYWORD", "or"):
self.match("KEYWORD", "or")
self.match("STRING")
req_list.append(self.ct.value())

self.match("STRING")
req_list.append(self.ct)
self.match("SEMI")

item["trace_to"].append(req_list)

while self.peek("KEYWORD", "or"):
self.match("KEYWORD", "or")
elif self.peek("KEYWORD", "from"):
self.match("KEYWORD", "from")
self.match("COLON")

req_list = []
self.match("STRING")
req_list.append(self.ct)
req_list.append(self.ct.value())

self.match("SEMI")
while self.peek("KEYWORD", "or"):
self.match("KEYWORD", "or")
self.match("STRING")
req_list.append(self.ct.value())

item["raw_trace_requirements"].append(req_list)
self.match("SEMI")

item["trace_from"].append(req_list)

else:
self.error(self.nt.loc,
Expand All @@ -219,28 +218,19 @@ def parse_level_declaration(self):
def load(mh, file_name):
parser = Parser(mh, file_name)
ast = parser.parse()

# Resolve requires links now
item_names = list(ast.keys())
for item in ast.values():
item["breakdown_requirements"] = []
if len(item["raw_trace_requirements"]) > 0:
for chain in item["raw_trace_requirements"]:
new_chain = []
for tok in chain:
if tok.value() not in ast:
mh.error(tok.loc, "unknown level %s" % tok.value())
if item["name"] not in ast[tok.value()]["traces"]:
mh.error(tok.loc,
"%s cannot trace to %s items" %
(tok.value(),
item["name"]))
new_chain.append(tok.value())
item["breakdown_requirements"].append(new_chain)
else:
for src in ast.values():
if item["name"] in src["traces"]:
item["breakdown_requirements"].append([src["name"]])
del item["raw_trace_requirements"]
if len(item["trace_to"]) > 0:
for trace_to in item["trace_to"]:
if not set(trace_to).issubset(item_names):
mh.error(set(trace_to).issubset(item_names),
"cannot trace to %s items" % ",".join(trace_to))

if len(item["trace_from"]) > 0:
for trace_from in item["trace_from"]:
if not set(trace_from).issubset(item_names):
mh.error("cannot trace from %s items" %
",".join(trace_from))

return ast

Expand Down
41 changes: 25 additions & 16 deletions lobster/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,31 +134,42 @@ def determine_status(self, config, stab):
level = config[self.level]

has_up_ref = len(self.ref_up) > 0
has_down_ref = len(self.ref_down) > 0
has_just_up = len(self.just_up) > 0 or len(self.just_global) > 0
has_just_down = len(self.just_down) > 0 or len(self.just_global) > 0
has_init_errors = len(self.messages) > 0

# Check up references
ok_up = True
if level["needs_tracing_up"]:
if level["trace_to"]:
if not has_up_ref and not has_just_up:
ok_up = False
self.messages.append("missing up reference")

for trace_to in level["trace_to"]:
# and
if not ok_up:
break

# or
refs_levels = [stab[ref.key()].level for ref in self.ref_up]
ok_up = len(set(refs_levels).intersection(set(trace_to))) > 0

# Check set of down references
ok_down = True
if level["needs_tracing_down"]:
has_trace = {name : False
for name in config
if self.level in config[name]["traces"]}
for ref in self.ref_down:
has_trace[stab[ref.key()].level] = True
for chain in level["breakdown_requirements"]:
if not any(has_trace[src] for src in chain) and \
not has_just_down:
ok_down = False
self.messages.append("missing reference to %s" %
" or ".join(sorted(chain)))
if level["trace_from"]:
if not has_down_ref and not has_just_down:
ok_down = False
self.messages.append("missing down reference")

for trace_from in level["trace_from"]:
# and
if not ok_down:
break
# or
refs_levels = [stab[ref.key()].level for ref in self.ref_down]
ok_down = len(set(refs_levels)
.intersection(set(trace_from))) > 0

# Set status
if self.has_error:
Expand All @@ -168,9 +179,7 @@ def determine_status(self, config, stab):
self.tracing_status = Tracing_Status.JUSTIFIED
else:
self.tracing_status = Tracing_Status.OK
elif (ok_up or ok_down) and \
level["needs_tracing_up"] and \
level["needs_tracing_down"]:
elif ok_up or ok_down:
self.tracing_status = Tracing_Status.PARTIAL
else:
self.tracing_status = Tracing_Status.MISSING
Expand Down
5 changes: 3 additions & 2 deletions lobster/tools/core/html_report/html_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,11 @@ def create_policy_diagram(doc, report, dot):

for level in report.config.values():
source = name_hash(level["name"])
for target in map(name_hash, level["traces"]):
all_trace_tos = sum(level["trace_to"], [])
for target in map(name_hash, all_trace_tos):
# Not a mistake; we want to show the tracing down, whereas
# in the config file we indicate how we trace up.
graph += ' n_%s -> n_%s;\n' % (target, source)
graph += ' n_%s -> n_%s;\n' % (source, target)
graph += "}\n"

with tempfile.TemporaryDirectory() as tmp_dir:
Expand Down
30 changes: 30 additions & 0 deletions packages/lobster-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,41 @@ You can generate a report linking everything together with `lobster-report`.
The report is in JSON, but you can generate more readable versions of it
with additional tools:

* `lobster-report`: Creation of JSON format report from provided config file
* `lobster-online-report`: Preprocess a JSON report to contain github
references instead of local file references
* `lobster-html-report`: Generate a HTML report
* `lobster-ci-report`: Generate a compiler-message like output, useful for CI

## Configuration
The `lobster-report` tool works with a config file. In it you can declare the
upstream, downstream and source of tracing policies.

The configuration file follows the following rules:

* This file is able to get multiple `trace to` and `trace from` keys.
* `trace to` specifies all the outgoing targets.
* `trace from` specifies all the incoming targets.
* Multiple lines of the same rule are treated as if they had an `and` between them.
* `or` is supported in each line, to specify that only one target is needed, not
all of them.
* Self-references are also allowed.


```
requirements "Requirements" {
source: "file1.lobster";
}
requirements "System Requirements" {
source: "file2.lobster";
trace to: "Models" or "Software Requirements";
trace to: "Requirements";
trace from: "System Requirements" or "Integration Tests";
trace from: "Unit Tests";
}
```

## Requirements
* `lobster-online-report`: This tool needs `git 1.7.8` or higher to support
git submodules
Expand Down
27 changes: 27 additions & 0 deletions test-unit/lobster-core/data/data1.lobster
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"data": [
{
"tag": "req 17045@11",
"location": {
"kind": "codebeamer",
"cb_root": "https://codebeamer.company.net/",
"tracker": 1111,
"item": 1111,
"version": 11,
"name": "test1"
},
"name": "test1",
"messages": [],
"just_up": [],
"just_down": [],
"just_global": [],
"framework": "codebeamer",
"kind": "Technical Requirement",
"text": null,
"status": "Valid"
}
],
"generator": "lobster_codebeamer",
"schema": "lobster-req-trace",
"version": 4
}
27 changes: 27 additions & 0 deletions test-unit/lobster-core/data/data2.lobster
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"data": [
{
"tag": "req 17044@22",
"location": {
"kind": "codebeamer",
"cb_root": "https://codebeamer.company.net/",
"tracker": 2222,
"item": 2222,
"version": 22,
"name": "test"
},
"name": "test",
"messages": [],
"just_up": [],
"just_down": [],
"just_global": [],
"framework": "codebeamer",
"kind": "Technical Requirement",
"text": null,
"status": "Valid"
}
],
"generator": "lobster_codebeamer",
"schema": "lobster-req-trace",
"version": 4
}
Loading

0 comments on commit 3528538

Please sign in to comment.