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
35 changes: 35 additions & 0 deletions .github/scripts/run-specs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Copyright (c) Cratis. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.

# Accept dotnet run arguments, including -- before native xUnit runner options.
set -euo pipefail

report_directory=$(mktemp -d)
trap 'rm -rf -- "$report_directory"' EXIT
report="$report_directory/results.xml"

# set -e preserves a native failure without inspecting or replacing its exit code.
dotnet run "$@" -result-xml "$report"

python3 - "$report" <<'PY'
import re
import sys
import xml.etree.ElementTree as ET

try:
root = ET.parse(sys.argv[1]).getroot()
if root.tag != "assemblies":
raise ValueError("expected an assemblies root")
assemblies = list(root)
if not assemblies or any(assembly.tag != "assembly" for assembly in assemblies):
raise ValueError("expected assembly results")
totals = [assembly.get("total", "") for assembly in assemblies]
if any(re.fullmatch(r"[0-9]+", total) is None for total in totals):
raise ValueError("expected nonnegative integer assembly totals")
if sum(int(total) for total in totals) == 0:
raise ValueError("no tests selected")
except (OSError, ET.ParseError, ValueError) as error:
print(f"Invalid xUnit report {sys.argv[1]}: {error}", file=sys.stderr)
sys.exit(1)
PY
76 changes: 76 additions & 0 deletions .github/scripts/run-specs.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { test } from 'node:test';

const script = join(dirname(fileURLToPath(import.meta.url)), 'run-specs.sh');
const runnerArguments = ['--project', 'Safe.Specs.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--', '-class', 'Safe.Specs.*'];

function runWrapper(t, report, nativeExit = 0) {
const directory = mkdtempSync(join(tmpdir(), 'run-specs-test-'));
t.after(() => rmSync(directory, { recursive: true, force: true }));
const temporaryReports = join(directory, 'reports');
mkdirSync(temporaryReports);
const argumentsFile = join(directory, 'arguments.json');
writeFileSync(join(directory, 'dotnet'), `#!/usr/bin/env node
const fs = require('node:fs');
const args = process.argv.slice(2);
fs.writeFileSync(process.env.ARGUMENTS_FILE, JSON.stringify(args));
if (process.env.REPORT_XML !== undefined) {
fs.writeFileSync(args[args.indexOf('-result-xml') + 1], process.env.REPORT_XML);
}
process.exit(Number(process.env.NATIVE_EXIT));
`, { mode: 0o755 });
const env = {
...process.env,
PATH: `${directory}:${process.env.PATH}`,
TMPDIR: temporaryReports,
ARGUMENTS_FILE: argumentsFile,
NATIVE_EXIT: String(nativeExit)
};
delete env.REPORT_XML;
if (report !== undefined) env.REPORT_XML = report;
const result = spawnSync('bash', [script, ...runnerArguments], { env, encoding: 'utf8' });
assert.equal(result.error, undefined);
assert.equal(result.signal, null);
assert.deepEqual(readdirSync(temporaryReports), []);
return { ...result, arguments: JSON.parse(readFileSync(argumentsFile, 'utf8')) };
}

test('accepts a positive sum across assemblies and forwards runner arguments', t => {
const result = runWrapper(t, '<assemblies><assembly total="0"/><assembly total="4"/></assemblies>');
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(result.arguments.slice(0, -2), ['run', ...runnerArguments]);
assert.equal(result.arguments.at(-2), '-result-xml');
});

test('preserves native failure before attempting to parse a missing report', t => {
const result = runWrapper(t, undefined, 42);
assert.equal(result.status, 42);
assert.equal(result.stderr, '');
});

for (const [name, report] of [
['missing report', undefined],
['malformed XML', '<assemblies><assembly total="4"></assemblies>'],
['wrong root', '<results><assembly total="4"/></results>'],
['missing assemblies', '<assemblies/>'],
['unexpected child', '<assemblies><other total="4"/></assemblies>'],
['missing total', '<assemblies><assembly/></assemblies>'],
['negative total', '<assemblies><assembly total="-1"/></assemblies>'],
['fractional total', '<assemblies><assembly total="1.5"/></assemblies>'],
['nonnumeric total', '<assemblies><assembly total="four"/></assemblies>'],
['zero total', '<assemblies><assembly total="0"/><assembly total="0"/></assemblies>']
]) {
test(`rejects ${name}`, t => {
const result = runWrapper(t, report);
assert.equal(result.status, 1);
assert.match(result.stderr, /Invalid xUnit report/);
});
}
17 changes: 14 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ jobs:
run: dotnet build Samples.slnx --configuration Debug --no-restore

