Przeglądaj źródła

fix: 修复匹配等待异常问题
1. 修复了在匹配成功但是提取错误时的异常
2. 优化了结束测试后的稳定性

kindring 1 tydzień temu
rodzic
commit
19fc253b8a
4 zmienionych plików z 399 dodań i 86 usunięć
  1. 249 0
      bird_tool/ExcelLoad.cs
  2. 126 42
      bird_tool/TestEngine.cs
  3. 20 44
      bird_tool/bird_tool.cs
  4. 4 0
      bird_tool/bird_tool.csproj

+ 249 - 0
bird_tool/ExcelLoad.cs

@@ -0,0 +1,249 @@
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+
+namespace digital
+{
+    public class ExcelDataManager
+    {
+        private readonly string _excelPath;
+        private readonly string _progressPath;
+        private readonly List<Dictionary<string, string>> _rows = new List<Dictionary<string, string>>();
+        private readonly HashSet<string> _usedKeys = new HashSet<string>();
+        private int _currentIndex = 0;
+        private string[] _headers;
+
+        // 主键列名(用于进度跟踪)
+        private string _primaryKeyColumn = "账号";
+
+        public ExcelDataManager(string excelPath, string progressPath = "progress.json", string primaryKeyColumn = "账号")
+        {
+            _excelPath = excelPath;
+            _progressPath = progressPath;
+            _primaryKeyColumn = primaryKeyColumn;
+            LoadExcelData();
+            LoadProgress();
+        }
+
+        private void LoadExcelData()
+        {
+            FileInfo file = new FileInfo(_excelPath);
+            using (ExcelPackage package = new ExcelPackage(file))
+            {
+                ExcelWorksheet worksheet = package.Workbook.Worksheets[0];
+                int rowCount = worksheet.Dimension.Rows;
+                int colCount = worksheet.Dimension.Columns;
+
+                // 读取标题行
+                _headers = new string[colCount];
+                for (int col = 1; col <= colCount; col++)
+                {
+                    _headers[col - 1] = worksheet.Cells[1, col].Text.Trim();
+                }
+
+                // 读取数据行
+                for (int row = 2; row <= rowCount; row++)
+                {
+                    var rowData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+                    for (int col = 1; col <= colCount; col++)
+                    {
+                        string header = _headers[col - 1];
+                        string cellValue = worksheet.Cells[row, col].Text.Trim();
+                        rowData[header] = cellValue;
+                    }
+
+                    // 只添加有主键的行
+                    if (rowData.ContainsKey(_primaryKeyColumn))
+                    {
+                        _rows.Add(rowData);
+                    }
+                }
+            }
+        }
+
+        // 顺序获取下一行数据
+        public Dictionary<string, string> GetNextRow()
+        {
+            while (_currentIndex < _rows.Count)
+            {
+                var row = _rows[_currentIndex++];
+                string key = GetPrimaryKey(row);
+
+                if (!_usedKeys.Contains(key))
+                {
+                    _usedKeys.Add(key);
+                    return new Dictionary<string, string>(row);
+                }
+            }
+            return null;
+        }
+
+        // 根据主键值获取整行数据
+        public Dictionary<string, string> GetRowByKey(string keyValue)
+        {
+            if (string.IsNullOrWhiteSpace(keyValue)) return null;
+
+            var row = _rows.FirstOrDefault(r =>
+                GetPrimaryKey(r).Equals(keyValue, StringComparison.OrdinalIgnoreCase) &&
+                !_usedKeys.Contains(GetPrimaryKey(r)));
+
+            if (row != null)
+            {
+                _usedKeys.Add(GetPrimaryKey(row));
+                return new Dictionary<string, string>(row);
+            }
+            return null;
+        }
+
+        // 根据任意列查询获取整行数据
+        public Dictionary<string, string> FindRowByColumnValue(string columnName, string columnValue)
+        {
+            if (string.IsNullOrWhiteSpace(columnName)) return null;
+            if (string.IsNullOrWhiteSpace(columnValue)) return null;
+
+            var row = _rows.FirstOrDefault(r =>
+                r.ContainsKey(columnName) &&
+                r[columnName].Equals(columnValue, StringComparison.OrdinalIgnoreCase) &&
+                !_usedKeys.Contains(GetPrimaryKey(r)));
+
+            if (row != null)
+            {
+                _usedKeys.Add(GetPrimaryKey(row));
+                return new Dictionary<string, string>(row);
+            }
+            return null;
+        }
+
+        // 根据主键值获取指定列的值
+        public string GetColumnValue(string keyValue, string columnName)
+        {
+            var row = GetRowByKey(keyValue);
+            return row != null && row.TryGetValue(columnName, out string value) ? value : null;
+        }
+
+        // 根据任意列查询获取指定列的值
+        public string GetColumnValueByQuery(string queryColumn, string queryValue, string resultColumn)
+        {
+            var row = FindRowByColumnValue(queryColumn, queryValue);
+            return row != null && row.TryGetValue(resultColumn, out string value) ? value : null;
+        }
+
+        // 获取主键值
+        private string GetPrimaryKey(Dictionary<string, string> row)
+        {
+            return row.TryGetValue(_primaryKeyColumn, out string value) ? value : "";
+        }
+
+        // 保存进度
+        public void SaveProgress()
+        {
+            var progress = new ProgressData
+            {
+                CurrentIndex = _currentIndex,
+                UsedKeys = _usedKeys.ToList()
+            };
+
+            File.WriteAllText(_progressPath, JsonConvert.SerializeObject(progress));
+        }
+
+        // 加载进度
+        private void LoadProgress()
+        {
+            if (File.Exists(_progressPath))
+            {
+                try
+                {
+                    var json = File.ReadAllText(_progressPath);
+                    var progress = JsonConvert.DeserializeObject<ProgressData>(json);
+
+                    _currentIndex = progress.CurrentIndex;
+                    _usedKeys.UnionWith(progress.UsedKeys);
+                }
+                catch
+                {
+                    ResetProgress();
+                }
+            }
+        }
+
+        // 重置进度
+        public void ResetProgress()
+        {
+            _currentIndex = 0;
+            _usedKeys.Clear();
+            SaveProgress();
+        }
+
+        // 获取所有列名
+        public IEnumerable<string> GetColumnNames()
+        {
+            return _headers;
+        }
+        // 查找所有匹配的行(不标记为已使用)
+        public List<Dictionary<string, string>> FindAllRowsByColumnValue(string columnName, string columnValue)
+        {
+            return _rows.Where(r =>
+                r.ContainsKey(columnName) &&
+                r[columnName].Equals(columnValue, StringComparison.OrdinalIgnoreCase))
+                .Select(r => new Dictionary<string, string>(r))
+                .ToList();
+        }
+
+        // 多条件查询
+        public Dictionary<string, string> FindRowByMultipleConditions(
+            params (string column, string value)[] conditions)
+        {
+            foreach (var row in _rows)
+            {
+                bool match = true;
+                string key = GetPrimaryKey(row);
+
+                if (_usedKeys.Contains(key)) continue;
+
+                foreach (var condition in conditions)
+                {
+                    if (!row.TryGetValue(condition.column, out string value) ||
+                        !value.Equals(condition.value, StringComparison.OrdinalIgnoreCase))
+                    {
+                        match = false;
+                        break;
+                    }
+                }
+
+                if (match)
+                {
+                    _usedKeys.Add(key);
+                    return new Dictionary<string, string>(row);
+                }
+            }
+            return null;
+        }
+
+        // 获取当前进度状态
+        public (int total, int used, int remaining) GetProgressStatus()
+        {
+            int total = _rows.Count;
+            int used = _usedKeys.Count;
+            return (total, used, total - used);
+        }
+
+        // 标记主键为已使用
+        public void MarkKeyAsUsed(string keyValue)
+        {
+            if (!string.IsNullOrWhiteSpace(keyValue))
+            {
+                _usedKeys.Add(keyValue);
+            }
+        }
+
+        private class ProgressData
+        {
+            public int CurrentIndex { get; set; }
+            public List<string> UsedKeys { get; set; }
+        }
+    }
+}

