Add task state tracking and timeout handling to CliRunner, update project files and tests
- Introduced `CliTaskState` and `CliTaskInfo` classes for enhanced tracking of task states. - Added timeout handling logic to `CliRunner` with methods like `CancelUndoneTasksAsTimedOut` and state refresh. - Updated `TBF.csproj` to include new classes `CliTaskState` and `CliTaskInfo`. - Refactored test methods in `CliRunnerTest` and introduced new timeout-related tests in `CliRunnerTimeoutTest` and `CliRunnerTimeoutUnitTest`. - Standardized code formatting and added minor improvements to logging and task lifecycle management.
This commit is contained in:
parent
06a401a1ff
commit
c4b61d885b
@ -13,46 +13,29 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class CliRunner
|
||||
{
|
||||
//static readonly ILog log = LogManager.GetLogger(typeof(CliRunner));
|
||||
private readonly ILog log;
|
||||
private List<Task> taskPool = new List<Task>();
|
||||
private long startTime;
|
||||
private long incommingTime;
|
||||
|
||||
public List<Task> TaskPool { get { return taskPool; } }
|
||||
public void AddTask(Task task) { taskPool.Add(task); }
|
||||
public void WaitAll() { Task.WaitAll(taskPool.ToArray()); }
|
||||
public void Clear() { taskPool.Clear(); }
|
||||
public void CancelAll() { Task.WhenAll(taskPool).ContinueWith(t => { }); }
|
||||
private readonly List<CliTaskInfo> taskPool = new List<CliTaskInfo>();
|
||||
private long startTimeMs;
|
||||
private long incommingTimeMs;
|
||||
|
||||
public void StartAll()
|
||||
public List<CliTaskInfo> TaskPool
|
||||
{
|
||||
taskPool.ForEach(t => t.Start());
|
||||
}
|
||||
|
||||
public bool AreTasksDone()
|
||||
{
|
||||
return taskPool.All(task => task.IsCompleted);
|
||||
get { return taskPool; }
|
||||
}
|
||||
|
||||
public long StartTime
|
||||
{
|
||||
get => startTime;
|
||||
get { return startTimeMs; }
|
||||
}
|
||||
|
||||
public long IncommingTime
|
||||
{
|
||||
get => incommingTime;
|
||||
get { return incommingTimeMs; }
|
||||
}
|
||||
|
||||
public bool TimeOutReceived(long timeout)
|
||||
{
|
||||
return (DateTime.Now.Ticks - startTime) > timeout;
|
||||
}
|
||||
|
||||
public CliRunner(bool isCliLogging)
|
||||
{
|
||||
startTime = DateTime.Now.Ticks;
|
||||
ResetStartTime();
|
||||
|
||||
if (isCliLogging)
|
||||
{
|
||||
@ -60,88 +43,180 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
log = new ScopedLoggerFactory().CreateLogger<CliRunner>(
|
||||
@"C:\TBF\Logs\CliRunner.txt",
|
||||
10, // maxFileSizeMB
|
||||
7, // maxBackups
|
||||
10,
|
||||
7,
|
||||
log4net.Core.Level.Debug,
|
||||
true, // zipRolledFiles
|
||||
true, // singleZipPerDay
|
||||
TimeSpan.FromMinutes(2) // zipScanInterval
|
||||
true,
|
||||
true,
|
||||
TimeSpan.FromMinutes(2)
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Fallback to console or handle gracefully
|
||||
Console.WriteLine($"Failed to initialize logger: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg)
|
||||
public void ResetStartTime()
|
||||
{
|
||||
var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
|
||||
taskPool.Add(task);
|
||||
startTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
public void AddSendAsync(string fileName, string args)
|
||||
{
|
||||
var task = SendAsync(fileName, args);
|
||||
taskPool.Add(task);
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> SendAsync(string fileName, string args, CancellationToken ct = default)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||
process.Start();
|
||||
|
||||
Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
|
||||
Task<string> stdErrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
try
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var item in taskPool)
|
||||
{
|
||||
await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (!process.HasExited)
|
||||
try
|
||||
{
|
||||
item?.Cts?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
throw;
|
||||
try
|
||||
{
|
||||
if (item?.Process != null)
|
||||
{
|
||||
item.Process.Dispose();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
//comming answer from serial port - good place for time stamp
|
||||
incommingTime = DateTime.Now.Ticks;
|
||||
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
return allOutput;
|
||||
taskPool.Clear();
|
||||
}
|
||||
|
||||
|
||||
public void AddRunAndCaptureJsonAsync<T>(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new()
|
||||
public void WaitAll()
|
||||
{
|
||||
var task = RunAndCaptureJsonAsync<T>(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
|
||||
taskPool.Add(task);
|
||||
var tasks = taskPool
|
||||
.Where(t => t != null && t.Task != null)
|
||||
.Select(t => t.Task)
|
||||
.ToArray();
|
||||
|
||||
if (tasks.Length == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
RefreshTaskStates();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CancellationToken ct = default) where T : new()
|
||||
|
||||
public bool AreTasksDone()
|
||||
{
|
||||
RefreshTaskStates();
|
||||
return taskPool.Count > 0 && taskPool.All(t => t.State != CliTaskState.Running);
|
||||
}
|
||||
|
||||
public bool TimeOutReceived(long timeoutMs)
|
||||
{
|
||||
long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
return (nowMs - startTimeMs) > timeoutMs;
|
||||
}
|
||||
|
||||
public void RefreshTaskStates()
|
||||
{
|
||||
foreach (var item in taskPool)
|
||||
{
|
||||
if (item == null || item.Task == null)
|
||||
continue;
|
||||
|
||||
if (item.State == CliTaskState.TimedOut)
|
||||
continue;
|
||||
|
||||
if (!item.Task.IsCompleted)
|
||||
{
|
||||
item.State = CliTaskState.Running;
|
||||
}
|
||||
else if (item.Task.IsCanceled)
|
||||
{
|
||||
item.State = CliTaskState.Canceled;
|
||||
}
|
||||
else if (item.Task.IsFaulted)
|
||||
{
|
||||
item.State = CliTaskState.Faulted;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.State = CliTaskState.Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelUndoneTasksAsTimedOut()
|
||||
{
|
||||
foreach (var item in taskPool)
|
||||
{
|
||||
if (item == null || item.Task == null)
|
||||
continue;
|
||||
|
||||
if (item.Task.IsCompleted)
|
||||
{
|
||||
if (item.Task.IsCanceled)
|
||||
item.State = CliTaskState.Canceled;
|
||||
else if (item.Task.IsFaulted)
|
||||
item.State = CliTaskState.Faulted;
|
||||
else
|
||||
item.State = CliTaskState.Completed;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
item.State = CliTaskState.TimedOut;
|
||||
|
||||
try
|
||||
{
|
||||
item.Cts?.Cancel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Warn("Cancel token failed", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (item.Process != null && !item.Process.HasExited)
|
||||
{
|
||||
item.Process.Kill();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Warn("Kill process failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg)
|
||||
{
|
||||
AddSendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
|
||||
}
|
||||
|
||||
public void AddSendAsync(string fileName, string args)
|
||||
{
|
||||
ResetStartTime();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var info = new CliTaskInfo
|
||||
{
|
||||
Cts = cts,
|
||||
Name = "SendAsync"
|
||||
};
|
||||
|
||||
var task = SendAsync(fileName, args, info, cts.Token);
|
||||
info.Task = task;
|
||||
taskPool.Add(info);
|
||||
}
|
||||
|
||||
public async Task<string> SendAsync(string fileName, string args, CliTaskInfo info, CancellationToken ct = default)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
@ -153,80 +228,210 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||
process.Start();
|
||||
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
|
||||
{
|
||||
info.Process = process;
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
||||
var stderrTask = process.StandardError.ReadToEndAsync();
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct));
|
||||
Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
|
||||
Task<string> stdErrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (info.State != CliTaskState.TimedOut)
|
||||
info.State = CliTaskState.Canceled;
|
||||
|
||||
string json = ExtractJson(allOutput);
|
||||
if (TryJsonStringDeserialize(json, out T result)) return result;
|
||||
return default;
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
|
||||
info.State = CliTaskState.Completed;
|
||||
return allOutput;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.State = CliTaskState.Faulted;
|
||||
log?.Error("SendAsync failed", ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
info.Process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryJsonStringDeserialize<T>(string json, out T runAndCaptureJsonAsync) where T : new()
|
||||
public void AddRunAndCaptureJsonAsync<T>(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new()
|
||||
{
|
||||
AddRunAndCaptureJsonAsync<T>(
|
||||
data.SerialPortCmdClientPath,
|
||||
data.DefaultArgSettings(eMeterArg));
|
||||
}
|
||||
|
||||
public void AddRunAndCaptureJsonAsync<T>(string fileName, string args) where T : new()
|
||||
{
|
||||
ResetStartTime();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var info = new CliTaskInfo
|
||||
{
|
||||
Cts = cts,
|
||||
Name = $"RunAndCaptureJsonAsync<{typeof(T).Name}>"
|
||||
};
|
||||
|
||||
var task = RunAndCaptureJsonAsync<T>(fileName, args, info, cts.Token);
|
||||
|
||||
info.Task = task;
|
||||
taskPool.Add(info);
|
||||
}
|
||||
|
||||
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CliTaskInfo info, CancellationToken ct = default) where T : new()
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
|
||||
{
|
||||
info.Process = process;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
||||
var stderrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (info.State != CliTaskState.TimedOut)
|
||||
info.State = CliTaskState.Canceled;
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
|
||||
string json = ExtractJson(allOutput);
|
||||
T result;
|
||||
if (TryJsonStringDeserialize(json, out result))
|
||||
{
|
||||
info.State = CliTaskState.Completed;
|
||||
return result;
|
||||
}
|
||||
|
||||
info.State = CliTaskState.Completed;
|
||||
return default(T);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.State = CliTaskState.Faulted;
|
||||
log?.Error("RunAndCaptureJsonAsync failed", ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
info.Process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
|
||||
{
|
||||
if (json != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
runAndCaptureJsonAsync = JsonConvert.DeserializeObject<T>(json);
|
||||
value = JsonConvert.DeserializeObject<T>(json);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Debug(ex.Message);
|
||||
runAndCaptureJsonAsync = TryConvert<T>(json);
|
||||
log?.Debug(ex.Message);
|
||||
value = TryConvert<T>(json);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
runAndCaptureJsonAsync = default;
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static T TryConvert<T>(string json) where T : new()
|
||||
{
|
||||
|
||||
T obj = new T();
|
||||
T obj = new T();
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
|
||||
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
if (!prop.CanWrite)
|
||||
continue;
|
||||
|
||||
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
JToken token;
|
||||
if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token))
|
||||
{
|
||||
if (!prop.CanWrite) continue;
|
||||
|
||||
JToken token;
|
||||
if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token))
|
||||
try
|
||||
{
|
||||
object value = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
object value = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// leave default if conversion fails
|
||||
}
|
||||
}
|
||||
// else → keep default value
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public string ExtractJson(string text)
|
||||
@ -241,6 +446,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void AddTaskForTest(Task task, string name = "TestTask", CancellationTokenSource cts = null)
|
||||
{
|
||||
taskPool.Add(new CliTaskInfo
|
||||
{
|
||||
Task = task,
|
||||
Cts = cts,
|
||||
Name = name,
|
||||
State = task.IsCompleted
|
||||
? (task.IsCanceled ? CliTaskState.Canceled :
|
||||
task.IsFaulted ? CliTaskState.Faulted :
|
||||
CliTaskState.Completed)
|
||||
: CliTaskState.Running
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs
Normal file
25
TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class CliTaskInfo
|
||||
{
|
||||
public Task Task { get; set; }
|
||||
public CancellationTokenSource Cts { get; set; }
|
||||
public Process Process { get; set; }
|
||||
public CliTaskState State { get; set; } = CliTaskState.Running;
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool UseResult
|
||||
{
|
||||
get
|
||||
{
|
||||
return State == CliTaskState.Completed
|
||||
&& Task != null
|
||||
&& Task.Status == TaskStatus.RanToCompletion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskState.cs
Normal file
13
TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskState.cs
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public enum CliTaskState
|
||||
{
|
||||
Running,
|
||||
Completed,
|
||||
TimedOut,
|
||||
Canceled,
|
||||
Faulted
|
||||
}
|
||||
|
||||
}
|
||||
@ -330,10 +330,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
/// for debug purposes what time will consume answer
|
||||
/// </summary>
|
||||
private long startTimeInMilis = -1, fullTimeInMilis = -1;
|
||||
/// 30 seconds
|
||||
private static long SafetyTimeOut = 30 * 1000;
|
||||
private long incommingTime = -1;
|
||||
private bool _lastOpTimedOut;
|
||||
|
||||
|
||||
/// 30 seconds
|
||||
|
||||
public long DeltaTime { get{return fullTimeInMilis;}}
|
||||
public long IncommingTime { get{return incommingTime;}}
|
||||
@ -350,108 +352,146 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
if (_currentOp == CurrentPoseidonOp.SendStartDataStream)
|
||||
{
|
||||
CliRunner.Clear();
|
||||
_lastOpTimedOut = false;
|
||||
CliRunner.AddSendAsync(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
return Event.Busy;
|
||||
_currentOp = CurrentPoseidonOp.SendStartDataStream_Runing;
|
||||
return Event.Busy;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Runing)
|
||||
{
|
||||
if (CliRunner.AreTasksDone()
|
||||
|| CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
if (CliRunner.AreTasksDone())
|
||||
{
|
||||
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
|
||||
}
|
||||
return Event.Busy;
|
||||
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
{
|
||||
_lastOpTimedOut = true;
|
||||
CliRunner.CancelUndoneTasksAsTimedOut();
|
||||
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
|
||||
}
|
||||
|
||||
return Event.Busy;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Done)
|
||||
{
|
||||
var firstTask = CliRunner.TaskPool.FindLast(t => t is Task<string>);
|
||||
var firstTaskInfo = CliRunner.TaskPool
|
||||
.FindLast(t => t.Task is Task<string> && t.UseResult);
|
||||
|
||||
|
||||
if (firstTask != null && firstTask is Task<string>)
|
||||
if (firstTaskInfo != null)
|
||||
{
|
||||
string data = null;
|
||||
data = (firstTask as Task<string>).Result;
|
||||
var task = (Task<string>)firstTaskInfo.Task;
|
||||
string data = task.Result;
|
||||
|
||||
if (data != null && TryGetDeviceId(data, out wmSerialNr))
|
||||
if (!string.IsNullOrEmpty(data))
|
||||
{
|
||||
|
||||
TryGetDeviceId(data, out wmSerialNr);
|
||||
}
|
||||
}
|
||||
else if (_lastOpTimedOut)
|
||||
{
|
||||
log.Warn(
|
||||
$"PoseidonReader {Name}: SendStartDataStream timed out, no completed result will be used.");
|
||||
}
|
||||
|
||||
_currentOp = CurrentPoseidonOp.Done;
|
||||
CliRunner.Clear();
|
||||
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
|
||||
return Event.Done;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
||||
{
|
||||
startTimeInMilis = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
|
||||
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
incommingTime = -1;
|
||||
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
||||
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
CliRunner.Clear();
|
||||
_lastOpTimedOut = false;
|
||||
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
||||
return Event.Busy;
|
||||
}else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
|
||||
{
|
||||
if (CliRunner.AreTasksDone()
|
||||
|| CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
if (CliRunner.AreTasksDone())
|
||||
{
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||
//set time stamp - end of reading
|
||||
incommingTime = CliRunner.IncommingTime;
|
||||
}
|
||||
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
{
|
||||
_lastOpTimedOut = true;
|
||||
CliRunner.CancelUndoneTasksAsTimedOut();
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||
incommingTime = CliRunner.IncommingTime;
|
||||
}
|
||||
|
||||
return Event.Busy;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Done)
|
||||
{
|
||||
fullTimeInMilis = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - startTimeInMilis;
|
||||
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis} ");
|
||||
JsonDataFromPoseidon data = null;
|
||||
var first = CliRunner.TaskPool.FindLast(t => t is Task<JsonDataFromPoseidon>);
|
||||
if (first != null && first is Task<JsonDataFromPoseidon>)
|
||||
{
|
||||
data = (first as Task<JsonDataFromPoseidon>).Result;
|
||||
fullTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTimeInMilis;
|
||||
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis}");
|
||||
|
||||
//GET serial number
|
||||
JsonDataFromPoseidon data = null;
|
||||
|
||||
var firstTaskInfo = CliRunner.TaskPool
|
||||
.FindLast(t => t.Task is Task<JsonDataFromPoseidon> && t.UseResult);
|
||||
|
||||
if (firstTaskInfo != null)
|
||||
{
|
||||
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
|
||||
data = task.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_lastOpTimedOut)
|
||||
{
|
||||
log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warn("No completed JsonDataFromPoseidon task available.");
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(wmSerialNr))
|
||||
{
|
||||
try
|
||||
{
|
||||
wmSerialNr = data?.DeviceId ?? wmSerialNr;
|
||||
wmSerialNr = data.DeviceId ?? wmSerialNr;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error("Serial Nr - parse error!");
|
||||
log.Error("Serial Nr - parse error!", e);
|
||||
}
|
||||
}
|
||||
|
||||
//GET volume
|
||||
if (data != null && Double.TryParse(data.Reading, out double Volume))
|
||||
double volume;
|
||||
if (Double.TryParse(data.Reading, out volume))
|
||||
{
|
||||
double VolumeLi = Units.ConvertFrom(Unit.USgal, Volume);
|
||||
//double VolumeM3 = Units.ConvertTo(Unit.m3, VolumeLi);
|
||||
if (_isReadingStart)
|
||||
{
|
||||
//volume
|
||||
beginWMState = VolumeLi;
|
||||
}
|
||||
else
|
||||
{
|
||||
//volume
|
||||
endWMState = VolumeLi;
|
||||
}
|
||||
}
|
||||
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
|
||||
|
||||
if (_isReadingStart)
|
||||
beginWMState = volumeLi;
|
||||
else
|
||||
endWMState = volumeLi;
|
||||
}
|
||||
}
|
||||
|
||||
//Finish reading and loop
|
||||
_currentOp = CurrentPoseidonOp.Done;
|
||||
|
||||
CliRunner.Clear();
|
||||
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
|
||||
return Event.Done;
|
||||
}
|
||||
|
||||
return Event.None;
|
||||
|
||||
return Event.None;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Stop this operation</summary>
|
||||
public void Stop()
|
||||
{
|
||||
|
||||
@ -1321,6 +1321,8 @@
|
||||
<DependentUpon>RRCfgCtrl.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunner.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliTaskInfo.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliTaskState.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\Factory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\JsonDataFromPoseidon.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonCfg.cs" />
|
||||
|
||||
@ -12,35 +12,36 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
[TestSubject(typeof(CliRunner))]
|
||||
public class CliRunnerTest
|
||||
{
|
||||
|
||||
[TestMethod]
|
||||
public void ExtractJson_Test()
|
||||
{
|
||||
String allOutput = " {\n \"DeviceId\": \"1000000267\"\n}\n";
|
||||
string allOutput = " {\n \"DeviceId\": \"1000000267\"\n}\n";
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(json));
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(json));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void ExtractJson_Test2()
|
||||
{
|
||||
String allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
string allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(json));
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(json));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryJsonStringDeserialize_Test()
|
||||
{
|
||||
String allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
string allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
var ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
bool ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
|
||||
Assert.IsTrue(ok, "Deserialization failed");
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("1000000267", dto.DeviceId);
|
||||
@ -53,120 +54,165 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
[TestMethod]
|
||||
public void RunMultipleTimesTestProgram_CheckParalelWork()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData("COM3","cmdSleepTest.exe",74);
|
||||
|
||||
// Check if the executable exists in current directory
|
||||
SerialPortData serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74);
|
||||
|
||||
if (!System.IO.File.Exists(serialPort.SerialPortCmdClientPath))
|
||||
{
|
||||
Assert.Inconclusive($"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
Assert.Inconclusive(
|
||||
$"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
|
||||
|
||||
CliRunner cliRunnerOne = new CliRunner(false);
|
||||
DateTime StartTime = DateTime.Now;
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
cliRunnerOne.WaitAll();
|
||||
TimeSpan OneEndTime = DateTime.Now - StartTime;
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
|
||||
|
||||
TimeSpan oneEndTime = DateTime.Now - startTime;
|
||||
|
||||
startTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {startTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
}
|
||||
|
||||
cliRunner.WaitAll();
|
||||
|
||||
DateTime EndTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {EndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
TimeSpan delta = EndTime - StartTime;
|
||||
Console.WriteLine($"Delta Time One process {OneEndTime.Hours:D2}:{OneEndTime.Minutes:D2}:{OneEndTime.Seconds:D2}.{OneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
DateTime endTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {endTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
TimeSpan delta = endTime - startTime;
|
||||
Console.WriteLine(
|
||||
$"Delta Time One process {oneEndTime.Hours:D2}:{oneEndTime.Minutes:D2}:{oneEndTime.Seconds:D2}.{oneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
Console.WriteLine($"Total tasks: {cliRunner.TaskPool.Count}");
|
||||
|
||||
|
||||
foreach (Task<JsonDataFromPoseidon> task in cliRunner.TaskPool)
|
||||
|
||||
foreach (CliTaskInfo taskInfo in cliRunner.TaskPool)
|
||||
{
|
||||
if (task != null)
|
||||
Assert.IsNotNull(taskInfo);
|
||||
Assert.IsNotNull(taskInfo.Task);
|
||||
|
||||
if (taskInfo.UseResult)
|
||||
{
|
||||
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
|
||||
Assert.IsNotNull(task, "Expected Task<JsonDataFromPoseidon>.");
|
||||
|
||||
JsonDataFromPoseidon jsonDataFromPoseidon = task.Result;
|
||||
Console.WriteLine($"Result: {jsonDataFromPoseidon.DeviceId}");
|
||||
Console.WriteLine($"Result: {jsonDataFromPoseidon?.DeviceId}");
|
||||
Assert.IsNotNull(jsonDataFromPoseidon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail($"Task did not complete successfully. State={taskInfo.State}, Status={taskInfo.Task.Status}");
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the total time is greater than the sum of the individual times
|
||||
Assert.IsTrue((cliRunner.TaskPool.Count*OneEndTime.Ticks) > delta.Ticks);
|
||||
|
||||
|
||||
Assert.IsTrue((cliRunner.TaskPool.Count * oneEndTime.Ticks) > delta.Ticks);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task RunMultipleTimesTestProgram_CheckParalelWorkCumulative()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData("COM3","cmdSleepTest.exe",74);
|
||||
|
||||
// Check if the executable exists in current directory
|
||||
SerialPortData serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74);
|
||||
|
||||
if (!System.IO.File.Exists(serialPort.SerialPortCmdClientPath))
|
||||
{
|
||||
Assert.Inconclusive($"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
Assert.Inconclusive(
|
||||
$"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<CliRunner> cliRunnerList = new List<CliRunner>();
|
||||
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
cliRunnerList.Add(new CliRunner(false));
|
||||
|
||||
}
|
||||
|
||||
CliRunner cliRunnerOne = new CliRunner(false);
|
||||
DateTime StartTime = DateTime.Now;
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
cliRunnerOne.WaitAll();
|
||||
TimeSpan OneEndTime = DateTime.Now - StartTime;
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
|
||||
TimeSpan oneEndTime = DateTime.Now - startTime;
|
||||
|
||||
startTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {startTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
for (int iCliRunner = 0; iCliRunner < cliRunnerList.Count; iCliRunner++)
|
||||
{
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for ALL tasks from ALL runners
|
||||
var allTasks = cliRunnerList.SelectMany(r => r.TaskPool).ToArray();
|
||||
Task[] allTasks = cliRunnerList
|
||||
.SelectMany(r => r.TaskPool)
|
||||
.Where(ti => ti != null && ti.Task != null)
|
||||
.Select(ti => ti.Task)
|
||||
.ToArray();
|
||||
|
||||
await Task.WhenAll(allTasks);
|
||||
|
||||
DateTime EndTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {EndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
TimeSpan delta = EndTime - StartTime;
|
||||
Console.WriteLine($"Delta Time One process {OneEndTime.Hours:D2}:{OneEndTime.Minutes:D2}:{OneEndTime.Seconds:D2}.{OneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
DateTime endTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {endTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
TimeSpan delta = endTime - startTime;
|
||||
Console.WriteLine(
|
||||
$"Delta Time One process {oneEndTime.Hours:D2}:{oneEndTime.Minutes:D2}:{oneEndTime.Seconds:D2}.{oneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
Console.WriteLine($"Total tasks: {cliRunnerList.Sum(runner => runner.TaskPool.Count)}");
|
||||
|
||||
|
||||
for (int iCliRunner = 0; iCliRunner < cliRunnerList.Count; iCliRunner++)
|
||||
{
|
||||
string results = $"iCliRunner: {iCliRunner}";
|
||||
foreach (Task<JsonDataFromPoseidon> task in cliRunnerList[iCliRunner].TaskPool)
|
||||
|
||||
foreach (CliTaskInfo taskInfo in cliRunnerList[iCliRunner].TaskPool)
|
||||
{
|
||||
if (task != null)
|
||||
Assert.IsNotNull(taskInfo);
|
||||
Assert.IsNotNull(taskInfo.Task);
|
||||
|
||||
if (taskInfo.UseResult)
|
||||
{
|
||||
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
|
||||
Assert.IsNotNull(task, "Expected Task<JsonDataFromPoseidon>.");
|
||||
|
||||
JsonDataFromPoseidon jsonDataFromPoseidon = task.Result;
|
||||
results += ($" ID: {jsonDataFromPoseidon.DeviceId}");
|
||||
results += $" ID: {jsonDataFromPoseidon?.DeviceId}";
|
||||
Assert.IsNotNull(jsonDataFromPoseidon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail(
|
||||
$"Task did not complete successfully. Runner={iCliRunner}, State={taskInfo.State}, Status={taskInfo.Task.Status}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(results);
|
||||
}
|
||||
|
||||
// Check that the total time is greater than the sum of the individual times
|
||||
Assert.IsTrue((cliRunnerList.Count*OneEndTime.Ticks) > delta.Ticks);
|
||||
Assert.IsTrue((cliRunnerList.Count * oneEndTime.Ticks) > delta.Ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,249 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
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 CliRunnerTimeoutUnitTest
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task CancelUndoneTasksAsTimedOut_ShouldMarkOnlyRunningTasks()
|
||||
{
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
var completedTask = Task.FromResult("done");
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var runningTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
|
||||
|
||||
cliRunner.AddTaskForTest(completedTask, "completed");
|
||||
cliRunner.AddTaskForTest(runningTask, "running", cts);
|
||||
|
||||
await Task.Delay(50);
|
||||
|
||||
cliRunner.RefreshTaskStates();
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(2, cliRunner.TaskPool.Count);
|
||||
|
||||
var completed = cliRunner.TaskPool.First(t => t.Name == "completed");
|
||||
var timedOut = cliRunner.TaskPool.First(t => t.Name == "running");
|
||||
|
||||
Assert.AreEqual(CliTaskState.Completed, completed.State);
|
||||
Assert.IsTrue(completed.UseResult);
|
||||
|
||||
Assert.AreEqual(CliTaskState.TimedOut, timedOut.State);
|
||||
Assert.IsFalse(timedOut.UseResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TimeOutReceived_ShouldReturnTrue_WhenElapsedExceedsTimeout()
|
||||
{
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
Thread.Sleep(30);
|
||||
|
||||
Assert.IsTrue(cliRunner.TimeOutReceived(1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task AreTasksDone_ShouldReturnTrue_WhenTimedOutTasksAreMarked()
|
||||
{
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var runningTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
|
||||
|
||||
cliRunner.AddTaskForTest(runningTask, "running", cts);
|
||||
|
||||
await Task.Delay(50);
|
||||
|
||||
Assert.IsFalse(cliRunner.AreTasksDone());
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.IsTrue(cliRunner.AreTasksDone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,6 +104,8 @@
|
||||
<Compile Include="Rig\Network\Camera\RoiForFixedStartKeyence\RoiTest.cs" />
|
||||
<Compile Include="Rig\Output\FileWriters\Enhanced\WriterTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutUnitTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user