Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion .agents/skills/openfasttrace/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ OpenFastTrace is a tool for requirement tracing across various artifacts (specif
- `Description: <text>`: Optional keyword to start description.
- `Rationale: <text>`, `Comment: <text>`.

## Syntax (Markdown)
## Syntax: Markdown

```markdown
### Title
Expand All @@ -37,6 +37,32 @@ Needs: dsn, impl, utest
- **Forwarding**: `arch --> dsn : req~id~1` (delegates coverage without repeating).
- **Exclusion**: Use `<!-- oft:off -->` and `<!-- oft:on -->` to skip parsing.

## Syntax: Coverage Tags (many file formats)

Implementation covering design in a C++ file:

```C++
\\ [impl -> dsn~hash-sum-calculation~1]
```

Unit test in a Java file:

```shell
\\ [utest -> dsn~hash-sum-calculation~1]
```

Coverage in a YMAL file (e.g., GitHub workflow)

```yaml
# [bld -> dsn~create-sbom~2]
```

Require coverage:

```plantuml
# [req -> dsn~hash-sum-calculation~1 >> impl, utest]
```

## Tracing

Tracing can be performed via CLI, Maven, or Gradle.
Expand Down
7 changes: 5 additions & 2 deletions doc/changes/changes_4.6.0.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# OpenFastTrace 4.6.0, released 2026-06-??
# OpenFastTrace 4.6.0, released 2026-07-??

Code name: ??

We also updated test and build dependencies to fix vulnerabilities. Runtime code is not affected, so no update is required.

## Summary

We moved some GitHub action permissions from workflow-level to job-level.

## Security

* #
* #556: Updated Junit, PlantUML, Jacoco Maven plugin and Central publishing Plugin dependencies to fix vulnerabilities

## Refactoring

* #552: Parameterized tests in `TestSpecobjectImporter`
* #546: Replaced `OsDetector` with JUnit5's `EnabledOnOs` annotation.
* #544: Replaced optional parameter with null check
* #543: Made `CliException` a `RuntimeException`
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
import static org.mockito.Mockito.*;

import java.io.BufferedReader;
Expand All @@ -19,25 +20,73 @@
import org.itsallcode.openfasttrace.importer.xmlparser.XmlParserFactory;
import org.itsallcode.openfasttrace.testutil.importer.input.StreamInput;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.FieldSource;

class TestSpecobjectImporter
{
private static final String PSEUDO_FILENAME = "pseudo_filename";
private static final Location STANDARD_LOCATION = Location.create(PSEUDO_FILENAME, 2);

@Test
void testImportOfMinimalSpecObject()
static List<Arguments> idImportChecks = List.of(
argumentSet("minimal import", """
<specobjects doctype="req">
<specobject>
<id>minimal</id>
<version>1</version>
</specobject>
</specobjects>""",
"req~minimal~1"),

argumentSet("strip superfluous artifact type", """
<specobjects doctype="impl">
<specobject>
<id>impl:strip_duplicate_prefix</id>
<version>0</version>
</specobject>
</specobjects>""",
"impl~strip_duplicate_prefix~0"),

argumentSet("ignore unknown XML elements", """
<specobjects doctype="feat">
<specobject>
<id>ignore-selected-xml-elements</id>
<version>12345</version>
<creationdate>1970-01-01</creationdate>
<source>john doe</source>
</specobject>
</specobjects>""",
"feat~ignore-selected-xml-elements~12345"),

argumentSet("fullfilled-by is ignored", """
<specobjects doctype="req">
<specobject>
<id>with-fulfilled-by</id>
<version>1</version>
<fulfilledby>
<ffbObj>
<ffbType>impl</ffbType>
<ffbId>ffb-a</ffbId>
<ffbVersion>1</ffbVersion>
</ffbObj>
<ffbObj>
<ffbType>utest</ffbType>
<ffbId>ffb-b</ffbId>
<ffbVersion>2</ffbVersion>
</ffbObj>
</fulfilledby>
</specobject>
</specobjects>""", "req~with-fulfilled-by~1"));

@ParameterizedTest
@FieldSource("idImportChecks")
Comment thread
redcatbear marked this conversation as resolved.
void testImportSeesTheCorrectId(final String input, final String expectedId)
{
final ImportEventListener listenerMock = importFromString("""
<specobjects doctype="req">
<specobject>
<id>minimal</id>
<version>1</version>
</specobject>
</specobjects>""");
final ImportEventListener listenerMock = importFromString(input);
verify(listenerMock).beginSpecificationItem();
verify(listenerMock).setLocation(STANDARD_LOCATION);
verify(listenerMock).setId(SpecificationItemId.parseId("req~minimal~1"));
verify(listenerMock).setLocation(TestSpecobjectImporter.STANDARD_LOCATION);
verify(listenerMock).setId(SpecificationItemId.parseId(expectedId));
verify(listenerMock).endSpecificationItem();
verifyNoMoreInteractions(listenerMock);
}
Expand Down Expand Up @@ -103,26 +152,6 @@ void testImportOnlyShortDescription()
verifyNoMoreInteractions(listenerMock);
}

@Test
void testSelectedElementsAreIgnoredDuringImport()
{
final ImportEventListener listenerMock = importFromString("""
<specobjects doctype="feat">
<specobject>
<id>ignore-selected-xml-elements</id>
<version>12345</version>
<creationdate>1970-01-01</creationdate>
<source>john doe</source>
</specobject>
</specobjects>""");
verify(listenerMock).beginSpecificationItem();
verify(listenerMock).setLocation(STANDARD_LOCATION);
verify(listenerMock)
.setId(SpecificationItemId.parseId("feat~ignore-selected-xml-elements~12345"));
verify(listenerMock).endSpecificationItem();
verifyNoMoreInteractions(listenerMock);
}

@Test
void testStripSuperfluousArtifactPrefixFromName()
{
Expand All @@ -146,13 +175,13 @@ void testTakeOverLocationFromImportedFile()
final String expectedFileName = "/home/johndoe/openfasttrace/examples/specobject.xml";
final int expectedLine = 42;
final ImportEventListener listenerMock = importFromString("<specobjects doctype=\"utest\">\n" //
+ " <specobject>\n" //
+ " <id>takeOverLocation</id>\n" //
+ " <version>99999999</version>\n" //
+ " <sourcefile>" + expectedFileName + "</sourcefile>" //
+ " <sourceline>" + expectedLine + "</sourceline>" // "
+ " </specobject>\n" //
+ "</specobjects>");
+ " <specobject>\n" //
+ " <id>takeOverLocation</id>\n" //
+ " <version>99999999</version>\n" //
+ " <sourcefile>" + expectedFileName + "</sourcefile>" //
+ " <sourceline>" + expectedLine + "</sourceline>" // "
+ " </specobject>\n" //
+ "</specobjects>");
verify(listenerMock).beginSpecificationItem();
verify(listenerMock).setLocation(Location.create(expectedFileName, expectedLine));
verify(listenerMock).setId(SpecificationItemId.parseId("utest~takeOverLocation~99999999"));
Expand Down Expand Up @@ -229,35 +258,6 @@ void testImportWithDependencies()
verifyNoMoreInteractions(listenerMock);
}

@Test
void testImportFulfilledByIsIgnored()
{
final ImportEventListener listenerMock = importFromString("""
<specobjects doctype="req">
<specobject>
<id>with-fulfilled-by</id>
<version>1</version>
<fulfilledby>
<ffbObj>
<ffbType>impl</ffbType>
<ffbId>ffb-a</ffbId>
<ffbVersion>1</ffbVersion>
</ffbObj>
<ffbObj>
<ffbType>utest</ffbType>
<ffbId>ffb-b</ffbId>
<ffbVersion>2</ffbVersion>
</ffbObj>
</fulfilledby>
</specobject>
</specobjects>""");
verify(listenerMock).beginSpecificationItem();
verify(listenerMock).setLocation(STANDARD_LOCATION);
verify(listenerMock).setId(SpecificationItemId.parseId("req~with-fulfilled-by~1"));
verify(listenerMock).endSpecificationItem();
verifyNoMoreInteractions(listenerMock);
}

@Test
void testImportProvidesCoverage()
{
Expand Down Expand Up @@ -290,7 +290,7 @@ void testImportProvidesCoverage()
@Test
void testCustomTagsWithoutNamespacesLogsWarning()
{
try( final RecordingLogHandler logHandler = new RecordingLogHandler() )
try (final RecordingLogHandler logHandler = new RecordingLogHandler())
{
importFromString("""
<specdocument>\
Expand All @@ -314,7 +314,7 @@ void testCustomTagsWithoutNamespacesLogsWarning()
@Test
void testCustomTagsWithNamespacesLogsNoWarning()
{
try( final RecordingLogHandler logHandler = new RecordingLogHandler() )
try (final RecordingLogHandler logHandler = new RecordingLogHandler())
{
importFromString("""
<specdocument xmlns:x="http://extension">
Expand All @@ -336,7 +336,7 @@ void testCustomTagsWithNamespacesLogsNoWarning()
@Test
void testCustomTagsWithNamespacesPlusOftNamespaceLogsNoWarning()
{
try( final RecordingLogHandler logHandler = new RecordingLogHandler() )
try (final RecordingLogHandler logHandler = new RecordingLogHandler())
{
importFromString("""
<specdocument xmlns="https://github.com/itsallcode/openfasttrace"
Expand Down Expand Up @@ -368,7 +368,8 @@ public RecordingLogHandler()
rootLogger.addHandler(this);
}

@Override public void publish(final LogRecord logRecord)
@Override
public void publish(final LogRecord logRecord)
{
logRecords.add(logRecord);
super.publish(logRecord);
Expand All @@ -379,7 +380,8 @@ public Stream<LogRecord> getLogRecords()
return logRecords.stream();
}

@Override public void close()
@Override
public void close()
{
Arrays.stream(rootLogger.getHandlers())
.filter((RecordingLogHandler.class::isInstance))
Expand Down
2 changes: 1 addition & 1 deletion parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<description>Free requirement tracking suite</description>
<url>https://github.com/itsallcode/openfasttrace</url>
<properties>
<revision>4.5.0</revision>
<revision>4.6.0</revision>
<java.version>17</java.version>
<junit.version>6.1.0-M1</junit.version>
<junit.version>6.1.1</junit.version>
Expand Down
Loading