|
@@ -10,6 +10,7 @@ using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
|
|
+using System.Threading.Channels;
|
|
|
|
|
|
namespace Bird_tool
|
|
namespace Bird_tool
|
|
{
|
|
{
|
|
@@ -36,6 +37,17 @@ namespace Bird_tool
|
|
DeviceInfoItems[key] = value;
|
|
DeviceInfoItems[key] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ // 新增:获取设备信息的键值对
|
|
|
|
+ public IEnumerable<KeyValuePair<string, string>> GetDeviceInfoPairs()
|
|
|
|
+ {
|
|
|
|
+ return DeviceInfoItems;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 新增:获取测试项结果
|
|
|
|
+ public IEnumerable<TestReportItem> GetTestItems()
|
|
|
|
+ {
|
|
|
|
+ return Items;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
public class TestReportItem
|
|
public class TestReportItem
|
|
@@ -123,7 +135,6 @@ namespace Bird_tool
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -272,8 +283,21 @@ namespace Bird_tool
|
|
public bool IsRunning { get; set; }
|
|
public bool IsRunning { get; set; }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public static class TaskExtensions
|
|
|
|
+ {
|
|
|
|
+ public static async Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken)
|
|
|
|
+ {
|
|
|
|
+ var tcs = new TaskCompletionSource<T>();
|
|
|
|
+ using (cancellationToken.Register(() => tcs.TrySetCanceled()))
|
|
|
|
+ {
|
|
|
|
+ return await await Task.WhenAny(task, tcs.Task);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
class TestExecutor
|
|
class TestExecutor
|
|
{
|
|
{
|
|
|
|
+
|
|
public delegate void LogHandler(string message, LogLevel level);
|
|
public delegate void LogHandler(string message, LogLevel level);
|
|
public delegate void LogShowHandler(string message);
|
|
public delegate void LogShowHandler(string message);
|
|
|
|
|
|
@@ -283,6 +307,8 @@ namespace Bird_tool
|
|
public delegate void TestFailHandler(TestStepConfig step, TestContext context);
|
|
public delegate void TestFailHandler(TestStepConfig step, TestContext context);
|
|
public delegate void TestSuccessHandler(TestContext context);
|
|
public delegate void TestSuccessHandler(TestContext context);
|
|
|
|
|
|
|
|
+
|
|
|
|
+
|
|
|
|
|
|
public event LogHandler OnLog;
|
|
public event LogHandler OnLog;
|
|
public event LogShowHandler OnLogShow;
|
|
public event LogShowHandler OnLogShow;
|
|
@@ -298,63 +324,115 @@ namespace Bird_tool
|
|
// 步骤表
|
|
// 步骤表
|
|
private List<TestStepConfig> _step_map;
|
|
private List<TestStepConfig> _step_map;
|
|
|
|
|
|
|
|
+ private readonly Channel<Func<CancellationToken, Task>> _taskQueue;
|
|
|
|
+ private CancellationTokenSource _cts = new CancellationTokenSource();
|
|
|
|
+ private Task _queueProcessor;
|
|
|
|
+
|
|
public bool isStart { get; private set; } = false;
|
|
public bool isStart { get; private set; } = false;
|
|
public bool isInit { get; private set; } = false;
|
|
public bool isInit { get; private set; } = false;
|
|
|
|
+ public bool enableMakeResult { get; private set; } = false;
|
|
|
|
|
|
public string ReportFileNameTemplate { get; set; } = "TestReport_{{mac}}_{{timestamp}}";
|
|
public string ReportFileNameTemplate { get; set; } = "TestReport_{{mac}}_{{timestamp}}";
|
|
|
|
+ // 新增:CSV文件路径
|
|
|
|
+ private string _csvReportPath = "TestReport/report.csv";
|
|
|
|
+ private static readonly object _csvLock = new object();
|
|
|
|
|
|
public TestExecutor(SerialManager serialManager)
|
|
public TestExecutor(SerialManager serialManager)
|
|
{
|
|
{
|
|
bird_tool.Log("TestExecutor ");
|
|
bird_tool.Log("TestExecutor ");
|
|
_serialManager = serialManager;
|
|
_serialManager = serialManager;
|
|
- _serialManager.OnLineReceived += HandleResponse;
|
|
|
|
|
|
+ _taskQueue = Channel.CreateBounded<Func<CancellationToken, Task>>(1000);
|
|
|
|
|
|
|
|
+ StartQueueProcessor();
|
|
}
|
|
}
|
|
|
|
|
|
- // 新增方法:将重试操作加入队列异步执行
|
|
|
|
- private void QueueRetryOperation(Action operation)
|
|
|
|
|
|
+ private void StartQueueProcessor()
|
|
{
|
|
{
|
|
- OnLog?.Invoke($"排队重试操作: {operation.Method.Name}", LogLevel.debug);
|
|
|
|
- Task.Run(() =>
|
|
|
|
|
|
+ // 确保之前的队列处理器已停止
|
|
|
|
+ if (_queueProcessor != null && !_queueProcessor.IsCompleted)
|
|
{
|
|
{
|
|
- OnLog?.Invoke($"开始执行排队操作1: {operation.Method.Name}", LogLevel.debug);
|
|
|
|
- // 添加短暂延迟避免立即重试
|
|
|
|
- Thread.Sleep(100);
|
|
|
|
-
|
|
|
|
- lock (_lock)
|
|
|
|
|
|
+ _queueProcessor.Dispose();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _queueProcessor = Task.Run(async () =>
|
|
|
|
+ {
|
|
|
|
+ while (!_cts.Token.IsCancellationRequested &&
|
|
|
|
+ await _taskQueue.Reader.WaitToReadAsync(_cts.Token))
|
|
{
|
|
{
|
|
- // 检查测试是否仍在运行
|
|
|
|
- if (_context != null && _context.IsRunning)
|
|
|
|
|
|
+ if (_taskQueue.Reader.TryRead(out var taskFunc))
|
|
{
|
|
{
|
|
- OnLog?.Invoke($"开始执行排队操作2: {operation.Method.Name}", LogLevel.debug);
|
|
|
|
- operation();
|
|
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ await taskFunc(_cts.Token);
|
|
|
|
+ }
|
|
|
|
+ catch (OperationCanceledException)
|
|
|
|
+ {
|
|
|
|
+ // 正常取消
|
|
|
|
+ }
|
|
|
|
+ catch (Exception ex)
|
|
|
|
+ {
|
|
|
|
+ OnLog?.Invoke($"任务执行失败: {ex.Message}", LogLevel.error);
|
|
|
|
+ OnLogShow?.Invoke($"任务执行失败: {ex.Message}");
|
|
|
|
+ }
|
|
|
|
+
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
- public void StopTest()
|
|
|
|
|
|
+ private void EnqueueTask(Func<CancellationToken, Task> task)
|
|
{
|
|
{
|
|
-
|
|
|
|
- Thread.Sleep(2000);
|
|
|
|
|
|
+ if (!_taskQueue.Writer.TryWrite(task))
|
|
|
|
+ {
|
|
|
|
+ OnLog?.Invoke("任务队列已满,无法添加新任务", LogLevel.error);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- lock (_lock)
|
|
|
|
|
|
+ public async Task StopTestAsync(bool waitForCompletion = false)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
{
|
|
{
|
|
-
|
|
|
|
- if (isStart)
|
|
|
|
- {
|
|
|
|
- isStart = false;
|
|
|
|
- // 确保释放计时器资源
|
|
|
|
- _timeoutTimer?.Dispose();
|
|
|
|
- _timeoutTimer = null;
|
|
|
|
|
|
+ // 取消所有任务
|
|
|
|
+ _cts.Cancel();
|
|
|
|
|
|
- _context.IsRunning = false;
|
|
|
|
- _context = null;
|
|
|
|
|
|
+ // 等待队列处理完成(如果需要)
|
|
|
|
+ if (waitForCompletion && _queueProcessor != null && !_queueProcessor.IsCompleted)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ await _queueProcessor;
|
|
|
|
+ }
|
|
|
|
+ catch (OperationCanceledException)
|
|
|
|
+ {
|
|
|
|
+ // 正常取消
|
|
|
|
+ }
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+ }
|
|
|
|
+ catch (Exception ex)
|
|
|
|
+ {
|
|
|
|
+ OnLog?.Invoke($"停止测试时出错: {ex.Message}", LogLevel.error);
|
|
|
|
+ }
|
|
|
|
+ finally
|
|
|
|
+ {
|
|
|
|
+ // 清理资源
|
|
|
|
+ if (_serialManager != null)
|
|
|
|
+ {
|
|
|
|
+ _serialManager.OnLineReceived -= HandleResponse;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 重置状态
|
|
|
|
+ _context = null;
|
|
|
|
+ isStart = false;
|
|
|
|
+
|
|
|
|
+ // 重新创建 CTS 以便下次使用
|
|
|
|
+ _cts.Dispose();
|
|
|
|
+ _cts = new CancellationTokenSource();
|
|
|
|
+
|
|
|
|
+ // 重新启动队列处理器
|
|
|
|
+ StartQueueProcessor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
private bool StartTestWithSteps(List<TestStepConfig> steps)
|
|
private bool StartTestWithSteps(List<TestStepConfig> steps)
|
|
{
|
|
{
|
|
lock (_lock)
|
|
lock (_lock)
|
|
@@ -370,16 +448,22 @@ namespace Bird_tool
|
|
step.stepStatus = StepStatus.NotRun;
|
|
step.stepStatus = StepStatus.NotRun;
|
|
step.RetryCount = 0;
|
|
step.RetryCount = 0;
|
|
}
|
|
}
|
|
- // 初始化上下文
|
|
|
|
- _context = new TestContext
|
|
|
|
|
|
+ _serialManager.OnLineReceived += HandleResponse;
|
|
|
|
+ EnqueueTask(async ct =>
|
|
{
|
|
{
|
|
- Steps = steps, // 使用传入的步骤列表
|
|
|
|
- CurrentStepIndex = -1
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- isStart = true;
|
|
|
|
|
|
+ // 初始化上下文
|
|
|
|
+ _context = new TestContext
|
|
|
|
+ {
|
|
|
|
+ Steps = steps, // 使用传入的步骤列表
|
|
|
|
+ CurrentStepIndex = -1,
|
|
|
|
+ Report = new TestReport(),
|
|
|
|
+ IsRunning = true,
|
|
|
|
+ };
|
|
|
|
+ isStart = true;
|
|
|
|
+ MoveToNextStep();
|
|
|
|
+
|
|
|
|
+ });
|
|
|
|
|
|
- MoveToNextStep();
|
|
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -392,6 +476,7 @@ namespace Bird_tool
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
+ enableMakeResult = true;
|
|
StartTestWithSteps(_step_map);
|
|
StartTestWithSteps(_step_map);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
@@ -420,6 +505,7 @@ namespace Bird_tool
|
|
step.stepStatus = StepStatus.NotRun;
|
|
step.stepStatus = StepStatus.NotRun;
|
|
step.RetryCount = 0;
|
|
step.RetryCount = 0;
|
|
}
|
|
}
|
|
|
|
+ enableMakeResult = false;
|
|
return StartTestWithSteps(stepsFromGroup);
|
|
return StartTestWithSteps(stepsFromGroup);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -440,8 +526,8 @@ namespace Bird_tool
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
OnLog?.Invoke($"找到分组ID为 {groupId} 的步骤", LogLevel.info);
|
|
OnLog?.Invoke($"找到分组ID为 {groupId} 的步骤", LogLevel.info);
|
|
-
|
|
|
|
|
|
|
|
|
|
+ enableMakeResult = false;
|
|
// 使用提取的分组步骤启动测试
|
|
// 使用提取的分组步骤启动测试
|
|
return StartTestWithSteps(groupSteps);
|
|
return StartTestWithSteps(groupSteps);
|
|
}
|
|
}
|
|
@@ -585,9 +671,6 @@ namespace Bird_tool
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // 从特定方法开始执行
|
|
|
|
-
|
|
|
|
-
|
|
|
|
// 字符串模板替换方法
|
|
// 字符串模板替换方法
|
|
public string ReplaceTemplateVariables(string input, Dictionary<string, string> variables)
|
|
public string ReplaceTemplateVariables(string input, Dictionary<string, string> variables)
|
|
{
|
|
{
|
|
@@ -671,7 +754,7 @@ namespace Bird_tool
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
- private async void ExecuteCurrentStep()
|
|
|
|
|
|
+ private async Task ExecuteCurrentStepAsync(CancellationToken ct)
|
|
{
|
|
{
|
|
var step = _context.CurrentStep;
|
|
var step = _context.CurrentStep;
|
|
RecordStepResult(step, TestStatus.Running, "步骤开始执行");
|
|
RecordStepResult(step, TestStatus.Running, "步骤开始执行");
|
|
@@ -730,10 +813,7 @@ namespace Bird_tool
|
|
// 发送命令
|
|
// 发送命令
|
|
if (!string.IsNullOrEmpty(command))
|
|
if (!string.IsNullOrEmpty(command))
|
|
{
|
|
{
|
|
- if (!step.PrivateCammand)
|
|
|
|
- {
|
|
|
|
- OnLog?.Invoke($"发送命令: {command.Trim()}", LogLevel.info);
|
|
|
|
- }
|
|
|
|
|
|
+ OnLog?.Invoke($"发送命令: {command.Trim()}", step.PrivateCammand? LogLevel.debug: LogLevel.info);
|
|
_serialManager.SendCommand(command);
|
|
_serialManager.SendCommand(command);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -745,60 +825,75 @@ namespace Bird_tool
|
|
// 让当前任务的状态转变未Waiting, 用于在发送消息在进行匹配
|
|
// 让当前任务的状态转变未Waiting, 用于在发送消息在进行匹配
|
|
step.stepStatus = StepStatus.Waiting;
|
|
step.stepStatus = StepStatus.Waiting;
|
|
|
|
|
|
- OnLog?.Invoke($"配置定时器 {step.Timeout}", LogLevel.debug);
|
|
|
|
- _context.IsRunning = true;
|
|
|
|
- _timeoutTimer?.Dispose();
|
|
|
|
- _timeoutTimer = null;
|
|
|
|
- _timeoutTimer = new System.Threading.Timer(
|
|
|
|
- _ => HandleTimeout(),
|
|
|
|
- null,
|
|
|
|
- step.Timeout,
|
|
|
|
- Timeout.Infinite
|
|
|
|
- );
|
|
|
|
|
|
+ // 重构响应等待逻辑
|
|
|
|
+ var timeoutCts = new CancellationTokenSource(step.Timeout);
|
|
|
|
+ var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCts.Token) ;
|
|
|
|
+ OnLog?.Invoke($"发送命令 test", LogLevel.info);
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ if (step.RequiresUserPrompt &&
|
|
|
|
+ string.IsNullOrEmpty(step.SuccessPattern) &&
|
|
|
|
+ string.IsNullOrEmpty(step.SuccessText))
|
|
|
|
+ {
|
|
|
|
+ await PromptQuestionAsync(step, linkedCts.Token);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ OnLog?.Invoke($"等待响应(发送后延迟)", LogLevel.debug);
|
|
|
|
+ var response = await WaitForResponseAsync(step, linkedCts.Token);
|
|
|
|
+ ProcessResponse(response, step, linkedCts.Token);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested)
|
|
|
|
+ {
|
|
|
|
+ HandleStepFailure("操作超时", false);
|
|
|
|
+ }
|
|
|
|
|
|
// 如果没有SuccessPattern 与 SuccessText则直接进行询问
|
|
// 如果没有SuccessPattern 与 SuccessText则直接进行询问
|
|
if (step.RequiresUserPrompt && string.IsNullOrEmpty(step.SuccessPattern) && string.IsNullOrEmpty(step.SuccessText))
|
|
if (step.RequiresUserPrompt && string.IsNullOrEmpty(step.SuccessPattern) && string.IsNullOrEmpty(step.SuccessText))
|
|
{
|
|
{
|
|
- PromptQuestion(step);
|
|
|
|
|
|
+ await PromptQuestionAsync(step, linkedCts.Token);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
- public void HandleResponse(string data)
|
|
|
|
|
|
+ private async Task<string> WaitForResponseAsync(TestStepConfig step, CancellationToken ct)
|
|
{
|
|
{
|
|
|
|
+ var tcs = new TaskCompletionSource<string>();
|
|
|
|
+ Action<string> responseHandler = (data) =>
|
|
|
|
+ {
|
|
|
|
+ if ((!string.IsNullOrEmpty(step.SuccessPattern) && Regex.IsMatch(data, step.SuccessPattern)) ||
|
|
|
|
+ (!string.IsNullOrEmpty(step.SuccessText) && data.Contains(step.SuccessText)) ||
|
|
|
|
+ (!string.IsNullOrEmpty(step.FailurePattern) && Regex.IsMatch(data, step.FailurePattern)) ||
|
|
|
|
+ (!string.IsNullOrEmpty(step.FailureText) && data.Contains(step.FailureText)))
|
|
|
|
+ {
|
|
|
|
+ tcs.TrySetResult(data);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
|
|
- // 减少锁的持有时间
|
|
|
|
- TestContext localContext = null;
|
|
|
|
- TestStepConfig step = null;
|
|
|
|
- OnLog?.Invoke($"收到响应 {data} ", LogLevel.debug);
|
|
|
|
-
|
|
|
|
- lock (_lock)
|
|
|
|
|
|
+ try
|
|
{
|
|
{
|
|
- if (_context == null || !_context.IsRunning) return;
|
|
|
|
- localContext = _context;
|
|
|
|
- step = _context.CurrentStep;
|
|
|
|
|
|
+ _serialManager.OnLineReceived += responseHandler;
|
|
|
|
+ return await tcs.Task.WaitAsync(ct);
|
|
}
|
|
}
|
|
-
|
|
|
|
- // 判断当前任务是否已经开始
|
|
|
|
- if (step.stepStatus != StepStatus.Waiting)
|
|
|
|
|
|
+ finally
|
|
{
|
|
{
|
|
- return;
|
|
|
|
|
|
+ _serialManager.OnLineReceived -= responseHandler;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- // 检查成功模式
|
|
|
|
- if (
|
|
|
|
- (!string.IsNullOrEmpty(step.SuccessPattern) && Regex.IsMatch(data, step.SuccessPattern))
|
|
|
|
- ||
|
|
|
|
- (!string.IsNullOrEmpty(step.SuccessText) && data.Contains(step.SuccessText))
|
|
|
|
- )
|
|
|
|
- {
|
|
|
|
|
|
|
|
|
|
+ // 处理响应
|
|
|
|
+ private async void ProcessResponse(string data, TestStepConfig step, CancellationToken ct)
|
|
|
|
+ {
|
|
|
|
+ if ((!string.IsNullOrEmpty(step.SuccessPattern) && Regex.IsMatch(data, step.SuccessPattern)) ||
|
|
|
|
+ (!string.IsNullOrEmpty(step.SuccessText) && data.Contains(step.SuccessText)))
|
|
|
|
+ {
|
|
if (!string.IsNullOrEmpty(step.ExtractPattern) && !ExtractVariables(data, step))
|
|
if (!string.IsNullOrEmpty(step.ExtractPattern) && !ExtractVariables(data, step))
|
|
{
|
|
{
|
|
if (!ExtractVariables(data, step))
|
|
if (!ExtractVariables(data, step))
|
|
{
|
|
{
|
|
- OnLog?.Invoke($"匹配成功, 但是无法提取变量 {data} ", LogLevel.info);
|
|
|
|
|
|
+ OnLog?.Invoke($"匹配成功, 但是无法提取变量 {data} ", LogLevel.error);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -820,28 +915,24 @@ namespace Bird_tool
|
|
// 判断是否需要进行提问. 如果需要提问则先进行提问
|
|
// 判断是否需要进行提问. 如果需要提问则先进行提问
|
|
if (step.RequiresUserPrompt)
|
|
if (step.RequiresUserPrompt)
|
|
{
|
|
{
|
|
- PromptQuestion(step);
|
|
|
|
|
|
+ await PromptQuestionAsync(step, ct);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
HandleSetpSuccess("匹配到成功关键词");
|
|
HandleSetpSuccess("匹配到成功关键词");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- return;
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
- // 检查失败模式
|
|
|
|
- if ((!string.IsNullOrEmpty(step.FailurePattern) && Regex.IsMatch(data, step.FailurePattern))
|
|
|
|
- ||
|
|
|
|
- (!string.IsNullOrEmpty(step.FailureText) && data.Contains(step.FailureText))
|
|
|
|
- )
|
|
|
|
|
|
+ else if ((!string.IsNullOrEmpty(step.FailurePattern) && Regex.IsMatch(data, step.FailurePattern)) ||
|
|
|
|
+ (!string.IsNullOrEmpty(step.FailureText) && data.Contains(step.FailureText)))
|
|
{
|
|
{
|
|
HandleStepFailure("匹配到失败关键词", false);
|
|
HandleStepFailure("匹配到失败关键词", false);
|
|
- return;
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ public void HandleResponse(string data)
|
|
|
|
+ {
|
|
|
|
+ OnLog?.Invoke($"收到响应 {data} ", LogLevel.debug);
|
|
}
|
|
}
|
|
|
|
|
|
private bool ExtractVariables(string data, TestStepConfig step)
|
|
private bool ExtractVariables(string data, TestStepConfig step)
|
|
@@ -860,7 +951,7 @@ namespace Bird_tool
|
|
_context.Variables[varName] = match.Groups[i].Value;
|
|
_context.Variables[varName] = match.Groups[i].Value;
|
|
matchTotal++;
|
|
matchTotal++;
|
|
isMatch = true;
|
|
isMatch = true;
|
|
- OnLog?.Invoke(msg, LogLevel.info);
|
|
|
|
|
|
+ OnLog?.Invoke(msg, step.PrivateCammand? LogLevel.debug: LogLevel.info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 记录缺失变量
|
|
// 记录缺失变量
|
|
@@ -877,35 +968,49 @@ namespace Bird_tool
|
|
{
|
|
{
|
|
HandleTestEnd(false);
|
|
HandleTestEnd(false);
|
|
OnFailed?.Invoke(_context.CurrentStep, _context);
|
|
OnFailed?.Invoke(_context.CurrentStep, _context);
|
|
- StopTest();
|
|
|
|
|
|
+ StopTestAsync();
|
|
}
|
|
}
|
|
private void HandleTestSuccess()
|
|
private void HandleTestSuccess()
|
|
{
|
|
{
|
|
HandleTestEnd(true);
|
|
HandleTestEnd(true);
|
|
OnSuccess?.Invoke(_context);
|
|
OnSuccess?.Invoke(_context);
|
|
- StopTest();
|
|
|
|
|
|
+ StopTestAsync();
|
|
}
|
|
}
|
|
|
|
|
|
- private async void PromptQuestion(TestStepConfig step)
|
|
|
|
|
|
+ // 替换现有的 PromptQuestion 方法
|
|
|
|
+ private async Task PromptQuestionAsync(TestStepConfig step, CancellationToken ct)
|
|
{
|
|
{
|
|
OnLog?.Invoke($"等待用户确认: {step.PromptQuestion}", LogLevel.info);
|
|
OnLog?.Invoke($"等待用户确认: {step.PromptQuestion}", LogLevel.info);
|
|
- // 停止计时器, 防止用户在确认阶段导致失败
|
|
|
|
- _timeoutTimer?.Dispose();
|
|
|
|
- _timeoutTimer = null;
|
|
|
|
|
|
|
|
- if (step.DelayBefore > 0)
|
|
|
|
|
|
+ if (step.DelayAfterPrompt > 0)
|
|
{
|
|
{
|
|
- OnLog?.Invoke($"等待 {step.DelayBefore}ms (等待确认前延迟)", LogLevel.debug);
|
|
|
|
- await Task.Delay(step.DelayBefore);
|
|
|
|
|
|
+ OnLog?.Invoke($"等待 {step.DelayAfterPrompt}ms (提示延迟)", LogLevel.debug);
|
|
|
|
+ await Task.Delay(step.DelayAfterPrompt, ct);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ var promptTcs = new TaskCompletionSource<bool>();
|
|
|
|
+
|
|
|
|
+ // 在主线程执行UI提示
|
|
OnPrompt?.Invoke(
|
|
OnPrompt?.Invoke(
|
|
ReplaceTemplateVariables(step.PromptQuestion, _context.Variables),
|
|
ReplaceTemplateVariables(step.PromptQuestion, _context.Variables),
|
|
- () => HandleSetpSuccess("用户确认成功"),
|
|
|
|
- () => HandleStepFailure("用户确认失败", true)
|
|
|
|
|
|
+ () => promptTcs.TrySetResult(true),
|
|
|
|
+ () => promptTcs.TrySetResult(false)
|
|
);
|
|
);
|
|
- }
|
|
|
|
|
|
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ bool result = await promptTcs.Task.WaitAsync(ct);
|
|
|
|
+
|
|
|
|
+ if (result)
|
|
|
|
+ HandleSetpSuccess("用户确认成功");
|
|
|
|
+ else
|
|
|
|
+ HandleStepFailure("用户确认失败", true);
|
|
|
|
+ }
|
|
|
|
+ catch (OperationCanceledException)
|
|
|
|
+ {
|
|
|
|
+ HandleStepFailure("用户响应超时", true);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
private void HandleSetpSuccess(string message)
|
|
private void HandleSetpSuccess(string message)
|
|
{
|
|
{
|
|
@@ -1118,10 +1223,11 @@ namespace Bird_tool
|
|
// 在测试结束时调用生成报告
|
|
// 在测试结束时调用生成报告
|
|
private void HandleTestEnd(bool saveReport)
|
|
private void HandleTestEnd(bool saveReport)
|
|
{
|
|
{
|
|
- GenerateTestReport();
|
|
|
|
- if (saveReport)
|
|
|
|
|
|
+ if (saveReport && enableMakeResult)
|
|
{
|
|
{
|
|
|
|
+ GenerateTestReport();
|
|
SaveReportToHtml(_context.Report);
|
|
SaveReportToHtml(_context.Report);
|
|
|
|
+ AppendReportToCsv(_context.Report); // 新增CSV记录
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1192,59 +1298,62 @@ namespace Bird_tool
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
- if (allowRetry)
|
|
|
|
- {
|
|
|
|
- // 重新执行任务
|
|
|
|
- QueueRetryOperation(() => ExecuteCurrentStep());
|
|
|
|
- }
|
|
|
|
- else if (isJump && jumpIdx >= 0)
|
|
|
|
|
|
+ EnqueueTask(async ct =>
|
|
{
|
|
{
|
|
- // 跳转至特定任务重新执行
|
|
|
|
- QueueRetryOperation(() => JumpToStep(jumpIdx));
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- _context.IsRunning = false;
|
|
|
|
- OnLog?.Invoke(failStr, level);
|
|
|
|
- // 自定义失败处理
|
|
|
|
- step.OnFailure?.Invoke(_context);
|
|
|
|
- OnStepChanged?.Invoke(step, _context, false);
|
|
|
|
- // 测试失败, 判断是直接失败还是忽略此项
|
|
|
|
- if (step.FailContinue)
|
|
|
|
|
|
+ if (allowRetry)
|
|
|
|
+ {
|
|
|
|
+ // 重新执行任务
|
|
|
|
+ await ExecuteCurrentStepAsync(ct);
|
|
|
|
+ }
|
|
|
|
+ else if (isJump && jumpIdx >= 0)
|
|
{
|
|
{
|
|
- QueueRetryOperation(MoveToNextStep);
|
|
|
|
|
|
+ // 跳转至特定任务重新执行
|
|
|
|
+ JumpToStep(jumpIdx);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- HandleTestFail();
|
|
|
|
|
|
+ _context.IsRunning = false;
|
|
|
|
+ OnLog?.Invoke(failStr, level);
|
|
|
|
+ // 自定义失败处理
|
|
|
|
+ step.OnFailure?.Invoke(_context);
|
|
|
|
+ OnStepChanged?.Invoke(step, _context, false);
|
|
|
|
+ // 测试失败, 判断是直接失败还是忽略此项
|
|
|
|
+ if (step.FailContinue)
|
|
|
|
+ {
|
|
|
|
+ MoveToNextStep();
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ HandleTestFail();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
private void MoveToNextStep()
|
|
private void MoveToNextStep()
|
|
{
|
|
{
|
|
- // 判断是否需要等待 DelayAfter
|
|
|
|
|
|
+ EnqueueTask(async ct =>
|
|
|
|
+ {
|
|
|
|
+ // 确保上下文存在
|
|
|
|
+ if (_context == null || !_context.IsRunning)
|
|
|
|
+ {
|
|
|
|
+ OnLog?.Invoke($"========== 程序未执行,无法进入下一步 ==========", LogLevel.error);
|
|
|
|
+ OnLogShow?.Invoke($"========== 程序未执行,无法进入下一步 ==========");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ _context.CurrentStepIndex++;
|
|
|
|
|
|
- _context.CurrentStepIndex++;
|
|
|
|
|
|
+ if (_context.CurrentStepIndex >= _context.Steps.Count)
|
|
|
|
+ {
|
|
|
|
+ HandleTestSuccess();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- if (_context.CurrentStepIndex >= _context.Steps.Count)
|
|
|
|
- {
|
|
|
|
- // 测试完成
|
|
|
|
- _context.IsRunning = false;
|
|
|
|
- OnLog?.Invoke("所有测试步骤完成", LogLevel.info);
|
|
|
|
- HandleTestSuccess();
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (_context.CurrentStepIndex < _context.Steps.Count)
|
|
|
|
- {
|
|
|
|
OnStepChanged?.Invoke(_context.CurrentStep, _context, true);
|
|
OnStepChanged?.Invoke(_context.CurrentStep, _context, true);
|
|
OnLog?.Invoke($"========== 进入步骤: {_context.CurrentStep.Name} ==========", LogLevel.info);
|
|
OnLog?.Invoke($"========== 进入步骤: {_context.CurrentStep.Name} ==========", LogLevel.info);
|
|
- }
|
|
|
|
- _context.RetryCount = 0;
|
|
|
|
|
|
|
|
- // 执行下一步
|
|
|
|
- ExecuteCurrentStep();
|
|
|
|
|
|
+ await ExecuteCurrentStepAsync(ct);
|
|
|
|
+ });
|
|
}
|
|
}
|
|
private int FindKeyByIndex(string key)
|
|
private int FindKeyByIndex(string key)
|
|
{
|
|
{
|
|
@@ -1258,58 +1367,106 @@ namespace Bird_tool
|
|
}
|
|
}
|
|
return -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
+
|
|
private void JumpToStep(int newIndex)
|
|
private void JumpToStep(int newIndex)
|
|
{
|
|
{
|
|
- if (newIndex < 0 || newIndex >= _context.Steps.Count)
|
|
|
|
- {
|
|
|
|
- OnLog?.Invoke($"无效的步骤索引: {newIndex}", LogLevel.error);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- // 跳转计数器增加
|
|
|
|
- _context.CurrentStep.JumpCount++;
|
|
|
|
- OnLog?.Invoke($"跳转到步骤: {_context.Steps[newIndex].Name}", LogLevel.debug);
|
|
|
|
- // 当前步骤的失败保留, 前面任务的失败进行移除
|
|
|
|
- int oldIdx = _context.CurrentStepIndex;
|
|
|
|
- if (oldIdx >= 0)
|
|
|
|
|
|
+ EnqueueTask(async ct =>
|
|
{
|
|
{
|
|
- if (oldIdx < newIndex)
|
|
|
|
|
|
+ if (newIndex < 0 || newIndex >= _context.Steps.Count)
|
|
{
|
|
{
|
|
- // 跳转到后方, 一般不会出现
|
|
|
|
- for (int i = oldIdx; i <= newIndex; i++)
|
|
|
|
- {
|
|
|
|
- var step = _context.Steps[i];
|
|
|
|
- step.RetryCount = 0;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
+ OnLog?.Invoke($"无效的步骤索引: {newIndex}", LogLevel.error);
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- else if (newIndex < oldIdx)
|
|
|
|
|
|
+ // 跳转计数器增加
|
|
|
|
+ _context.CurrentStep.JumpCount++;
|
|
|
|
+ OnLog?.Invoke($"跳转到步骤: {_context.Steps[newIndex].Name}", LogLevel.debug);
|
|
|
|
+ // 当前步骤的失败保留, 前面任务的失败进行移除
|
|
|
|
+ int oldIdx = _context.CurrentStepIndex;
|
|
|
|
+ if (oldIdx >= 0)
|
|
{
|
|
{
|
|
- // 跳转到前方执行
|
|
|
|
- for (int i = newIndex; i < oldIdx; i++)
|
|
|
|
|
|
+ if (oldIdx < newIndex)
|
|
|
|
+ {
|
|
|
|
+ // 跳转到后方, 一般不会出现
|
|
|
|
+ for (int i = oldIdx; i <= newIndex; i++)
|
|
|
|
+ {
|
|
|
|
+ var step = _context.Steps[i];
|
|
|
|
+ step.RetryCount = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ else if (newIndex < oldIdx)
|
|
{
|
|
{
|
|
- var step = _context.Steps[i];
|
|
|
|
- step.RetryCount = 0;
|
|
|
|
|
|
+ // 跳转到前方执行
|
|
|
|
+ for (int i = newIndex; i < oldIdx; i++)
|
|
|
|
+ {
|
|
|
|
+ var step = _context.Steps[i];
|
|
|
|
+ step.RetryCount = 0;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- _context.CurrentStepIndex = newIndex;
|
|
|
|
- ExecuteCurrentStep();
|
|
|
|
|
|
+ _context.CurrentStepIndex = newIndex;
|
|
|
|
+ await ExecuteCurrentStepAsync(ct);
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
- private void HandleTimeout()
|
|
|
|
|
|
+
|
|
|
|
+ // 新增:追加CSV记录
|
|
|
|
+ private void AppendReportToCsv(TestReport report)
|
|
{
|
|
{
|
|
- // 使用 try-catch 避免锁嵌套问题
|
|
|
|
- bool shouldHandle = false;
|
|
|
|
- lock (_lock)
|
|
|
|
|
|
+ lock (_csvLock)
|
|
{
|
|
{
|
|
- shouldHandle = (_context != null && _context.IsRunning);
|
|
|
|
- }
|
|
|
|
|
|
+ bool fileExists = File.Exists(_csvReportPath);
|
|
|
|
|
|
- if (shouldHandle)
|
|
|
|
- {
|
|
|
|
- OnLog?.Invoke("操作超时", LogLevel.info);
|
|
|
|
- HandleStepFailure("操作超时", false);
|
|
|
|
|
|
+ using (var writer = new StreamWriter(_csvReportPath, true, Encoding.UTF8))
|
|
|
|
+ {
|
|
|
|
+ // 写入表头(如果文件不存在)
|
|
|
|
+ if (!fileExists)
|
|
|
|
+ {
|
|
|
|
+ var headers = new List<string>
|
|
|
|
+ {
|
|
|
|
+ "StartTime", "EndTime", "Result"
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 添加设备信息列
|
|
|
|
+ foreach (var kv in report.GetDeviceInfoPairs())
|
|
|
|
+ {
|
|
|
|
+ headers.Add(kv.Key);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加测试项列
|
|
|
|
+ foreach (var item in report.GetTestItems())
|
|
|
|
+ {
|
|
|
|
+ headers.Add($"{item.GroupName}_Status");
|
|
|
|
+ headers.Add($"{item.GroupName}_Details");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ writer.WriteLine(string.Join(",", headers));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 构建数据行
|
|
|
|
+ var rowData = new List<string>
|
|
|
|
+ {
|
|
|
|
+ report.StartTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
+ report.EndTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
+ report.TestResult
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 添加设备信息值
|
|
|
|
+ foreach (var kv in report.GetDeviceInfoPairs())
|
|
|
|
+ {
|
|
|
|
+ rowData.Add(kv.Value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加测试项结果
|
|
|
|
+ foreach (var item in report.GetTestItems())
|
|
|
|
+ {
|
|
|
|
+ rowData.Add(item.Status.ToString());
|
|
|
|
+ rowData.Add(item.Details.Replace(",", ";"));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ writer.WriteLine(string.Join(",", rowData));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|