- name: Run focused Debug specifications
shell: bash
run: |
set -euo pipefail
for project in \
Arc/React/Arc.React.Specs/Arc.React.Specs.csproj \
Chronicle/Backend/Backend.Specs/Backend.Specs.csproj \
Expand All @@ -53,17 +55,26 @@ jobs:
Chronicle/Processing/Processing.Specs/Processing.Specs.csproj \
Chronicle/ProjectionStyles/ProjectionStyles.Specs/ProjectionStyles.Specs.csproj \
Chronicle/Quickstart/Common.AspNetCore.Specs/Common.AspNetCore.Specs.csproj \
GeneratedValues/Shop/Rendered/Shop.Rendered.csproj \
GeneratedValues/Shop/Shop.Specs/Shop.Specs.csproj \
LearningPath/05-Testing/Testing.Specs/Testing.Specs.csproj \
LearningPath/07-ValidationAndAuthorization/ValidationAndAuthorization.Specs/ValidationAndAuthorization.Specs.csproj \
LearningPath/08-Production/Production.Specs/Production.Specs.csproj
do
dotnet test "$project" --configuration Debug --verbosity normal
bash .github/scripts/run-specs.sh --project "$project" --configuration Debug --no-build --no-restore --
done

- name: Run Library Lending in-process specifications
run: dotnet test Library/Lending/Lending.csproj --configuration Debug --no-build --filter "FullyQualifiedName~.for_" --verbosity normal
run: bash .github/scripts/run-specs.sh --project Library/Lending/Lending.csproj --configuration Debug --no-build --no-restore -- --specifications -class "*.for_*"

- name: Run Library Members in-process specifications
run: bash .github/scripts/run-specs.sh --project Library/Members/Members.csproj --configuration Debug --no-build --no-restore -- --specifications

# Stage owns this v2 runner until L13/L44 regenerate it.
- name: Run Stage-generated specifications
run: dotnet test GeneratedValues/Shop/Rendered/Shop.Rendered.csproj --configuration Debug --no-build --verbosity normal

- name: Restore Release dependencies
run: dotnet restore Samples.slnx -p:Configuration=Release

- name: Build Release without regenerating proxies
run: dotnet build Samples.slnx --configuration Release --no-restore -p:CratisProxiesOutputPath=
Expand Down
11 changes: 3 additions & 8 deletions Arc/React/Arc.React.Specs/Arc.React.Specs.csproj
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<RootNamespace>Arc.React.Specs</RootNamespace>
<!-- Cratis Specifications uses snake_case facts, inline attributes, and convention-based teardown. -->
<NoWarn>$(NoWarn);CA1001;IDE1006;SA1134</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Cratis.Fundamentals" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="xunit.v3" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Arc/React/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ dotnet build Arc/React/Arc.React.csproj -c Debug
dotnet build Arc/React/Arc.React.csproj -c Release -p:CratisProxiesOutputPath=

# Run the focused backend specifications
dotnet test Arc/React/Arc.React.Specs/Arc.React.Specs.csproj -c Debug
dotnet run --project Arc/React/Arc.React.Specs/Arc.React.Specs.csproj -c Debug --

# Type-check, lint, specify, and bundle React
yarn tsc -b Arc/React/.frontend/tsconfig.json
Expand Down
10 changes: 3 additions & 7 deletions Chronicle/Backend/Backend.Specs/Backend.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@
<PropertyGroup>
<RootNamespace>Chronicle.Backend.Specs</RootNamespace>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Cratis.Chronicle" />
<PackageReference Include="Cratis.Specifications" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.v3" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Chronicle/Backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ app.UseCratisChronicle();

```bash
dotnet build Chronicle/Backend/Backend.csproj
dotnet test Chronicle/Backend/Backend.Specs/Backend.Specs.csproj
dotnet run --project Chronicle/Backend/Backend.Specs/Backend.Specs.csproj --
```

The small specification project checks the append and history behavior without requiring a running Chronicle container.
Expand Down
10 changes: 3 additions & 7 deletions Chronicle/CrossStore/CrossStore.Specs/CrossStore.Specs.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>CrossStore.Specs</RootNamespace>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand All @@ -11,11 +11,7 @@

<ItemGroup>
<PackageReference Include="Cratis.Specifications" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Chronicle/CrossStore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public FulfillmentOrderReceived Requested(OrderRequestedForFulfillment @event) =

