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
9 changes: 4 additions & 5 deletions docs/design/program.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
`Program` is the entry point of the ReqStream executable. It owns the top-level execution flow,
dispatches to the appropriate subsystem based on the parsed command-line options, and establishes the
error-handling boundary for the entire process. All meaningful work is delegated to `Context`,
`Validation`, `Linter`, `Requirements`, and `TraceMatrix`; `Program` itself contains no domain logic.
`Validation`, `Requirements`, and `TraceMatrix`; `Program` itself contains no domain logic.

## Properties

Expand Down Expand Up @@ -60,7 +60,7 @@ order; if a step applies, execution returns immediately without reaching later s
| 2 | (always) | Call `PrintBanner` |
| 3 | `context.Help` is `true` | Call `PrintHelp`; return |
| 4 | `context.Validate` is `true` | Call `Validation.Run(context)`; return |
| 5 | `context.Lint` is `true` | Call `Linter.Lint(context, context.RequirementsFiles)`; return |
| 5 | `context.Lint` is `true` | Call `Requirements.Load(context.RequirementsFiles)`; report lint issues; return |
| 6 | (default) | Call `ProcessRequirements(context)` |

### `PrintBanner`
Expand All @@ -77,7 +77,7 @@ argument, grouped logically. It is only called when `--help` is present.
### `ProcessRequirements`

`ProcessRequirements` orchestrates the normal (non-version, non-help, non-validate, non-lint) run.
It begins by calling `Requirements.Read(context.RequirementsFiles)` to build the parsed requirement
It begins by calling `Requirements.Load(context.RequirementsFiles)` to build the parsed requirement
tree. It then conditionally generates the requirements report (if `--report` is set) and the
justifications report (if `--justifications` is set). If `--tests` files are provided, a
`TraceMatrix` is constructed from the requirement tree and the test result files to enable coverage
Expand All @@ -103,8 +103,7 @@ internal error flag and eventually produces a non-zero exit code.
| ---- | --------------------- |
| `Context` | Created in `Main`; passed to all subsystems; owns output and exit code |
| `Validation` | Called by `Run` when `--validate` is present |
| `Linter` | Called by `Run` when `--lint` is present |
| `Requirements` | Constructed in `ProcessRequirements`; provides the requirement tree |
| `Requirements` | Constructed in `ProcessRequirements`; provides the requirement tree; also used for linting |
| `TraceMatrix` | Constructed in `ProcessRequirements` when test files are present |

## References
Expand Down
5 changes: 2 additions & 3 deletions docs/design/self-test/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ outcome `Passed` or `Failed`.
| -------------- | ---------- |
| `.trx` | TRX serializer (`DemaConsulting.TestResults.IO`) |
| `.xml` | JUnit serializer (`DemaConsulting.TestResults.IO`) |
| Any other | Throws `ArgumentException` |
| Any other | Reports error via `context.WriteError` and returns |

The serializer is invoked with the assembled `TestResults` object and the resolved output path.

Expand Down Expand Up @@ -86,9 +86,8 @@ after the test completes, regardless of whether the test passes or fails.
| ---- | --------------------- |
| `Context` | Reads `ResultsFile`, `Version`, `Silent`; calls `WriteLine` for headers and summary |
| `Program` | `Run` internally exercises `Program.Run` or individual workflow methods |
| `Requirements` | Tests exercise `Requirements.Read` with fixture YAML files |
| `Requirements` | Tests exercise `Requirements.Load` with fixture YAML files |
| `TraceMatrix` | Tests exercise `TraceMatrix` construction with fixture test-result files |
| `Linter` | `RunLintTest` exercises `Linter.Lint` with fixture YAML files |

## References

Expand Down
10 changes: 5 additions & 5 deletions docs/design/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Overview

This chapter describes how the ReqStream software units work together as an integrated system.
Where the unit chapters (Program, Context, Validation, Requirements, TraceMatrix, Linter) each
Where the unit chapters (Program, Context, Validation, Requirements, TraceMatrix) each
describe one component in isolation, this chapter focuses on the end-to-end data flow, the
coordination points between units, and the integrated scenarios that the units collectively
enable.
Expand All @@ -16,8 +16,8 @@ processing invocation:
| Source | Data | Destination |
| ------ | ---- | ----------- |
| CLI arguments | Parsed options | `Context` |
| `Context.RequirementsFiles` | Glob-expanded file paths | `Requirements.Read` |
| `Requirements.Read` | Requirement tree | `Program.ProcessRequirements` |
| `Context.RequirementsFiles` | Glob-expanded file paths | `Requirements.Load` |
| `Requirements.Load` | Requirement tree | `Program.ProcessRequirements` |
| `Context.TestFiles` | Glob-expanded file paths | `TraceMatrix` constructor |
| Requirement tree | Requirements | `TraceMatrix` constructor |
| `TraceMatrix` | Coverage data | `Program.EnforceRequirementsCoverage` |
Expand All @@ -30,7 +30,7 @@ non-validate, non-lint) invocation:

1. **Argument parsing** — `Context.Create(args)` parses all CLI flags and expands any glob
patterns in `--requirements` and `--tests` arguments.
2. **Requirements loading** — `Requirements.Read(context.RequirementsFiles)` reads and merges all
2. **Requirements loading** — `Requirements.Load(context.RequirementsFiles)` reads and merges all
YAML requirements files into a single requirement tree. Files listed via `includes` are resolved
recursively.
3. **Report generation** — if `--report` is set, the requirements report is exported. If
Expand Down Expand Up @@ -78,7 +78,7 @@ so that the evidence can be fed back into ReqStream's own requirements enforceme
| ------------ | ----------- | --------- | ------- |
| `Program` | `Context` | `Main` | Parses CLI arguments; owns output and exit code |
| `Program` | `Validation` | `Run` | Runs self-validation suite when `--validate` is set |
| `Program` | `Linter` | `Run` | Lints requirements files when `--lint` is set |
| `Program` | `Requirements` | `Run` | Loads and lints requirements files when `--lint` is set |
| `Program` | `Requirements` | `ProcessRequirements` | Reads and merges YAML requirement files |
| `Program` | `TraceMatrix` | `ProcessRequirements` | Loads test results and maps them to requirements |
| `Validation` | `Program` | test methods | Invokes `Program.Run` to exercise the full pipeline |
Expand Down
10 changes: 5 additions & 5 deletions docs/design/tracing/tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ The `Tracing` subsystem contains the following software unit:

