Initial commit
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using RobotNet10.NavigationTune.Execution;
|
||||
using RobotNet10.NavigationTune.Shared.Interfaces;
|
||||
using RobotNet10.NavigationTune.Shared.Models;
|
||||
using RobotNet10.NavigationTune.Test.Helpers;
|
||||
using RobotNet10.NavigationTune.Scenarios;
|
||||
|
||||
namespace RobotNet10.NavigationTune.Test.Execution;
|
||||
|
||||
public class TestExecutorTests
|
||||
{
|
||||
private Mock<ILocalizationProvider> CreateMockLocalization(double x = 0.0, double y = 0.0, double theta = 0.0)
|
||||
{
|
||||
var mock = new Mock<ILocalizationProvider>();
|
||||
mock.Setup(l => l.X).Returns(x);
|
||||
mock.Setup(l => l.Y).Returns(y);
|
||||
mock.Setup(l => l.Theta).Returns(theta);
|
||||
return mock;
|
||||
}
|
||||
|
||||
private Mock<IVelocityProvider> CreateMockVelocityProvider(
|
||||
double linearVel = 0.0,
|
||||
double angularVel = 0.0,
|
||||
double confidence = 1.0)
|
||||
{
|
||||
var mock = new Mock<IVelocityProvider>();
|
||||
mock.Setup(v => v.GetActualVelocity()).Returns((linearVel, angularVel));
|
||||
mock.Setup(v => v.GetModelConfidence()).Returns(confidence);
|
||||
return mock;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithProviders_ShouldInitialize()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
|
||||
// Act
|
||||
var executor = new TestExecutor(
|
||||
localization.Object,
|
||||
velocityProvider.Object);
|
||||
|
||||
// Assert
|
||||
executor.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithStraightLineScenario_ShouldComplete()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(5.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
var telemetryUpdates = new List<TelemetryData>();
|
||||
|
||||
// Act
|
||||
var result = await executor.ExecuteAsync(
|
||||
scenario,
|
||||
parameters,
|
||||
telemetry => telemetryUpdates.Add(telemetry),
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Status.Should().BeOneOf(TestStatus.Completed, TestStatus.Aborted);
|
||||
telemetryUpdates.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WhenCancelled_ShouldAbort()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(10.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
|
||||
|
||||
// Act
|
||||
var result = await executor.ExecuteAsync(scenario, parameters, null, cts.Token);
|
||||
|
||||
// Assert
|
||||
result.Status.Should().BeOneOf(TestStatus.Aborted, TestStatus.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProgress_BeforeExecution_ShouldReturnZero()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
// Act
|
||||
var progress = executor.GetProgress();
|
||||
|
||||
// Assert
|
||||
progress.ProgressPercent.Should().Be(0.0);
|
||||
progress.DistanceTraveled.Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Pause_WhenRunning_ShouldChangeStatus()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(1.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act - Start test in background
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await executor.ExecuteAsync(scenario, parameters, null,
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token);
|
||||
});
|
||||
|
||||
// Wait a bit then pause
|
||||
await Task.Delay(100);
|
||||
executor.Pause();
|
||||
|
||||
// Assert
|
||||
var progress = executor.GetProgress();
|
||||
progress.Should().NotBeNull();
|
||||
|
||||
// Cleanup
|
||||
executor.Stop();
|
||||
await task;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Resume_AfterPause_ShouldContinue()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(1.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await executor.ExecuteAsync(scenario, parameters, null,
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token);
|
||||
});
|
||||
|
||||
await Task.Delay(100);
|
||||
executor.Pause();
|
||||
await Task.Delay(50);
|
||||
executor.Resume();
|
||||
|
||||
// Assert
|
||||
var progress = executor.GetProgress();
|
||||
progress.Should().NotBeNull();
|
||||
|
||||
// Cleanup
|
||||
executor.Stop();
|
||||
await task;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Stop_WhenRunning_ShouldAbort()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(10.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await executor.ExecuteAsync(scenario, parameters, null,
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
|
||||
});
|
||||
|
||||
await Task.Delay(200);
|
||||
executor.Stop();
|
||||
|
||||
// Assert
|
||||
await task;
|
||||
var progress = executor.GetProgress();
|
||||
progress.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EmergencyStop_WhenRunning_ShouldImmediatelyStop()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(10.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await executor.ExecuteAsync(scenario, parameters, null,
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
|
||||
});
|
||||
|
||||
await Task.Delay(200);
|
||||
executor.EmergencyStop();
|
||||
|
||||
// Assert
|
||||
await task;
|
||||
var progress = executor.GetProgress();
|
||||
progress.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithCircleScenario_ShouldComplete()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateCircleScenario(2.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
var result = await executor.ExecuteAsync(
|
||||
scenario,
|
||||
parameters,
|
||||
null,
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Status.Should().BeOneOf(TestStatus.Completed, TestStatus.Aborted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldCollectTelemetry()
|
||||
{
|
||||
// Arrange
|
||||
var localization = CreateMockLocalization();
|
||||
var velocityProvider = CreateMockVelocityProvider();
|
||||
var executor = new TestExecutor(localization.Object, velocityProvider.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario(2.0);
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
var telemetryList = new List<TelemetryData>();
|
||||
|
||||
// Act
|
||||
var result = await executor.ExecuteAsync(
|
||||
scenario,
|
||||
parameters,
|
||||
telemetry => telemetryList.Add(telemetry),
|
||||
new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token);
|
||||
|
||||
// Assert
|
||||
telemetryList.Should().NotBeEmpty();
|
||||
result.TelemetryData.Should().NotBeEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using RobotNet10.NavigationTune.Execution;
|
||||
using RobotNet10.NavigationTune.Shared.Interfaces;
|
||||
using RobotNet10.NavigationTune.Shared.Models;
|
||||
using RobotNet10.NavigationTune.Test.Helpers;
|
||||
using RobotNet10.NavigationTune.Services;
|
||||
|
||||
namespace RobotNet10.NavigationTune.Test.Execution;
|
||||
|
||||
public class TuningNavigationTests
|
||||
{
|
||||
private Mock<ITestExecutor> CreateMockTestExecutor()
|
||||
{
|
||||
var mock = new Mock<ITestExecutor>();
|
||||
return mock;
|
||||
}
|
||||
|
||||
private Mock<IMetricsCalculator> CreateMockMetricsCalculator()
|
||||
{
|
||||
var mock = new Mock<IMetricsCalculator>();
|
||||
mock.Setup(m => m.CalculateMetrics(
|
||||
It.IsAny<List<TelemetryData>>(),
|
||||
It.IsAny<ReferencePath>()))
|
||||
.Returns(new TestMetrics
|
||||
{
|
||||
OverallScore = 0.85f,
|
||||
CrossTrackErrorRMS = 0.05f,
|
||||
HeadingErrorRMS = 0.02f
|
||||
});
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithDependencies_ShouldInitialize()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
// Act
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
// Assert
|
||||
tuningNav.Should().NotBeNull();
|
||||
tuningNav.IsTestRunning.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteTestAsync_WithValidInputs_ShouldReturnResult()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
testExecutor.Setup(e => e.ExecuteAsync(
|
||||
It.IsAny<TestScenario>(),
|
||||
It.IsAny<NavigationParameterSet>(),
|
||||
It.IsAny<Action<TelemetryData>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new TestExecutionResult
|
||||
{
|
||||
TestRunId = Guid.NewGuid(),
|
||||
Status = TestStatus.Completed,
|
||||
TelemetryData = new List<TelemetryData>(),
|
||||
StartTime = DateTime.UtcNow
|
||||
});
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario();
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
var result = await tuningNav.ExecuteTestAsync(scenario, parameters);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Status.Should().Be(TestStatus.Completed);
|
||||
testExecutor.Verify(e => e.ExecuteAsync(
|
||||
It.IsAny<TestScenario>(),
|
||||
It.IsAny<NavigationParameterSet>(),
|
||||
It.IsAny<Action<TelemetryData>>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteTestAsync_WhenAlreadyRunning_ShouldThrow()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
// Simulate long-running test
|
||||
testExecutor.Setup(e => e.ExecuteAsync(
|
||||
It.IsAny<TestScenario>(),
|
||||
It.IsAny<NavigationParameterSet>(),
|
||||
It.IsAny<Action<TelemetryData>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
return new TestExecutionResult { Status = TestStatus.Completed };
|
||||
});
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario();
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
var task1 = tuningNav.ExecuteTestAsync(scenario, parameters);
|
||||
await Task.Delay(50); // Let first test start
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await tuningNav.ExecuteTestAsync(scenario, parameters));
|
||||
|
||||
// Cleanup
|
||||
tuningNav.Stop();
|
||||
await task1;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProgress_ShouldDelegateToTestExecutor()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
testExecutor.Setup(e => e.GetProgress())
|
||||
.Returns(new TestProgress
|
||||
{
|
||||
ProgressPercent = 0.5,
|
||||
DistanceTraveled = 5.0
|
||||
});
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
// Act
|
||||
var progress = tuningNav.GetProgress();
|
||||
|
||||
// Assert
|
||||
progress.Should().NotBeNull();
|
||||
progress.ProgressPercent.Should().Be(0.5);
|
||||
testExecutor.Verify(e => e.GetProgress(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pause_ShouldDelegateToTestExecutor()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
// Act
|
||||
tuningNav.Pause();
|
||||
|
||||
// Assert
|
||||
testExecutor.Verify(e => e.Pause(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resume_ShouldDelegateToTestExecutor()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
// Act
|
||||
tuningNav.Resume();
|
||||
|
||||
// Assert
|
||||
testExecutor.Verify(e => e.Resume(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stop_ShouldDelegateToTestExecutor()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
// Act
|
||||
tuningNav.Stop();
|
||||
|
||||
// Assert
|
||||
testExecutor.Verify(e => e.Stop(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmergencyStop_ShouldDelegateToTestExecutor()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
// Act
|
||||
tuningNav.EmergencyStop();
|
||||
|
||||
// Assert
|
||||
testExecutor.Verify(e => e.EmergencyStop(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteTestAsync_ShouldInvokeTelemetryUpdateEvent()
|
||||
{
|
||||
// Arrange
|
||||
var testExecutor = CreateMockTestExecutor();
|
||||
var metricsCalculator = CreateMockMetricsCalculator();
|
||||
|
||||
var telemetryReceived = false;
|
||||
var tuningNav = new TuningNavigation(
|
||||
testExecutor.Object,
|
||||
metricsCalculator.Object);
|
||||
|
||||
tuningNav.OnTelemetryUpdate += (telemetry) => telemetryReceived = true;
|
||||
|
||||
testExecutor.Setup(e => e.ExecuteAsync(
|
||||
It.IsAny<TestScenario>(),
|
||||
It.IsAny<NavigationParameterSet>(),
|
||||
It.IsAny<Action<TelemetryData>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<TestScenario, NavigationParameterSet, Action<TelemetryData>?, CancellationToken>(
|
||||
(s, p, callback, ct) =>
|
||||
{
|
||||
callback?.Invoke(TestHelpers.CreateTelemetryData());
|
||||
})
|
||||
.ReturnsAsync(new TestExecutionResult
|
||||
{
|
||||
Status = TestStatus.Completed,
|
||||
TelemetryData = new List<TelemetryData>()
|
||||
});
|
||||
|
||||
var scenario = TestHelpers.CreateStraightLineScenario();
|
||||
var parameters = TestHelpers.CreateDefaultParameterSet();
|
||||
|
||||
// Act
|
||||
await tuningNav.ExecuteTestAsync(scenario, parameters);
|
||||
|
||||
// Assert
|
||||
telemetryReceived.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user