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
54 changes: 31 additions & 23 deletions .github/workflows/publish-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: Publish Artifacts

on:
workflow_call:
outputs:
signing_enabled:
description: "Whether package signing was enabled for this run."
value: ${{ jobs.package.outputs.signing_enabled }}
inputs:
package_version:
description: "Optional package version, usually the release tag without the leading v."
Expand Down Expand Up @@ -36,13 +40,13 @@ on:
default: true
secrets:
NUGET_SIGN_CERTIFICATE_BASE64:
required: true
required: false
NUGET_SIGN_CERTIFICATE_PASSWORD:
required: true
required: false
NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT:
required: true
required: false
NUGET_SIGN_TIMESTAMP_URL:
required: true
required: false

permissions:
contents: read
Expand All @@ -51,6 +55,8 @@ jobs:
package:
name: Pack packages
runs-on: ubuntu-latest
outputs:
signing_enabled: ${{ steps.signing.outputs.enabled }}

steps:
- name: Checkout
Expand Down Expand Up @@ -137,32 +143,38 @@ jobs:
-o nupkg
-p:PackageVersion=${{ steps.version.outputs.redis_version }}

- name: Import signing certificate
- name: Resolve signing mode
id: signing
env:
NUGET_SIGN_CERTIFICATE_BASE64: ${{ secrets.NUGET_SIGN_CERTIFICATE_BASE64 }}
NUGET_SIGN_CERTIFICATE_PASSWORD: ${{ secrets.NUGET_SIGN_CERTIFICATE_PASSWORD }}
NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT: ${{ secrets.NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT }}
NUGET_SIGN_TIMESTAMP_URL: ${{ secrets.NUGET_SIGN_TIMESTAMP_URL }}
run: |
if [ -z "$NUGET_SIGN_CERTIFICATE_BASE64" ]; then
echo "NUGET_SIGN_CERTIFICATE_BASE64 secret is required for package signing." >&2
exit 1
if [ -n "$NUGET_SIGN_CERTIFICATE_BASE64" ] \
&& [ -n "$NUGET_SIGN_CERTIFICATE_PASSWORD" ] \
&& [ -n "$NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT" ] \
&& [ -n "$NUGET_SIGN_TIMESTAMP_URL" ]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
echo "Package signing enabled."
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "Package signing disabled. Continuing with unsigned packages."
fi

- name: Import signing certificate
if: ${{ steps.signing.outputs.enabled == 'true' }}
env:
NUGET_SIGN_CERTIFICATE_BASE64: ${{ secrets.NUGET_SIGN_CERTIFICATE_BASE64 }}
run: |
printf '%s' "$NUGET_SIGN_CERTIFICATE_BASE64" | base64 --decode > signing-cert.pfx

- name: Sign packages
if: ${{ steps.signing.outputs.enabled == 'true' }}
env:
NUGET_SIGN_CERTIFICATE_PASSWORD: ${{ secrets.NUGET_SIGN_CERTIFICATE_PASSWORD }}
NUGET_SIGN_TIMESTAMP_URL: ${{ secrets.NUGET_SIGN_TIMESTAMP_URL }}
run: |
if [ -z "$NUGET_SIGN_CERTIFICATE_PASSWORD" ]; then
echo "NUGET_SIGN_CERTIFICATE_PASSWORD secret is required for package signing." >&2
exit 1
fi

if [ -z "$NUGET_SIGN_TIMESTAMP_URL" ]; then
echo "NUGET_SIGN_TIMESTAMP_URL secret is required for package signing." >&2
exit 1
fi

