save progress

This commit is contained in:
master
2026-01-09 18:27:36 +02:00
parent e608752924
commit a21d3dbc1f
361 changed files with 63068 additions and 1192 deletions

View File

@@ -0,0 +1,97 @@
// <copyright file="AgentStatisticsTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
using Xunit;
namespace StellaOps.Signals.RuntimeAgent.Tests;
/// <summary>
/// Tests for <see cref="AgentStatistics"/>.
/// </summary>
[Trait("Category", "Unit")]
public class AgentStatisticsTests
{
[Fact]
public void Empty_HasZeroValues()
{
var timeProvider = new FakeTimeProvider();
timeProvider.SetUtcNow(new DateTimeOffset(2026, 1, 9, 12, 0, 0, TimeSpan.Zero));
var stats = AgentStatistics.Empty("test-agent", timeProvider);
stats.AgentId.Should().Be("test-agent");
stats.State.Should().Be(AgentState.Stopped);
stats.TotalEventsCollected.Should().Be(0);
stats.EventsLastMinute.Should().Be(0);
stats.EventsDropped.Should().Be(0);
stats.UniqueMethodsObserved.Should().Be(0);
stats.Uptime.Should().Be(TimeSpan.Zero);
}
[Fact]
public void Empty_UsesProvidedTimestamp()
{
var timeProvider = new FakeTimeProvider();
var expectedTime = new DateTimeOffset(2026, 1, 9, 12, 0, 0, TimeSpan.Zero);
timeProvider.SetUtcNow(expectedTime);
var stats = AgentStatistics.Empty("test-agent", timeProvider);
stats.Timestamp.Should().Be(expectedTime);
}
[Fact]
public void Statistics_CanBeCreatedWithValues()
{
var stats = new AgentStatistics
{
AgentId = "agent-123",
Timestamp = DateTimeOffset.UtcNow,
State = AgentState.Running,
Uptime = TimeSpan.FromHours(2),
TotalEventsCollected = 50000,
EventsLastMinute = 1234,
EventsDropped = 50,
UniqueMethodsObserved = 500,
UniqueTypesObserved = 100,
UniqueAssembliesObserved = 20,
BufferUtilizationPercent = 45.5,
EstimatedCpuOverheadPercent = 2.3,
MemoryUsageBytes = 1024 * 1024 * 50
};
stats.AgentId.Should().Be("agent-123");
stats.TotalEventsCollected.Should().Be(50000);
stats.BufferUtilizationPercent.Should().BeApproximately(45.5, 0.01);
}
[Fact]
public void Statistics_CanTrackError()
{
var stats = new AgentStatistics
{
AgentId = "agent-123",
Timestamp = DateTimeOffset.UtcNow,
State = AgentState.Error,
Uptime = TimeSpan.FromMinutes(5),
TotalEventsCollected = 1000,
EventsLastMinute = 0,
EventsDropped = 0,
UniqueMethodsObserved = 100,
UniqueTypesObserved = 50,
UniqueAssembliesObserved = 10,
BufferUtilizationPercent = 0,
EstimatedCpuOverheadPercent = 0,
MemoryUsageBytes = 0,
LastError = "Connection lost",
LastErrorTimestamp = DateTimeOffset.UtcNow
};
stats.State.Should().Be(AgentState.Error);
stats.LastError.Should().Be("Connection lost");
stats.LastErrorTimestamp.Should().NotBeNull();
}
}

View File