+ 126 - 42
bird_tool/TestEngine.cs

@@ -276,7 +276,16 @@ namespace Bird_tool
         // 重试次数
         public int RetryCount { get; set; }
         // 当前测试项的状态
-        public TestStepConfig CurrentStep => Steps?[CurrentStepIndex];
+        public TestStepConfig CurrentStep
+        {
+            get
+            {
+                if (Steps == null || CurrentStepIndex < 0 || CurrentStepIndex >= Steps.Count)
+                    return null;
+                return Steps[CurrentStepIndex];
+            }
+        }
+
         public List<TestStepConfig> Steps { get; set; }
         public List<TestStepResult> StepResults { get; } = new List<TestStepResult>();
         public TestReport Report { get; set; } = new TestReport();
@@ -306,8 +315,10 @@ namespace Bird_tool
         public delegate void StepChangedHandler(TestStepConfig step, TestContext context, bool isStarting);
         public delegate void TestFailHandler(TestStepConfig step, TestContext context);
         public delegate void TestSuccessHandler(TestContext context);
+        public delegate void TestEndHandler(bool isFail);
+
+
 
-       
 
 
         public event LogHandler OnLog;
@@ -316,6 +327,7 @@ namespace Bird_tool
         public event StepChangedHandler OnStepChanged;
         public event TestFailHandler OnFailed;
         public event TestSuccessHandler OnSuccess;
