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
22 changes: 18 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
.idea/
# DocFX generated HTML site
_site/

# DocFX metadata output
obj/api/

# .NET build artifacts
bin/
obj/
_site/
__pycache__/
*.pyc
*.dll
*.exe
*.pdb
*.deps.json
*.runtimeconfig.json

# Dumps
*.dmp
*.dump
*.mdmp
*.core
1 change: 1 addition & 0 deletions ModularityKit.Mutator.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<Project Path="Examples/Governance/VersionedResolution/VersionedResolution.csproj" />
</Folder>
<Folder Name="/Tests/">
<Project Path="Tests/ModularityKit.Mutator.Examples.SmokeTests/ModularityKit.Mutator.Examples.SmokeTests.csproj" />
<Project Path="Tests/ModularityKit.Mutator.Tests/ModularityKit.Mutator.Tests.csproj" />
<Project Path="Tests/ModularityKit.Mutator.Governance.Tests/ModularityKit.Mutator.Governance.Tests.csproj" />
<Project Path="Tests/ModularityKit.Mutator.Governance.Redis.Tests/ModularityKit.Mutator.Governance.Redis.Tests.csproj" />
Expand Down
6 changes: 6 additions & 0 deletions Tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea/
bin/
obj/
_site/
__pycache__/
*.pyc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using ModularityKit.Mutator.Examples.SmokeTests.Support;

namespace ModularityKit.Mutator.Examples.SmokeTests.Examples.Core;

/// <summary>
/// Smoke coverage for the executable samples shipped under <c>Examples/Core</c>.
/// </summary>
public sealed class CoreExamplesSmokeTests
{
[Fact]
public Task BillingQuotas_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("BillingQuotas", "Examples/Core/BillingQuotas/BillingQuotas.csproj"));

[Fact]
public Task FeatureFlags_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("FeatureFlags", "Examples/Core/FeatureFlags/FeatureFlags.csproj"));

[Fact]
public Task IamRoles_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("IamRoles", "Examples/Core/IamRoles/IamRoles.csproj"));

[Fact]
public Task WorkflowApprovals_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("WorkflowApprovals", "Examples/Core/WorkflowApprovals/WorkflowApprovals.csproj"));

private static ExampleSmokeCase Create(string name, string projectPath)
=> new(
name,
projectPath,
result =>
{
if (result.TimedOut)
return "process timed out";

if (result.ExitCode != 0)
return $"expected exit code 0 but got {result.ExitCode}";

if (string.IsNullOrWhiteSpace(result.StandardOutput))
return "example did not produce any stdout";

if (!result.StandardOutput.Contains("METRICS & STATISTICS", StringComparison.Ordinal))
return "expected metrics section in stdout";

if (result.StandardError.Contains("Unhandled exception", StringComparison.OrdinalIgnoreCase))
return "stderr contains unhandled exception output";

return null;
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using ModularityKit.Mutator.Examples.SmokeTests.Support;

namespace ModularityKit.Mutator.Examples.SmokeTests.Examples.Governance;

/// <summary>
/// Smoke coverage for the executable samples shipped under <c>Examples/Governance</c>.
/// </summary>
public sealed class GovernanceExamplesSmokeTests
{
[Fact]
public Task ApprovalWorkflow_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("ApprovalWorkflow", "Examples/Governance/ApprovalWorkflow/ApprovalWorkflow.csproj"));

[Fact]
public Task GovernedExecution_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("GovernedExecution", "Examples/Governance/GovernedExecution/GovernedExecution.csproj"));

[Fact]
public Task DecisionTaxonomy_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("DecisionTaxonomy", "Examples/Governance/DecisionTaxonomy/DecisionTaxonomy.csproj"));

[Fact]
public Task Queries_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("Queries", "Examples/Governance/Queries/Queries.csproj"));

[Fact]
public Task RedisQueries_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(CreateRedis());

[Fact]
public Task RequestLifecycle_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("RequestLifecycle", "Examples/Governance/RequestLifecycle/RequestLifecycle.csproj"));

[Fact]
public Task VersionedResolution_runs_successfully()
=> ExampleSmokeRunner.RunAndAssertAsync(Create("VersionedResolution", "Examples/Governance/VersionedResolution/VersionedResolution.csproj"));