@@ -0,0 +1,170 @@
// <copyright file="DotNetEventPipeAgentTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using Xunit;
namespace StellaOps.Signals.RuntimeAgent.Tests;
/// <summary>
/// Tests for <see cref="DotNetEventPipeAgent"/>.
/// </summary>
[Trait("Category", "Unit")]
public class DotNetEventPipeAgentTests
{
private readonly FakeTimeProvider _timeProvider = new();
private readonly TestGuidGenerator _guidGenerator = new();
[Fact]
public void Platform_IsDotNet()
{
var agent = CreateAgent();
agent.Platform.Should().Be(RuntimePlatform.DotNet);
}
[Fact]
public async Task EmitMethodObservation_WithIncludePattern_EmitsMatchingEvent()
{
var agent = CreateAgent();
var options = RuntimeAgentOptions.Development with
{
IncludePatterns = ["MyApp.*"],
ExcludePatterns = []
};
await agent.StartAsync(options, CancellationToken.None);
agent.EmitMethodObservation("ProcessData", "MyApp.Services.DataService", "MyApp.dll");
var stats = agent.GetStatistics();
stats.TotalEventsCollected.Should().Be(1);
stats.UniqueMethodsObserved.Should().Be(1);
}
[Fact]
public async Task EmitMethodObservation_WithExcludePattern_FiltersEvent()
{
var agent = CreateAgent();
var options = RuntimeAgentOptions.Development with
{
ExcludePatterns = ["System.*"]
};
await agent.StartAsync(options, CancellationToken.None);
agent.EmitMethodObservation("ToString", "System.String", "System.Runtime.dll");
var stats = agent.GetStatistics();
stats.TotalEventsCollected.Should().Be(0);
}
[Fact]
public async Task EmitMethodObservation_TracksUniqueMethods()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development with { ExcludePatterns = [] }, CancellationToken.None);
agent.EmitMethodObservation("Method1", "Type1", "Assembly1");
agent.EmitMethodObservation("Method1", "Type1", "Assembly1"); // Same
agent.EmitMethodObservation("Method2", "Type1", "Assembly1"); // Different method
var stats = agent.GetStatistics();
stats.TotalEventsCollected.Should().Be(3);
stats.UniqueMethodsObserved.Should().Be(2);
}
[Fact]
public async Task EmitMethodObservation_TracksUniqueTypes()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development with { ExcludePatterns = [] }, CancellationToken.None);
agent.EmitMethodObservation("Method1", "Type1", "Assembly1");
agent.EmitMethodObservation("Method2", "Type2", "Assembly1");
agent.EmitMethodObservation("Method3", "Type1", "Assembly1"); // Same type
var stats = agent.GetStatistics();
stats.UniqueTypesObserved.Should().Be(2);
}
[Fact]
public async Task EmitMethodObservation_TracksUniqueAssemblies()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development with { ExcludePatterns = [] }, CancellationToken.None);
agent.EmitMethodObservation("Method1", "Type1", "Assembly1.dll");
agent.EmitMethodObservation("Method2", "Type2", "Assembly2.dll");
agent.EmitMethodObservation("Method3", "Type3", "Assembly1.dll"); // Same assembly
var stats = agent.GetStatistics();
stats.UniqueAssembliesObserved.Should().Be(2);
}
[Fact]
public async Task StreamEventsAsync_YieldsEmittedEvents()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development with { ExcludePatterns = [] }, CancellationToken.None);
agent.EmitMethodObservation("Method1", "Type1", "Assembly1");
agent.EmitMethodObservation("Method2", "Type2", "Assembly2");
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
var events = new List<RuntimeMethodEvent>();
try
{
await foreach (var evt in agent.StreamEventsAsync(cts.Token))
{
events.Add(evt);
if (events.Count >= 2)
break;
}
}
catch (OperationCanceledException)
{
// Expected
}
events.Should().HaveCount(2);
events[0].MethodName.Should().Be("Method1");
events[1].MethodName.Should().Be("Method2");
}
[Fact]
public async Task GetStatistics_TracksUptime()
{
var agent = CreateAgent();
_timeProvider.SetUtcNow(new DateTimeOffset(2026, 1, 9, 10, 0, 0, TimeSpan.Zero));
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
_timeProvider.Advance(TimeSpan.FromMinutes(5));
var stats = agent.GetStatistics();
stats.Uptime.Should().Be(TimeSpan.FromMinutes(5));
}
private DotNetEventPipeAgent CreateAgent()
{
return new DotNetEventPipeAgent(
"test-agent",
_timeProvider,
_guidGenerator,
NullLogger<DotNetEventPipeAgent>.Instance);
}
private sealed class TestGuidGenerator : IGuidGenerator
{
private int _counter;
public Guid NewGuid()
{
var bytes = new byte[16];
BitConverter.GetBytes(++_counter).CopyTo(bytes, 0);
return new Guid(bytes);
}
}
}

View File