+        public event TestEndHandler OnTestEnd;
 
         private readonly SerialManager _serialManager;
         private TestContext _context;
@@ -388,25 +400,21 @@ namespace Bird_tool
             }
         }
 
-        public async Task StopTestAsync(bool waitForCompletion = false)
+        public async Task StopTestAsync(bool waitForCompletion = false, bool testEnd = false)
         {
             try
             {
                 // 取消所有任务
                 _cts.Cancel();
-
+                // 清空任务队列
+                OnLog?.Invoke($"_cts.Cancel();", LogLevel.error);
                 // 等待队列处理完成(如果需要)
                 if (waitForCompletion && _queueProcessor != null && !_queueProcessor.IsCompleted)
                 {
-                    try
-                    {
-                        await _queueProcessor;
-                    }
-                    catch (OperationCanceledException)
-                    {
-                        // 正常取消
-                    }
+                    OnLog?.Invoke($"_queueProcessor", LogLevel.error);
+                    await _queueProcessor;
                 }
+                OnLog?.Invoke($"_queueProcessor ok", LogLevel.error);
             }
             catch (Exception ex)
             {
@@ -420,17 +428,31 @@ namespace Bird_tool
                     _serialManager.Disconnect();
                     _serialManager.OnLineReceived -= HandleResponse;
                 }
+                OnLog?.Invoke($"_serialManager", LogLevel.error);
 
-                // 重置状态
-                _context = null;
-                isStart = false;
+                // 重置上下文但保留配置
+                if (_context != null)
+                {
+                    _context.IsRunning = false;
+                    _context.CurrentStepIndex = -1;
+                    _context.Report = new TestReport();
+                }
 
-                // 重新创建 CTS 以便下次使用
-                _cts.Dispose();
+                OnLog?.Invoke($"Dispose", LogLevel.debug);
+                // 重新创建资源
+                _cts?.Dispose();
                 _cts = new CancellationTokenSource();
-
+                OnLog?.Invoke($"StartQueueProcessor", LogLevel.debug);
                 // 重新启动队列处理器
+                
+                OnLog?.Invoke($"停止任务完成", LogLevel.debug);
+                if (testEnd && isStart)
+                {
+                    OnTestEnd?.Invoke(false);
+                }
+                isStart = false;
                 StartQueueProcessor();
+
             }
         }
 