private static ExampleSmokeCase Create(string name, string projectPath)
=> new(
name,
projectPath,
result =>
{
if (result.TimedOut)
return "process timed out";

if (result.ExitCode != 0)
return $"expected exit code 0 but got {result.ExitCode}";

if (string.IsNullOrWhiteSpace(result.StandardOutput) && string.IsNullOrWhiteSpace(result.StandardError))
return "example did not produce any output";

if (result.StandardError.Contains("Unhandled exception", StringComparison.OrdinalIgnoreCase))
return "stderr contains unhandled exception output";

return null;
});

private static ExampleSmokeCase CreateRedis()
=> new(
"RedisQueries",
"Examples/Governance/RedisQueries/RedisQueries.csproj",
result =>
{
if (result.TimedOut)
return "process timed out";

if (result.ExitCode != 0)
return $"expected exit code 0 but got {result.ExitCode}";

var hasRedisOutput = result.StandardOutput.Contains("Pending Approval Queue", StringComparison.Ordinal)
&& result.StandardOutput.Contains("Recent Execution Outcomes", StringComparison.Ordinal);

var hasExpectedDependencyWarning =
result.StandardError.Contains("Could not connect to Redis", StringComparison.Ordinal)
&& result.StandardError.Contains("Start Redis locally or set MODULARITYKIT_REDIS to a reachable endpoint.", StringComparison.Ordinal);

if (!hasRedisOutput && !hasExpectedDependencyWarning)
return "expected either Redis query output or the documented Redis prerequisite warning";

if (result.StandardError.Contains("Unhandled exception", StringComparison.OrdinalIgnoreCase))
return "stderr contains unhandled exception output";

return null;
},
new Dictionary<string, string?>
{
["MODULARITYKIT_REDIS"] = "localhost:6379,connectTimeout=1000,abortConnect=false,syncTimeout=1000"
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

</Project>
62 changes: 62 additions & 0 deletions Tests/ModularityKit.Mutator.Examples.SmokeTests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Example Smoke Tests

This project provides lightweight smoke coverage for the executable samples under:

- `Examples/Core`
- `Examples/Governance`

The goal is to catch sample drift against the current public API and runtime behavior without turning the examples into a second full test suite.

## Structure

- `Examples/Core` contains smoke tests for core runtime samples.
- `Examples/Governance` contains smoke tests for governance and Redis-backed samples.
- `Support` contains the shared runner and result models used by the smoke layer.

## How it works

Each smoke test:

1. builds the target example project in `Release`
2. executes the built assembly with `dotnet exec`
3. captures `stdout` and `stderr`
4. validates a small set of expected signals for that sample

The runner keeps the checks focused on sample viability:

- non-zero exit codes fail the test
- hung processes are terminated by timeout
- failures include captured output for fast diagnosis

## Redis example behavior

`RedisQueries` accepts either:

- normal Redis-backed query output, or
- the documented "could not connect to Redis" message

That keeps the sample smoke testable even when Redis is not running locally.

## Run

Build:

```bash
dotnet build Tests/ModularityKit.Mutator.Examples.SmokeTests/ModularityKit.Mutator.Examples.SmokeTests.csproj -c Debug
```

Run:

```bash
dotnet test Tests/ModularityKit.Mutator.Examples.SmokeTests/ModularityKit.Mutator.Examples.SmokeTests.csproj -c Debug --no-build
```

Run a single smoke test:

```bash
dotnet test Tests/ModularityKit.Mutator.Examples.SmokeTests/ModularityKit.Mutator.Examples.SmokeTests.csproj -c Debug --no-build --filter FullyQualifiedName=ModularityKit.Mutator.Examples.SmokeTests.GovernanceExamplesSmokeTests.RedisQueries_runs_successfully
```

## Environment note

`vstest` uses a local socket. In restricted sandboxes that block local bind operations, the test host may need to run outside the sandbox even though the examples themselves are local processes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ModularityKit.Mutator.Examples.SmokeTests.Support;

/// <summary>
/// Captures the observable outcome of running an example process.
/// </summary>
public sealed record ExampleRunResult(
int ExitCode,
string StandardOutput,
string StandardError,
bool TimedOut);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ModularityKit.Mutator.Examples.SmokeTests.Support;

/// <summary>
/// Describes a single executable example covered by the smoke test suite.
/// </summary>
public sealed record ExampleSmokeCase(
string Name,
string ProjectPath,
Func<ExampleRunResult, string?> Validate,
IReadOnlyDictionary<string, string?>? EnvironmentVariables = null)
{
public override string ToString() => Name;
}
Loading
Loading