@@ -0,0 +1,144 @@
// <copyright file="RuntimeAgentBaseTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using Xunit;
namespace StellaOps.Signals.RuntimeAgent.Tests;
/// <summary>
/// Tests for <see cref="RuntimeAgentBase"/>.
/// </summary>
[Trait("Category", "Unit")]
public class RuntimeAgentBaseTests
{
private readonly FakeTimeProvider _timeProvider = new();
private readonly TestGuidGenerator _guidGenerator = new();
[Fact]
public void Constructor_InitializesProperties()
{
var agent = CreateAgent();
agent.AgentId.Should().Be("test-agent");
agent.Platform.Should().Be(RuntimePlatform.DotNet);
agent.State.Should().Be(AgentState.Stopped);
agent.Posture.Should().Be(RuntimePosture.None);
}
[Fact]
public async Task StartAsync_TransitionsToRunning()
{
var agent = CreateAgent();
var options = RuntimeAgentOptions.Development;
await agent.StartAsync(options, CancellationToken.None);
agent.State.Should().Be(AgentState.Running);
agent.Posture.Should().Be(RuntimePosture.ActiveTracing);
}
[Fact]
public async Task StartAsync_WhenAlreadyRunning_Throws()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
var act = () => agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>();
}
[Fact]
public async Task StopAsync_TransitionsToStopped()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
await agent.StopAsync(CancellationToken.None);
agent.State.Should().Be(AgentState.Stopped);
}
[Fact]
public async Task PauseAsync_TransitionsToPaused()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
await agent.PauseAsync(CancellationToken.None);
agent.State.Should().Be(AgentState.Paused);
}
[Fact]
public async Task ResumeAsync_TransitionsBackToRunning()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
await agent.PauseAsync(CancellationToken.None);
await agent.ResumeAsync(CancellationToken.None);
agent.State.Should().Be(AgentState.Running);
}
[Fact]
public async Task SetPostureAsync_ChangesPosture()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
await agent.SetPostureAsync(RuntimePosture.Sampled, CancellationToken.None);
agent.Posture.Should().Be(RuntimePosture.Sampled);
}
[Fact]
public async Task GetStatistics_ReturnsStatistics()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
var stats = agent.GetStatistics();
stats.AgentId.Should().Be("test-agent");
stats.State.Should().Be(AgentState.Running);
stats.TotalEventsCollected.Should().Be(0);
}
[Fact]
public async Task DisposeAsync_StopsAgent()
{
var agent = CreateAgent();
await agent.StartAsync(RuntimeAgentOptions.Development, CancellationToken.None);
await agent.DisposeAsync();
agent.State.Should().Be(AgentState.Stopped);
}
private DotNetEventPipeAgent CreateAgent()
{
return new DotNetEventPipeAgent(
"test-agent",
_timeProvider,
_guidGenerator,
NullLogger<DotNetEventPipeAgent>.Instance);
}
private sealed class TestGuidGenerator : IGuidGenerator
{
private int _counter;
public Guid NewGuid()
{
var bytes = new byte[16];
BitConverter.GetBytes(++_counter).CopyTo(bytes, 0);
return new Guid(bytes);
}
}
}

View File

@@ -0,0 +1,89 @@
// <copyright file="RuntimeAgentOptionsTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using FluentAssertions;
using Xunit;
namespace StellaOps.Signals.RuntimeAgent.Tests;
/// <summary>
/// Tests for <see cref="RuntimeAgentOptions"/>.
/// </summary>
[Trait("Category", "Unit")]
public class RuntimeAgentOptionsTests
{
[Fact]
public void Default_HasSampledPosture()
{
var options = new RuntimeAgentOptions();
options.Posture.Should().Be(RuntimePosture.Sampled);
}
[Fact]
public void Default_HasExcludePatterns()
{
var options = new RuntimeAgentOptions();
options.ExcludePatterns.Should().NotBeEmpty();
options.ExcludePatterns.Should().Contain("System.*");
options.ExcludePatterns.Should().Contain("Microsoft.*");
}
[Fact]
public void Development_HasActiveTracingPosture()
{
var options = RuntimeAgentOptions.Development;
options.Posture.Should().Be(RuntimePosture.ActiveTracing);
options.TrackMethodEnterExit.Should().BeTrue();
}
[Fact]
public void Production_HasSampledPosture()
{
var options = RuntimeAgentOptions.Production;
options.Posture.Should().Be(RuntimePosture.Sampled);
options.TrackMethodEnterExit.Should().BeFalse();
}
[Fact]
public void LowOverhead_HasPassivePosture()
{
var options = RuntimeAgentOptions.LowOverhead;
options.Posture.Should().Be(RuntimePosture.Passive);
options.TrackJitCompilation.Should().BeFalse();
}
[Fact]
public void Default_HasRateLimiting()
{
var options = new RuntimeAgentOptions();
options.MaxEventsPerSecond.Should().Be(10000);
}
[Fact]
public void Default_HasHeartbeatInterval()
{
var options = new RuntimeAgentOptions();
options.HeartbeatInterval.Should().Be(TimeSpan.FromSeconds(30));
}
[Fact]
public void WithPattern_CanCustomizePatterns()
{
var options = new RuntimeAgentOptions
{
IncludePatterns = ["MyApp.*"],
ExcludePatterns = []
};
options.IncludePatterns.Should().ContainSingle().Which.Should().Be("MyApp.*");
options.ExcludePatterns.Should().BeEmpty();
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Signals.RuntimeAgent\StellaOps.Signals.RuntimeAgent.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.TestKit\StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>