@@ -442,23 +464,31 @@ namespace Bird_tool
                 OnLogShow?.Invoke("串口打开失败");
                 return false;
             }
+            _serialManager.OnLineReceived += HandleResponse;
+
             lock (_lock)
             {
+
+
+                ResetAllSteps();
                 if (steps == null || steps.Count == 0)
                 {
                     OnLog?.Invoke("步骤列表为空,无法启动测试", LogLevel.error);
                     return false;
                 }
-                foreach (var step in steps)
-                {
-                    step.JumpCount = 0;
-                    step.stepStatus = StepStatus.NotRun;
-                    step.RetryCount = 0;
-                }
-                
-                _serialManager.OnLineReceived += HandleResponse;
+
                 EnqueueTask(async ct =>
                 {
+                    if (isStart)
+                    {
+                        OnLog?.Invoke("测试正在进行中,请先停止当前测试", LogLevel.error);
+                        StopTestAsync(true, false);
+                    }
+                    
+                    if (_context != null && _context.IsRunning)
+                    {
+                        await StopTestAsync(true, false);
+                    }
                     // 初始化上下文
                     _context = new TestContext
                     {
@@ -475,7 +505,17 @@ namespace Bird_tool
                 return true;
             }
         }
+        private void ResetAllSteps()
+        {
+            if (_step_map == null) return;
 
+            foreach (var step in _step_map)
+            {
+                step.stepStatus = StepStatus.NotRun;
+                step.RetryCount = 0;
+                step.JumpCount = 0;
+            }
+        }
         public bool StartTest()
         {
             lock (_lock)
@@ -504,7 +544,8 @@ namespace Bird_tool
                     OnLog?.Invoke($"无法找到分组 {groupId} 的起始步骤", LogLevel.error);
                     return false;
                 }
-                // todo 从第一个id出现的位置提取分组后面的所有内容
+                OnLog?.Invoke($"找到分组ID为 {groupId} 的起始位置 {firstIndex}", LogLevel.info);
+                // 从第一个id出现的位置提取分组后面的所有内容
                 var stepsFromGroup = _step_map.Skip(firstIndex).ToList();
                 foreach (var step in stepsFromGroup)
                 {
@@ -512,6 +553,7 @@ namespace Bird_tool
                     step.stepStatus = StepStatus.NotRun;
                     step.RetryCount = 0;
                 }
+                
                 enableMakeResult = false;
                 return StartTestWithSteps(stepsFromGroup);
             }
@@ -763,6 +805,11 @@ namespace Bird_tool
 
         private async Task ExecuteCurrentStepAsync(CancellationToken ct)
         {
+            if (_context == null || _context.CurrentStep == null)
+            {
+                OnLog?.Invoke("无法执行步骤:上下文或步骤无效", LogLevel.error);
+                return;
+            }
             var step = _context.CurrentStep;
             RecordStepResult(step, TestStatus.Running, "步骤开始执行");
             // 记录步骤开始
@@ -835,7 +882,6 @@ namespace Bird_tool
                 // 重构响应等待逻辑
                 var timeoutCts = new CancellationTokenSource(step.Timeout);
                 var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCts.Token) ;
-                OnLog?.Invoke($"发送命令 test",  LogLevel.info);
                 try
                 {
                     if (step.RequiresUserPrompt &&
@@ -846,34 +892,71 @@ namespace Bird_tool
                     }
                     else
                     {
-                        OnLog?.Invoke($"等待响应(发送后延迟)", LogLevel.debug);
+                        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则直接进行询问
-                if (step.RequiresUserPrompt && string.IsNullOrEmpty(step.SuccessPattern) && string.IsNullOrEmpty(step.SuccessText))
-                {
-                   await PromptQuestionAsync(step, linkedCts.Token);
-                }
             }
 
         }
         private async Task<string> WaitForResponseAsync(TestStepConfig step, CancellationToken ct)
         {
             var tcs = new TaskCompletionSource<string>();
-            Action<string> responseHandler = (data) =>
+            Action<string> responseHandler = async (data) =>
             {
+                if (_context == null || _context.CurrentStep != step)
+                {
+                    OnLog?.Invoke($"<忽略响应> 步骤已变更", LogLevel.debug);
+                    return;
+                }
                 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)))