for package in nupkg/*.nupkg; do
dotnet nuget sign "$package" \
--certificate-path signing-cert.pfx \
Expand All @@ -174,14 +186,10 @@ jobs:
done

- name: Verify signed packages
if: ${{ steps.signing.outputs.enabled == 'true' }}
env:
NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT: ${{ secrets.NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT }}
run: |
if [ -z "$NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT" ]; then
echo "NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT secret is required for package verification." >&2
exit 1
fi

for package in nupkg/*.nupkg; do
dotnet nuget verify "$package" \
--all \
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/publish-attested.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ on:
type: string
secrets:
NUGET_SIGN_CERTIFICATE_BASE64:
required: true
required: false
NUGET_SIGN_CERTIFICATE_PASSWORD:
required: true
required: false
NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT:
required: true
required: false
NUGET_SIGN_TIMESTAMP_URL:
required: true
required: false

permissions:
contents: write
Expand Down Expand Up @@ -56,6 +56,11 @@ jobs:
path: dist
merge-multiple: true

- name: Attest package artifacts
uses: actions/attest-build-provenance@v2
with:
subject-path: dist/*.nupkg

- name: Create or update draft release
env:
GITHUB_TOKEN: ${{ github.token }}
Expand Down
12 changes: 2 additions & 10 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,10 @@ jobs:
merge-multiple: true

- name: Verify signed packages
if: ${{ needs.package.outputs.signing_enabled == 'true' }}
env:
NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT: ${{ secrets.NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT }}
run: |
if [ -z "$NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT" ]; then
echo "NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT secret is required for package verification." >&2
exit 1
fi

for package in dist/*.nupkg; do
dotnet nuget verify "$package" \
--all \
Expand Down Expand Up @@ -184,14 +180,10 @@ jobs:
merge-multiple: true

- name: Verify signed packages
if: ${{ needs.package.outputs.signing_enabled == 'true' }}
env:
NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT: ${{ secrets.NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT }}
run: |
if [ -z "$NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT" ]; then
echo "NUGET_SIGN_CERTIFICATE_SHA256_FINGERPRINT secret is required for package verification." >&2
exit 1
fi

for package in dist/*.nupkg; do
dotnet nuget verify "$package" \
--all \
Expand Down
46 changes: 46 additions & 0 deletions Benchmarks/Engine/MutationEngineBatchBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using BenchmarkDotNet.Attributes;
using ModularityKit.Mutator.Abstractions;
using ModularityKit.Mutator.Abstractions.Context;
using ModularityKit.Mutator.Abstractions.Engine;

namespace ModularityKit.Mutator.Benchmarks.Engine;

/// <summary>
/// Benchmarks batch commit execution overhead for the performance-oriented engine path.
/// </summary>
[MemoryDiagnoser]
[InProcess]
public class MutationEngineBatchBenchmarks
{
private IMutationEngine _performanceEngine = null!;
private MutationEngineBenchmarkSupport.CounterState _state = null!;
private IReadOnlyList<IMutation<MutationEngineBenchmarkSupport.CounterState>> _batchMutations = null!;

/// <summary>
/// Controls how many commit mutations are executed in a single batch benchmark iteration.
/// </summary>
[Params(10, 100)]
public int BatchSize { get; set; }

/// <summary>
/// Prepares the engine, base state, and batch mutation list for the selected batch size.
/// </summary>
[GlobalSetup]
public void Setup()
{
_performanceEngine = MutationEngineBenchmarkSupport.BuildEngine(MutationEngineOptions.Performance);
_state = new MutationEngineBenchmarkSupport.CounterState(42);
_batchMutations = [.. Enumerable.Range(0, BatchSize)
.Select(i => MutationEngineBenchmarkSupport.CreateCounterMutation(MutationMode.Commit, $"batch-{i}"))];
}

/// <summary>
/// Measures sequential batch commit execution without policy pressure.
/// </summary>
[Benchmark]
public async Task Batch_Commit_Performance_NoPolicy()
{
var result = await _performanceEngine.ExecuteBatchAsync(_batchMutations, _state);
GC.KeepAlive(result);
}
}
113 changes: 113 additions & 0 deletions Benchmarks/Engine/MutationEngineBenchmarkSupport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using Microsoft.Extensions.DependencyInjection;
using ModularityKit.Mutator.Abstractions;
using ModularityKit.Mutator.Abstractions.Changes;
using ModularityKit.Mutator.Abstractions.Context;
using ModularityKit.Mutator.Abstractions.Engine;
using ModularityKit.Mutator.Abstractions.Intent;
using ModularityKit.Mutator.Abstractions.Policies;
using ModularityKit.Mutator.Abstractions.Results;
using ModularityKit.Mutator.Runtime;

namespace ModularityKit.Mutator.Benchmarks.Engine;

/// <summary>
/// Shared support types for core engine benchmark scenarios.
/// </summary>
internal static class MutationEngineBenchmarkSupport
{
public const string CounterStateId = "benchmark-counter";

public static IMutationEngine BuildEngine(
MutationEngineOptions options,
Action<IMutationEngine>? configure = null)
{
var services = new ServiceCollection();
services.AddMutators(options);

var engine = services
.BuildServiceProvider()
.GetRequiredService<IMutationEngine>();

configure?.Invoke(engine);
return engine;
}

public static IncrementCounterMutation CreateCounterMutation(MutationMode mode, string operationSuffix)
{
var context = MutationContext.System("benchmark")
with
{
StateId = CounterStateId,
Mode = mode,
CorrelationId = $"{CounterStateId}:{operationSuffix}"
};

return new IncrementCounterMutation(context);
}

/// <summary>
/// Minimal counter state used by engine benchmark scenarios.
/// </summary>
/// <param name="Value">The current counter value.</param>
public sealed record CounterState(int Value);

/// <summary>
/// Minimal counter mutation shared by core engine benchmark scenarios.
/// </summary>
public sealed class IncrementCounterMutation(MutationContext context) : IMutation<CounterState>
{
public MutationIntent Intent { get; } = new()
{
OperationName = "IncrementCounter",
Category = "Benchmark",
Description = "Increment the benchmark counter by one",
RiskLevel = MutationRiskLevel.Low,
IsReversible = true
};

public MutationContext Context { get; } = context;

public MutationResult<CounterState> Apply(CounterState state)
{
var next = state with { Value = state.Value + 1 };

return MutationResult<CounterState>.Success(
next,
ChangeSet.Single(StateChange.Modified(nameof(CounterState.Value), state.Value, next.Value)));
}

public ValidationResult Validate(CounterState state)
{
var result = ValidationResult.Success();

if (state.Value < 0)
result.AddError(nameof(CounterState.Value), "Counter value must be non-negative.");

return result;
}

public MutationResult<CounterState> Simulate(CounterState state)
{
var next = state with { Value = state.Value + 1 };

return MutationResult<CounterState>.Success(
next,
ChangeSet.Single(StateChange.Modified(nameof(CounterState.Value), state.Value, next.Value)));
}
}

/// <summary>
/// Trivial allow policy used to measure policy-aware engine paths.
/// </summary>
public sealed class AllowAllCounterPolicy : IMutationPolicy<CounterState>
{
public string Name => nameof(AllowAllCounterPolicy);

public int Priority => 0;

public string? Description => "Always allows the benchmark counter mutation.";

public PolicyDecision Evaluate(IMutation<CounterState> mutation, CounterState state)
=> PolicyDecision.Allow(Name, "Benchmark policy allows all mutations.");
}
}
53 changes: 53 additions & 0 deletions Benchmarks/Engine/MutationEngineCommitBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using BenchmarkDotNet.Attributes;
using ModularityKit.Mutator.Abstractions;
using ModularityKit.Mutator.Abstractions.Engine;

namespace ModularityKit.Mutator.Benchmarks.Engine;

/// <summary>
/// Benchmarks single commit execution for the core mutation engine with and without policy evaluation.
/// </summary>
[MemoryDiagnoser]
[InProcess]
public class MutationEngineCommitBenchmarks
{
private IMutationEngine _performanceEngine = null!;
private IMutationEngine _strictEngine = null!;
private MutationEngineBenchmarkSupport.CounterState _state = null!;
private MutationEngineBenchmarkSupport.IncrementCounterMutation _commitMutation = null!;

/// <summary>
/// Prepares the benchmark engines, state snapshot, and commit mutation instance.
/// </summary>
[GlobalSetup]
public void Setup()
{
_performanceEngine = MutationEngineBenchmarkSupport.BuildEngine(MutationEngineOptions.Performance);
_strictEngine = MutationEngineBenchmarkSupport.BuildEngine(
MutationEngineOptions.Strict,
engine => engine.RegisterPolicy(new MutationEngineBenchmarkSupport.AllowAllCounterPolicy()));

_state = new MutationEngineBenchmarkSupport.CounterState(42);
_commitMutation = MutationEngineBenchmarkSupport.CreateCounterMutation(Abstractions.Context.MutationMode.Commit, "commit-one");
}

/// <summary>
/// Measures a commit execution through the performance-oriented runtime path without policies.
/// </summary>
[Benchmark(Baseline = true)]
public async Task Commit_Performance_NoPolicy()
{
var result = await _performanceEngine.ExecuteAsync(_commitMutation, _state);
GC.KeepAlive(result);
}

/// <summary>
/// Measures a commit execution through the strict runtime path with one allow policy.
/// </summary>
[Benchmark]
public async Task Commit_Strict_WithPolicy()
{
var result = await _strictEngine.ExecuteAsync(_commitMutation, _state);
GC.KeepAlive(result);
}
}
Loading
Loading