This documents structure is derived from the "arc42" architectural template by Dr. Gernot Starke, Dr. Peter Hruschka.
If you build your own modifications based on this document, please keep the attrbiutions.
The terminology from the system requirement specification applies.
Syntax definitions in this document use the Augmented Backus-Naur Form.
The following definitions are used frequently throughout the document:
- ANY - any valid character
- LINEBREAK = the line break character of the platform
The algorithm that checks the requirement links has to follow each link between requester and provider at least once.
Given:
rn, n ∊ {1..N}
pm, m ∊ {1..M}
an,l, l ∊ {1..Ln}
cm,k, k ∊ {1..Mm}
Where r are the requesters, p the providers, a artifacts required by providers and c the coverage provided.
The number of forward links results to:
f = ΣNn = 1 ( Ln )
The number of backward links is:
b = ΣMn = 1 ( Kn )
The naive approach is to iterate over all required artifacts in all requesters and for each iterate over the complete set of providers to see if coverage exists.
If we assume a scenario where the coverage is 100% and a provider is found in average after iterating over half the providers this gives us the following number of Lookups l:
l = f p / 2
Assuming that each requester needs to be covered by an average of A artifacts we get:
f = r A
l = ( r A ) p / 2
Assuming further that each provider has an average of C back links to provide coverage we further get:
p = r C
l = ( r A ) ( r C ) / 2 = r² A C / 2
Note that A and C can be considered to have a small range in a typical project, depending only on the working style and not on the number of requirements the project need to handle.
In effect the lookup using the naive approach results in a complexity of O(n²). Not good.
If instead of relying on a linear search of all providers, we use an index. We saw earlier that iterating over the requesters results in a complexity of O(n). A proper index lookup should give O(log n) in the worst case. That leaves us with a complexity of O(n log(n)). Better.
Since the specification item IDs inherently look similar, load tests need to show which kind of index is best balanced and has the optimal access time.
The plugin loader discovers and loads available plugins.
For each specification artifact type OFT uses an importer. The importer uses the specification artifact as data source and reads specification items from it.
Importers emit events if they find parts of a specification item in the artifact they are importing.
The specification list builder is an import event listener that creates a list of specification items from import events.
The command line interpreter (CLI) takes parameters given to OFT and parses them. It is responsible for making sense of the parameter contents and issuing help and error messages about the command line syntax.
The linker is responsible for turning the imported specification items collected by the importers into linked specification items.
The tracer consumes the list of linked specification items and evaluates the link status for each link.
The reporter consumes the link status list and the specification item list and generates a report in the chosen output format.
API users select reporters via their name as strings. This allows plugging in custom reporters in a loosely coupled fashion.
The exporter transforms the internal representation of specification items into the desired target format (e.g. Markdown).
API users select exporters via their name as strings.
dsn~plugins.loading~1
OFT loads plugins at startup from these locations:
- Plugins included with OFT from the current ClassPath
- Third party plugins from folder
$HOME/.oft/plugins/<plugin-name>/*.jar
Rationale:
- A plugin may consist of multiple JARs, e.g. the plugin itself and it's dependencies.
- A plugin might need extra resources
Covers:
Needs: impl, utest, itest
dsn~plugins.loading.separate-classloader~1
The Plugin loader uses a separate ClassLoader for each plugin.
Rationale:
Separating plugins from each other avoids conflicts between potentially duplicate classes in plugin.
Covers:
Needs: impl, utest
dsn~plugins.loading.plugin-types~1
The Plugin loader supports loading factories for the following plugin types:
- Importers:
org.itsallcode.openfasttrace.api.importer.ImporterFactory
- Exporters:
org.itsallcode.openfasttrace.api.exporter.ExporterFactory
- Reports:
org.itsallcode.openfasttrace.api.report.ReporterFactory
Covers:
Needs: impl, itest
Depending on the source format a variety of importers takes care of reading the input specification items. Each importer emits events which an import event listener consumes.
Common parts of the import like filtering out unnecessary items or attributes are handled by the listener.
The most resource-friendly way to enable partial tracing is to ignore unnecessary data during import. This way less memory is used up and all subsequent steps are faster.
dsn~filtering-by-artifact-types-during-import~1
When OFT is configured to restrict inclusion to one or more artifact types the specification list builder imports the following elements only if they match at least one of the configured types:
- "Needs coverage" markers
- Specification items as a whole
- Links covering items with this artifact type
- Dependencies to item with this artifact type
Covers:
req~include-only-artifact-types~1
Needs: impl, utest, itest
dsn~filtering-by-tags-during-import~1
The specification list builder can be configured to import a specification item only if at least one of its tags is contained in the configured set of tags.
Covers:
req~include-items-where-at-least-on-tag-matches~1
Needs: impl, utest, itest
dsn~filtering-by-tags-or-no-tags-during-import~1
The specification list builder can be configured to import a specification item only if it either has no tags or at least one of its tags is contained in the configured set of tags.
Covers:
req~include-items-that-do-not-have-tags-or-where-at-least-one-tag-matches~1
Needs: impl, utest, itest
dsn~tracing.needed-coverage-status~1
The linker component iterates over all needed artifact types of all specification items and determines if and which coverage exists for each.
Comment: Note that the linker only takes care of swallow coverage. Deep coverage is determined by the tracer component.
Covers:
req~tracing.outgoing-coverage-link-status~1
Needs: utest, impl
dsn~tracing.outgoing-coverage-link-status~3
The linker component determines the coverage status of the outgoing link between the provider item and the requester item.
The possible results are:
- Covers: link points to a specification item which wants this coverage
- Outdated: link points to a specification item which has a higher revision number
- Predated: link points to a specification item which has a lower revision number
- Ambiguous: link points to a specification item that has duplicates
- Orphaned: link is broken - there is no matching coverage requester
- Unwanted: coverage provider has an artifact type the provider does not want
Covers:
req~tracing.outgoing-coverage-link-status~1
Needs: utest, impl
dsn~tracing.incoming-coverage-link-status~1
The linker component determines the coverage status of the incoming link between the requester item and the provider item.
The possible results are:
- Covered shallow: coverage provider for a required coverage exists
- Covered unwanted: coverage provider covers an artifact type the requester does not want
- Covered predated: coverage provider covers a higher revision number than the requester has
- Covered outdated: coverage provider covers a lower revision number than the requester has
Covers:
req~tracing.incoming-coverage-link-status~1
Needs: impl, utest
dsn~tracing.deep-coverage~1
The Linked Specification Item declares itself covered deeply if this item - and all items it needs coverage from - are covered recursively.
Covers:
req~tracing.deep-coverage~1
Needs: impl, utest
dsn~tracing.tracing.duplicate-items~1
The tracer marks a specification item as a duplicate if other items with an identical specification item ID exist.
Covers:
req~tracing.duplicate-items~1
Needs: impl, utest
dsn~tracing.defect-items~2
The tracer marks a specification item as defect if the following criteria apply to the item
has duplicates
or (not rejected
and (any outgoing coverage link has a different status than "Covers"
or not covered deeply
)
)
Covers:
req~tracing.defect-items~2
Needs: impl, utest
dsn~tracing.link-cycle~1
The tracer detects cycles in links between Linked Specification Items.
Covers:
req~tracing.link-cycle~1
Needs: impl, utest
dsn~reporting.plain-text.summary~2
The summary in the plain text report includes:
- Result status
- Total number of specification items
- Total number of specification items that are defect (if any)
Covers:
req~reporting.plain-text.summary~2
Needs: impl, utest
dsn~reporting.plain-text.specification-item-overview~2
An item summary consist in the plain text report includes
- Status
- Number of broken incoming links
- Total number of incoming links
- Number of broken outgoing links
- Total number of outgoing links
- Number of duplicates (not including this item)
- ID
- Status (unless "approved")
- Artifact types indicating coverage
Covers:
req~reporting.plain-text.specification-item-overview~2
Needs: impl, utest
dsn~reporting.plain-text.link-details~1
The link detail section shows for all links of a specification item:
- Incoming / Outgoing as arrow
- Link status as symbol
- ID of the specification item on the other end of the link
Covers:
req~reporting.plain-text.link-details~1
Needs: impl, utest
dsn~reporting.plain-text.specification-item-origin~1
If enabled, the plain text report shows the origin of a specification item
- for files:
<absolute path to file>:<line number>
Rationale:
This format is recognized by most IDEs and automatically turned into a link in the IDE's console.
Covers:
req~reporting.requirement-origin~1
Needs: impl, utest
dsn~reporting.plain-text.linked-specification-item-origin~1
If enabled, the links in the plain text report show the origin of a specification item
- for files:
<absolute path to file>:<line number>
Rationale:
This format is recognized by most IDEs and automatically turned into a link in the IDE's console.
Covers:
req~reporting.requirement-origin~1
Needs: impl, utest
dsn~reporting.plain-text.ansi-color~1
The plain text report uses ANSI escape sequences to color the output.
Covers:
req~colored-plain-text-report~1
Needs: impl, utest
dsn~reporting.plain-text.ansi-font-style~1
The plain text report uses ANSI escape sequences to modify the font style of the output.
Covers:
req~colored-plain-text-report~1
req~monochrome-plain-text-report-with-font-style~1
Needs: impl, utest
dsn~reporting.html.inline_css~1
OFT inlines the cascading style sheet (CSS) into the HTML report.
Covers:
req~reporting.html.single_file~1
Needs: impl, itest
dsn~reporting.html.details-display~1
OFT allows configuring the specification item detail section display status (expanded or collapsed). Default is collapsed.
Covers:
Needs: impl, utest
dsn~reporting.html.escape-html~1
OFT escapes characters <
and >
when rendering the following parts of a specification item to report:
- Title
- Description
- Rationale
- Comment
Rationale:
- This avoids generating invalid HTML when the Markdown contains text like
<section>
. - Other parts of a specification item (e.g. item ID) don't allow these characters and don't need escaping.
Covers:
Needs: impl, utest
dsn~conversion.reqm2-export~1
OFT exports to ReqM2's "SpecObject" format.
Comment: The ReqM2 format is specified in the ReqM2 handbook by Elektrobit.
Covers:
req~conversion.reqm2-export~1
Needs: impl, itest
dsn~reporting.html.specification-item-origin~1
If enabled, the HTML report shows the origin of a specification item as an HTML link pointing to the source.
Covers:
req~reporting.requirement-origin~1
Needs: impl, utest
dsn~reporting.html.linked-specification-item-origin~1
If enabled, the links in the plain text report show the origin of a specification item as an HTML link pointing to the source.
Covers:
req~reporting.requirement-origin~1
Needs: impl, utest
dsn~specification-item~3
A SpecificationItem
consists of the following parts:
- ID (
SpecificationItemId
) - Title (
String
, optional) - Status (
Enum
, optional) - Description (
String
, optional) - Rationale (
String
, optional) - Comment (
String
, optional) - Source file + line (
String
,int
, optional) - Covers (List of
SpecificationItemId
, optional) - Depends (List of
SpecificationItemId
, optional) - Needs (List of
String
, optional) - Tags (List of
String
, optional) - Forwards (
boolean
, internal)
Comment:
See req~forwarding_needed_coverage~1
for an explanation of the "forwards" fields meaning.
Covers:
req~specification-item~2
req~forwarding_needed_coverage~1
Needs: impl, utest
dsn~linked-specification-item~1
A LinkedSpecificationItem
is a container for a SpecificationItem that is enriched with references to other LinkedSpecificationItem
s.
Rationale: This allows navigating between specification items.
Covers:
req~specification-item~2
Needs: impl, utest
dsn~specification-item-id~1
A SpecificationItemId
consists of:
- Artifact type (String)
- name (String)
- revision (number)
Covers:
req~specification-item~2
Needs: impl, utest
dsn~md.specification-item-id-format~3
A requirement ID has the following format
requirement-id = type "~" id "~" revision
type = 1*ALPHA
id = id-fragment *("." id-fragment)
id-fragment = UNICODE_ALPHA *(UNICODE_ALPHA / DIGIT / "_" / "-")
revision = 1*DIGIT
Rationale:
- The ID may contain unicode letters to allow naming requirements using non-ASCII characters. This makes linking in formats like Markdown or HTML clean and easy.
- Requirement type and revision must be immediately recognizable from the requirement ID.
- The built-in revision number makes links break if a requirement is updated - a desired behavior.
Comment:
Note that the artifact type is integral part of the ID. That means that dsn~my-requirement~1
is something completely different then utest~my-requirement~1
. One of the benefits of making the artifact type mandatory part of the ID is that this allows for typical coverage chains like.
req~my-requirement~2 -> dsn~my-requirement~4 -> impl~my-requirement~4
Otherwise users would be forced to invent different names for each link in the chain.
Covers:
req~markdown-standard-syntax~1
Needs: impl, utest
dsn~md.specification-item-title~1
If a Markdown title directly precedes a specification item ID, then the Markdown title is used as title for the specification item.
Rationale:
Markdown titles show up in the outline and are a natural way of defining a requirement title.
Covers:
req~markdown-standard-syntax~1
req~markdown-outline-readable~1
Needs: impl, utest
dsn~md.requirement-references~1
In Markdown specification item references have the following format:
reference = (plain-reference / url-style-link)
plain-reference = requirement-id
url-style-link = "[" link-text "]" "(" "#" requirement-id ")"
Covers:
req~markdown-standard-syntax~1
Needs: impl, utest
dsn~md.covers-list~1
The Markdown Importer supports the following format for links that cover a different specification item.
covers-list = covers-header 1*(LINEBREAK covers-line)
covers-header = "Covers:" *WSP
covers-line = *WSP "*" *WSP reference
Only one traced reference per line is supported. Any optional text after the reference is ignored if it is separated by at least one whitespace character
Rationale:
Defining a link should be as natural and simple as possible in Markdown. It must also be rendered correctly by a regular Markdown renderer without modifications. Embedding links in lists to define the relationship looks nice and is language independent.
Covers:
req~markdown-standard-syntax~1
Needs: impl, utest
dsn~md.depends-list~1
The Markdown Importer supports the following format for links to a different specification item which the current depends on.
depends-list = depends-header 1*(LINEBREAK depends-line)
depends-header = "Depends:" *WSP
depends-line = *WSP "*" *WSP reference
Only one traced reference per line is supported. Any optional text after the reference is ignored if it is separated by at least one whitespace character
Rationale:
Defining a link should be as natural and simple as possible in Markdown. It must also be rendered correctly by a regular Markdown renderer without modifications. Embedding links in lists to define the relationship looks nice and is language independent.
Covers:
req~markdown-standard-syntax~1
Needs: impl, utest
dsn~md.needs-coverage-list~1
The Markdown Importer supports the following list format for defining the list of artifact types that are needed to fully cover the current specification item.
needs-list = needs-header 1*(LINEBREAK depends-line)
needs-header = "Needs:" *WSP
needs-line = *WSP "*" *WSP reference
Rationale:
This alternative style of the "needs" list provides backward compatibility to Elektrobit's legacy requirement enhanced Markdown format.
Covers:
req~markdown-standard-syntax~1
Needs: impl, utest
dsn~md.needs-coverage-list-single-line~2
The Markdown Importer supports the following format for defining the list of artifact types that are needed to fully cover the current specification item.
needs-list = "Needs:" *WSP reference *("," *WSP reference)
Rationale:
Unlike references to other requirements, artifact types are usually very short, so it is visually beneficial to use a compact style with a comma separated list in a single line.
Covers:
req~markdown-standard-syntax~1
Needs: impl, utest
dsn~md.artifact-forwarding-notation~1
The Markdown Importer supports forwarding required coverage from one artifact type to one or more different artifact types using the following notation.
artifact-need-redirection = skipped-artifact-type *WSP "-->" *WSP target-artifact-list
*WSP ":" *WSP original-requirement-id
skipped-artifact-type = artifact-type
target-artifact-list = artifact-type *("," *WSP artifact-type)
original-requirement-id = requirement-id
The following example shows an architectural specification item that forwards the needed coverage directly to the detailed design and an integration test:
arch --> dsn, itest : req~skip-this-requirement~1
Covers:
req~artifact-type-forwarding-in-markdown~1
Needs: impl, utest
dsn~import.full-coverage-tag~1
OFT imports coverage tags in the full tag format:
full-tag = "[" *WSP reference "->" requirement-id "]"
Covers:
req~import.full-coverage-tag-format~1
Needs: impl, utest
dsn~import.full-coverage-tag-with-needed-coverage~1
OFT imports coverage tags in the full tag format with a list of required/needed artifact types:
full-tag-with-needed-coverage = "[" *WSP reference *WSP "->" *WSP requirement-id
*WSP ">>" *WSP artifact-type *WSP *("," *WSP artifact-type) "]"
Rationale:
The Tag importer is the catch all solution for all file formats that don't have a dedicated importer. We want to allow specification items imported via Tag importer to be intermediate nodes in the specification tree, instead of limiting them to leaves in the specification tree. Because leaves can only cover other specification items, but not require coverage.
Especially when used for design document files like UML models, requiring coverage is useful.
Covers:
req~import.full-coverage-tag-format~1
Needs: impl, utest
dsn~import.full-coverage-tag-with-revision~1
OFT imports full coverage tags with an optional revision:
full-tag-with-revision = "[" *WSP reference "~" "~" revision "->" requirement-id "]"
full-tag-with-revision-with-needed-coverage = "[" *WSP reference "~" "~" revision *WSP "->" *WSP requirement-id
*WSP ">>" *WSP artifact-type *WSP *("," *WSP artifact-type) "]"
Rationale:
Specifying an explicit revision in coverage tags allows incrementing the revision when the implementation changes. Without this, OFT would always assign the default revision 0
.
Covers:
req~import.full-coverage-tag-format~1
Needs: impl, utest
dsn~import.full-coverage-tag-with-name-and-revision~1
OFT imports full coverage tags with optional name and revision:
full-tag-with-name-and-revision =
"[" *WSP covering-artifact-type "~" covering-name "~" covering-revision
"->" covered-requirement-id "]"
full-tag-with-name-and-revision-with-needed-coverage =
"[" *WSP covering-artifact-type "~" xxx "~" revision *WSP
"->" *WSP requirement-id *WSP
">>" *WSP artifact-type *WSP *("," *WSP artifact-type) "]"
Rationale:
Specifying an explicit name in coverage tags allows overriding the auto-generated name.
Covers:
req~import.full-coverage-tag-format~1
Needs: impl, utest
dsn~import.full-coverage-tag-with-needed-coverage-readable-names~1
OFT generates readable names without hash code ID for tags with needed coverage.
Rationale:
When you need to cover these items it's important that the name is predictable and does not change e.g. when the file changes.
Covers:
req~import.full-coverage-tag-format~1
Needs: impl, utest
dsn~import.short-coverage-tag~1
OFT imports coverage tags in the short tag format:
short-tag = "[" "[" *WSP reference ":" *revision "]" "]"
During import of short tags OFT requires the following configuration:
- Path from which to import the tags
- Artifact type of the tags
- Artifact type of the covered specification item
- Name prefix of the covered specification item. The prefix is optional, default value:
project name "."
Covers:
req~import.short-coverage-tag-format~1
Needs: impl, utest
dsn~cli.command-selection~1
The CLI expects one of the following commands as first unnamed command line parameter:
command = "trace" / "convert" / "help"
Covers:
req~cli.tracing.command~1
req~cli.conversion.command~1
Needs: impl, itest
dsn~cli.input-file-selection~1
The CLI accepts the following two variants for defining input files:
- A list of files
- A list of directories
In both cases relative and absolute paths are accepted. "Relative" means in relation to the current working directory.
Covers:
req~cli.input-selection~1
Needs: impl, itest
dsn~input-directory-recursive-traversal~1
The Importer reads all requirement input files from all input directories recursively.
Covers:
req~cli.input-directory-selection~1
Needs: impl, itest
dsn~cli.default-input~1
If the user does not specify any inputs as CLI parameters, the CLI uses the current working directory as default input.
Covers:
req~cli.default-input~1
Needs: impl, itest
dsn~newline-format~1
The CLI accepts one of the following newline formats:
new-line-format = "unix" / "windows"
Rationale:
When users work together in teams where the team members use different platforms, configuring the newline helps the team to set a common standard.
Covers:
req~cli.newline-format~1
Needs: impl, itest
dsn~cli.default-newline-format~1
If the user does not specify the newline format as parameter, the exporter uses the native newline format of the platform OFT is executed on.
Covers:
req~cli.default-newline-format~1
Needs: impl, itest
dsn~cli.tracing.output-format~1
The CLI accepts one of the following requirement tracing report formats as parameter:
report-formats = "plain"
Covers:
req~cli.tracing.output-format~1
Needs: impl, itest
dsn~cli.tracing.default-format~1
The CLI uses plain text as requirement tracing report format if none is given as a parameter.
Covers:
req~cli.tracing.default-output-format~1
Needs: impl, utest
dsn~cli.tracing.exit-status~1
The return value of the OFT executable is:
0
tracing was successful1
tracing ran successfully, but the tracing result is negative
Covers:
req~cli.tracing.exit-status~1
Needs: impl, itest
dsn~cli.conversion.output-format~1
The CLI accepts one of the following export formats as parameter:
export-formats = "reqm2"
Covers:
req~cli.conversion.output-format~1
Needs: impl, itest
dsn~cli.conversion.default-output-format~1
The CLI uses ReqM2 as export format if none is given as a parameter.
Covers:
req~cli.conversion.default-output-format~1
Needs: impl, itest, utest
dsn~cli.plugins.log~1
The CLI logs available plugins in OFT at startup.
Covers:
Needs: impl
dsn~reflection-based-cli~1
OFT got its own simple command line interpreter that uses reflection to feed the command line arguments to a receiver object.
Rationale:
One of the design goal of OFT is that it works without external runtime dependencies except for the Java Standard API. So taking an existing CLI was no option. Using reflection allows the CLI user to implement the receiver as a POJO. No annotations are necessary.
Covers:
req~cli.tracing.command~1
req~cli.conversion.command~1
Exchanging the CLI later takes considerable effort.
- No CLI (plain argument list) - not flexible enough
- External CLI - breaks design goal
dsn~cleaning-imported-multi-line-text-elements~1
The ImportEventListener
s do the following clean-up steps in multi-line text elements like the description:
- Trimming (removing leading and trailing spaces of the whole text -- no individual lines.)
Rationale:
This way we do cleanup in a central place and don't produce code duplication. Extra clean-up in the importers is still possible, but basics like trimming need to be handled in common code.
Needs: impl, utest
Authors of importers need to be able to rely on these cleanups being done centrally, so that they don't have to implement them themselves.
Clean-up in every importer individually. That was the case up to and including OFT 3.7.1 which turned out to make the importer more complex than necessary.
The following documents or are referenced in this specification.
- System Requirement Specification OpenFastTrace, Sebastian Bär
- Augmented BNF for Syntax Specifications: ABNF , D. Crocker, P. Overell, January 2008
- arc42 - Ressources for software architects, Dr. Gernot Starke, Dr. Peter Hruschka