+                (!string.IsNullOrEmpty(step.SuccessText) && data.Contains(step.SuccessText)))
+                {
+                    if (!string.IsNullOrEmpty(step.ExtractPattern) && !ExtractVariables(data, step))
+                    {
+                        if (!ExtractVariables(data, step))
+                        {
+                            OnLog?.Invoke($"匹配成功, 但是无法提取变量 {data} ", LogLevel.error);
+                            return;
+                        }
+                    }
+
+                    // 自定义验证逻辑
+                    bool shouldRetry = false;
+                    if (step.Validator != null)
+                    {
+                        shouldRetry = step.Validator(data, _context);
+                    }
+
+                    if (shouldRetry)
+                    {
+                        OnLog?.Invoke("验证失败,需要重试", LogLevel.info);
+                        HandleStepFailure("自定义验证失败", false);
+                        tcs.TrySetResult(data);
+                    }
+                    else
+                    {
+                        // 判断是否需要进行提问. 如果需要提问则先进行提问
+                        if (step.RequiresUserPrompt)
+                        {
+                            tcs.TrySetResult(data);
+                            await PromptQuestionAsync(step, ct);
+                        }
+                        else
+                        {
+                            HandleSetpSuccess("匹配到成功关键词");
+                            tcs.TrySetResult(data);
+                        }
+                    }
+                }
+                else if ((!string.IsNullOrEmpty(step.FailurePattern) && Regex.IsMatch(data, step.FailurePattern)) ||
+                         (!string.IsNullOrEmpty(step.FailureText) && data.Contains(step.FailureText)))
                 {
+                    HandleStepFailure("匹配到失败关键词", false);
                     tcs.TrySetResult(data);
                 }
             };
@@ -975,13 +1058,13 @@ namespace Bird_tool
         {
             HandleTestEnd(false);
             OnFailed?.Invoke(_context.CurrentStep, _context);
-            StopTestAsync();
+            StopTestAsync(false, true);
         }
         private void HandleTestSuccess()
         {
             HandleTestEnd(true);
             OnSuccess?.Invoke(_context);
-            StopTestAsync();
+            StopTestAsync(false, true);
         }
 
         // 替换现有的 PromptQuestion 方法
@@ -1021,6 +1104,7 @@ namespace Bird_tool
 
         private void HandleSetpSuccess(string message)
         {
+
             _timeoutTimer?.Dispose();
             _timeoutTimer = null;
             var step = _context.CurrentStep;

+ 20 - 44
bird_tool/bird_tool.cs

@@ -57,6 +57,8 @@ namespace Bird_tool
         private static LogLevel show_log_level = LogLevel.info;
         private static StringBuilder logBuilder = new StringBuilder();
 
+        private bool result_success = false;
+        private string result_msg = "";
 
         private bool SetWifi = false;
         private string wifi_ssid = "";
@@ -65,6 +67,9 @@ namespace Bird_tool
         private string sd_act = "hfyswj188";
         private string debug_passwd = "hfyswj100";
 
+        private string excel_path = "test.xlsx";
+        private string excel_primary_key = "uuid";
+
 
         public bird_tool()
         {
@@ -342,6 +347,7 @@ namespace Bird_tool
             _testExecutor.OnFailed += TestFailedhandle;
             _testExecutor.OnSuccess += TestSuccesshandle;
             _testExecutor.OnStepChanged += HandleStepChanged;
+            _testExecutor.OnTestEnd += TeseEndHandle;
         }
 
         // 刷新串口列表并保持选择
@@ -869,6 +875,7 @@ namespace Bird_tool
 
             if (!start_flag)
             {
+                Log("启动测试失败, 等待重新执行中", LogLevel.error);
                 StopTest(false);
             }
             
@@ -911,7 +918,8 @@ namespace Bird_tool
         {
             string str = $"------ 测试失败-------";
             _testExecutor.StopTestAsync();
-            progressPanel.ShowTestResult(false, str);
+            result_success = false;
+            result_msg = str;
             Log(str);
             Log($"------测试结束-------");
         }
@@ -919,12 +927,19 @@ namespace Bird_tool
         private void TestSuccesshandle(TestContext context)
         {
             string str = $"------ 测试完成, 请更换设备-------";
-            progressPanel.ShowTestResult(true, str);
+            _testExecutor.StopTestAsync();
+            result_success = true;
+            result_msg = str;
             Log(str);
             Log($"------测试通过-------");
             Log("------测试成功-------");
+        }
 
+        private void TeseEndHandle(bool isFailed)
+        {
+            progressPanel.ShowTestResult(result_success, result_msg);
         }
+        
 
 
         private List<TestStepConfig> CreateTestSteps()
@@ -985,17 +1000,7 @@ namespace Bird_tool
                     DelayBefore = 1000,
                     MaxRetries = 3,
                 },