```bash
dotnet build Chronicle/CrossStore/CrossStore.csproj
dotnet test Chronicle/CrossStore/CrossStore.Specs/CrossStore.Specs.csproj
dotnet run --project Chronicle/CrossStore/CrossStore.Specs/CrossStore.Specs.csproj --
```

## Make it yours
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>MultiTenancy.Specs</RootNamespace>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand All @@ -12,11 +12,7 @@
<ItemGroup>
<PackageReference Include="Cratis.Chronicle.Testing" />
<PackageReference Include="Cratis.Specifications" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Chronicle/MultiTenancy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Build it directly while exploring, or as part of the root Samples solution:

```bash
dotnet build Chronicle/MultiTenancy/MultiTenancy.csproj
dotnet test Chronicle/MultiTenancy/MultiTenancy.Specs/MultiTenancy.Specs.csproj
dotnet run --project Chronicle/MultiTenancy/MultiTenancy.Specs/MultiTenancy.Specs.csproj --
```

The focused namespace-isolation spec uses Chronicle's in-process event scenario, so the test suite needs no container.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>OperationsDiagnosis.Specs</RootNamespace>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand All @@ -11,11 +11,7 @@

<ItemGroup>
<PackageReference Include="Cratis.Specifications" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Chronicle/OperationsDiagnosis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Use **Overview**, **Observers**, **Failures**, and **Event Sequences** to follow

```bash
dotnet build Chronicle/OperationsDiagnosis/OperationsDiagnosis.csproj
dotnet test Chronicle/OperationsDiagnosis/OperationsDiagnosis.Specs/OperationsDiagnosis.Specs.csproj
dotnet run --project Chronicle/OperationsDiagnosis/OperationsDiagnosis.Specs/OperationsDiagnosis.Specs.csproj --
```

## Make it yours—safely
Expand Down
10 changes: 3 additions & 7 deletions Chronicle/Processing/Processing.Specs/Processing.Specs.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Processing.Specs</RootNamespace>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand All @@ -11,11 +11,7 @@

<ItemGroup>
<PackageReference Include="Cratis.Specifications" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Chronicle/Processing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Build it on its own while exploring, or as part of the root Samples solution:

```bash
dotnet build Chronicle/Processing/Processing.csproj
dotnet test Chronicle/Processing/Processing.Specs/Processing.Specs.csproj
dotnet run --project Chronicle/Processing/Processing.Specs/Processing.Specs.csproj --
```

The focused specifications show the projection binding, reducer fold (including an update before opening), and deterministic reactor result without introducing external infrastructure.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>ProjectionStyles.Specs</RootNamespace>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
Expand All @@ -10,9 +10,7 @@
<ItemGroup>
<PackageReference Include="Cratis.Chronicle.Testing" />
<PackageReference Include="Cratis.Specifications" />
<PackageReference Include="Cratis.Specifications.XUnit" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
<PackageReference Include="Cratis.Specifications.XUnit.v3" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Chronicle/ProjectionStyles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Both forms process an order's stream: neither can join `ProductRegistered` or `P
Install the [.NET 10 SDK](https://dotnet.microsoft.com/download), then run from the repository root:

```bash
dotnet test Chronicle/ProjectionStyles/ProjectionStyles.Specs/ProjectionStyles.Specs.csproj --configuration Debug
dotnet run --project Chronicle/ProjectionStyles/ProjectionStyles.Specs/ProjectionStyles.Specs.csproj --configuration Debug --
```

[`ProjectionStyles.Specs`](./ProjectionStyles.Specs/) uses `ReadModelScenario<T>` to discover each C# style and drive the same histories in memory: product joins before children, a child-only last event, duplicate adds, quantity adjustment, removal, and cancellation. Additional specs cover placement, shipment, and a rename after the line exists. A direct reducer spec checks an explicit occurrence time and preservation of prior state.
Expand Down
2 changes: 1 addition & 1 deletion Chronicle/Quickstart/AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ dotnet build AspNetCore.csproj --configuration Debug
The read-model collection names and the book's self-reducing transitions are covered by specs in `Common.AspNetCore.Specs`:

```bash
dotnet test ../Common.AspNetCore.Specs/Common.AspNetCore.Specs.csproj
dotnet run --project ../Common.AspNetCore.Specs/Common.AspNetCore.Specs.csproj --
```

## Send a couple of events
Expand Down
Loading
Loading