tbf/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs

279 lines
9.6 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
{
[TestClass]
[TestSubject(typeof(CliRunner))]
public class CliRunnerTimeoutTest
{
private static string GetCmdSleepTestPath()
{
var serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74);
return serialPort.SerialPortCmdClientPath;
}
private static void EnsureExeExists(string exePath)
{
if (!File.Exists(exePath))
{
Assert.Inconclusive(
$"Test executable '{exePath}' not found. " +
"Please ensure cmdSleepTest.exe exists in the test directory.");
}
}
[TestMethod]
public async Task Timeout_ShouldBeDetected_ForLongRunningTask()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(200);
bool timedOut = cliRunner.TimeOutReceived(100);
Assert.IsTrue(timedOut, "Timeout should have been detected.");
}
[TestMethod]
public async Task CancelUndoneTasksAsTimedOut_ShouldMarkRunningTaskAsTimedOut()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(150);
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(1, cliRunner.TaskPool.Count);
var taskInfo = cliRunner.TaskPool.Single();
Assert.IsNotNull(taskInfo);
Assert.AreEqual(CliTaskState.TimedOut, taskInfo.State);
Assert.IsFalse(taskInfo.UseResult, "Timed out task must not be used.");
}
[TestMethod]
public void CancelUndoneTasksAsTimedOut_ShouldKeepCompletedTaskCompleted()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50");
try
{
cliRunner.WaitAll();
}
catch
{
// let assertions inspect states
}
cliRunner.RefreshTaskStates();
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(1, cliRunner.TaskPool.Count);
var taskInfo = cliRunner.TaskPool.Single();
Assert.IsNotNull(taskInfo);
Assert.AreEqual(CliTaskState.Completed, taskInfo.State);
Assert.IsTrue(taskInfo.UseResult, "Completed task should remain usable.");
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(task);
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
Assert.IsNotNull(task.Result);
Assert.IsFalse(string.IsNullOrEmpty(task.Result.DeviceId));
}
[TestMethod]
public async Task CancelUndoneTasksAsTimedOut_ShouldKeepDoneTask_AndMarkUndoneTask()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50");
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(250);
cliRunner.RefreshTaskStates();
Assert.AreEqual(2, cliRunner.TaskPool.Count);
Assert.AreEqual(1, cliRunner.TaskPool.Count(t => t.State == CliTaskState.Completed),
"Exactly one fast task should be completed before timeout handling.");
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
var doneTask = cliRunner.TaskPool.Single(t => t.State == CliTaskState.Completed);
var timedOutTask = cliRunner.TaskPool.Single(t => t.State == CliTaskState.TimedOut);
Assert.IsTrue(doneTask.UseResult);
Assert.IsFalse(timedOutTask.UseResult);
var completed = doneTask.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(completed);
Assert.AreEqual(TaskStatus.RanToCompletion, completed.Status);
Assert.IsNotNull(completed.Result);
}
[TestMethod]
public async Task AreTasksDone_ShouldReturnTrue_AfterTimeoutCancellation()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(150);
Assert.IsFalse(cliRunner.AreTasksDone(), "Task should still be running before timeout cancellation.");
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.IsTrue(cliRunner.AreTasksDone(),
"After timed out tasks are marked, runner should report no running tasks.");
}
[TestMethod]
public async Task TimedOutTask_ShouldNotHaveUseResult()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(150);
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
var taskInfo = cliRunner.TaskPool.Single();
Assert.AreEqual(CliTaskState.TimedOut, taskInfo.State);
Assert.IsFalse(taskInfo.UseResult);
}
[TestMethod]
public async Task MultipleSlowTasks_ShouldAllBeMarkedTimedOut()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
for (int i = 0; i < 5; i++)
{
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
}
await Task.Delay(200);
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(5, cliRunner.TaskPool.Count);
Assert.AreEqual(5, cliRunner.TaskPool.Count(t => t.State == CliTaskState.TimedOut));
Assert.AreEqual(0, cliRunner.TaskPool.Count(t => t.UseResult));
}
[TestMethod]
public void FastTask_WithInvalidJson_ShouldFinishButNotProduceUsefulResult()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50 invalidjson=true");
try
{
cliRunner.WaitAll();
}
catch
{
}
cliRunner.RefreshTaskStates();
Assert.AreEqual(1, cliRunner.TaskPool.Count);
var taskInfo = cliRunner.TaskPool.Single();
Assert.AreEqual(CliTaskState.Completed, taskInfo.State);
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(task);
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
// depending on your TryJsonStringDeserialize fallback,
// result may be null or an empty/default object
// so avoid asserting UseResult=false here
// instead assert no valid device id
if (task.Result != null)
{
Assert.IsTrue(string.IsNullOrEmpty(task.Result.DeviceId),
"Invalid JSON should not produce a valid DeviceId.");
}
}
[TestMethod]
public void SerialPortData_ZeroMeterType_UsesPoseidonCliDefault()
{
var serialPort = new SerialPortData("COM3", "HatCliDemo.exe", 0);
Assert.AreEqual(SerialPortData.DefaultPoseidonMeterType, serialPort.MeterType);
Assert.AreEqual(
"-p COM3 -m 74 --operation readall",
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams));
}
[TestMethod]
public async Task CliProcess_ExitErrorWithoutJson_RecordsDiagnosticsAndReturnsNoReading()
{
string cmdExe = Environment.GetEnvironmentVariable("ComSpec") ?? @"C:\Windows\System32\cmd.exe";
var cliRunner = new CliRunner(false);
var info = new CliTaskInfo { Name = "missing-dependency-simulation" };
JsonDataFromPoseidon result = await cliRunner.RunAndCaptureJsonAsync<JsonDataFromPoseidon>(
cmdExe,
"/d /c \"echo Could not load file or assembly 'log4net' 1>&2 & exit /b 17\"",
info);
Assert.IsNull(result);
Assert.AreEqual(17, info.ExitCode);
StringAssert.Contains(info.FailureReason, "exit code 17");
StringAssert.Contains(info.StandardError, "log4net");
}
}
}