-                new TestStepConfig
-                {
-                    GroupId = "btn_long",
-                    Name = "双击测试3",
-                    Tips = "请再次双击电源按钮 第三次",
-                    Command = "\r\n",
-                    SuccessPattern = "_msg_dbclick_callback",
-                    Timeout = 20000,
-                    DelayBefore = 1000,
-                    MaxRetries = 3,
-                },
+                
 
                 // 红外传感器测试
                 new TestStepConfig
@@ -1100,36 +1105,7 @@ namespace Bird_tool
                     Timeout = 30000,
                     MaxRetries = 3,
                 },
-                new TestStepConfig
-                {
-                    GroupId = "btn_reset",
-                    Name = "重置按钮3",
-                    Tips = "请按下重置按钮 第三次",
-                    Command = "\r\n",
-                    SuccessPattern = "OS start",
-                    Timeout = 30000,
-                    MaxRetries = 3,
-                },
-                new TestStepConfig
-                {
-                    GroupId = "btn_reset",
-                    Name = "重置按钮4",
-                    Tips = "请按下重置按钮 第四次",
-                    Command = "\r\n",
-                    SuccessPattern = "OS start",
-                    Timeout = 30000,
-                    MaxRetries = 3,
-                },
-                new TestStepConfig
-                {
-                    GroupId = "btn_reset",
-                    Name = "重置按钮5",
-                    Tips = "请按下重置按钮 第五次",
-                    Command = "\r\n",
-                    SuccessPattern = "OS start",
-                    Timeout = 30000,
-                    MaxRetries = 3,
-                },
+               
 
                 // 其它命令性质测试
                 new TestStepConfig
@@ -1366,7 +1342,7 @@ namespace Bird_tool
                     GroupId = "wifi_test",
                     ShowStep = false,
                     Name = "配置设备wifi密码",
-                    Tips = "自动配置wifi账号",
+                    Tips = "自动配置wifi密码",
                     PrivateCammand = true,
                     Action = ActionMode.SetVal,
                     VariableNames = { "activeCode" },

+ 4 - 0
bird_tool/bird_tool.csproj

@@ -44,6 +44,7 @@
       <DependentUpon>bird_tool.cs</DependentUpon>
     </Compile>
     <Compile Include="Config.cs" />
+    <Compile Include="ExcelLoad.cs" />
     <Compile Include="ImageServer.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="ProgressPanel.cs">
@@ -92,6 +93,9 @@
     <PackageReference Include="Emgu.CV.runtime.windows">
       <Version>4.10.0.5680</Version>
     </PackageReference>
+    <PackageReference Include="EPPlus">
+      <Version>8.0.7</Version>
+    </PackageReference>
     <PackageReference Include="HttpMultipartParser">
       <Version>5.1.0</Version>
     </PackageReference>