The `Tracing` subsystem exposes the following interface to the rest of the tool:

| Interface | Direction | Description |
|----------------------------|-----------|---------------------------------------------------------------------|
| `TraceMatrix` constructor | Outbound | Loads test results and maps them to the provided requirements. |
| `TraceMatrix.Export` | Outbound | Exports the trace matrix to a Markdown report. |
| `TraceMatrix.GetSatisfied` | Outbound | Returns the count of requirements satisfied by passing tests. |
| Interface | Direction | Description |
|----------------------------------------------|-----------|---------------------------------------------------|
| `TraceMatrix` constructor | Outbound | Loads test results and maps them to requirements. |
| `TraceMatrix.Export` | Outbound | Exports the trace matrix to a Markdown report. |
| `TraceMatrix.CalculateSatisfiedRequirements` | Outbound | Returns satisfied and total requirement counts. |

## Interactions

Expand Down
2 changes: 0 additions & 2 deletions docs/reqstream/self-test/self-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,3 @@ sections:
- Validation_Run_WithXmlResultsFile_WritesXmlFile
- IntegrationTest_ValidateWithResults_GeneratesTrxFile
- IntegrationTest_ValidateWithResults_GeneratesJUnitFile
children:
- ReqStream-Val-EnforcementMode
2 changes: 1 addition & 1 deletion src/DemaConsulting.ReqStream/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public static void Run(Context context)

if (lintIssues.Count == 0)
{
context.WriteLine($"{context.RequirementsFiles[0]}: No issues found");
context.WriteLine("No issues found");
}

return;
Expand Down
89 changes: 89 additions & 0 deletions test/DemaConsulting.ReqStream.Tests/Cli/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,18 @@ public void Context_Create_NoArguments_ReturnsDefaultContext()
Assert.IsFalse(context.Help);
Assert.IsFalse(context.Silent);
Assert.IsFalse(context.Validate);
Assert.IsFalse(context.Lint);
Assert.IsEmpty(context.RequirementsFiles);
Assert.IsEmpty(context.TestFiles);
Assert.IsNull(context.FilterTags);
Assert.IsNull(context.ResultsFile);
Assert.IsFalse(context.Enforce);
Assert.IsNull(context.RequirementsReport);
Assert.AreEqual(1, context.ReportDepth);
Assert.IsNull(context.Matrix);
Assert.AreEqual(1, context.MatrixDepth);
Assert.IsNull(context.JustificationsFile);
Assert.AreEqual(1, context.JustificationsDepth);
Assert.AreEqual(0, context.ExitCode);
}

Expand Down Expand Up @@ -634,4 +639,88 @@ public void Context_Create_FilterSingleTag_ParsesCorrectly()
Assert.Contains("security", context.FilterTags);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test creating a context with multiple --filter arguments merges into one set.
/// </summary>
[TestMethod]
public void Context_Create_MultipleFilterArguments_MergesIntoSingleSet()
{
using var context = Context.Create(["--filter", "tag1", "--filter", "tag2"]);

Assert.IsNotNull(context.FilterTags);
Assert.HasCount(2, context.FilterTags);
Assert.Contains("tag1", context.FilterTags);
Assert.Contains("tag2", context.FilterTags);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test creating a context with lint flag.
/// </summary>
[TestMethod]
public void Context_Create_LintFlag_SetsLintProperty()
{
using var context = Context.Create(["--lint"]);

Assert.IsTrue(context.Lint);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test creating a context with justifications file.
/// </summary>
[TestMethod]
public void Context_Create_JustificationsFile_SetsJustificationsFileProperty()
{
using var context = Context.Create(["--justifications", "justifications.md"]);

Assert.AreEqual("justifications.md", context.JustificationsFile);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test creating a context with missing justifications filename.
/// </summary>
[TestMethod]
public void Context_Create_MissingJustificationsFilename_ThrowsException()
{
var ex = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--justifications"]));
Assert.Contains("--justifications requires a filename argument", ex.Message);
}

/// <summary>
/// Test creating a context with justifications depth.
/// </summary>
[TestMethod]
public void Context_Create_JustificationsDepth_SetsJustificationsDepthProperty()
{
using var context = Context.Create(["--justifications-depth", "3"]);

Assert.AreEqual(3, context.JustificationsDepth);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test creating a context with missing justifications depth argument.
/// </summary>
[TestMethod]
public void Context_Create_MissingJustificationsDepth_ThrowsException()
{
var ex = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--justifications-depth"]));
Assert.Contains("--justifications-depth requires a depth argument", ex.Message);
}

/// <summary>
/// Test creating a context with invalid justifications depth.
/// </summary>
[TestMethod]
public void Context_Create_InvalidJustificationsDepth_ThrowsException()
{
var ex1 = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--justifications-depth", "invalid"]));
Assert.Contains("--justifications-depth requires a positive integer", ex1.Message);

var ex2 = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--justifications-depth", "0"]));
Assert.Contains("--justifications-depth requires a positive integer", ex2.Message);
}
}
Loading