diff --git a/Command/CommandRunningTime.cs b/Command/CommandRunningTime.cs new file mode 100644 index 0000000..c0650e2 --- /dev/null +++ b/Command/CommandRunningTime.cs @@ -0,0 +1,47 @@ +using Common.Attributes; +using Common.Tools; +using System; + +namespace Command +{ + /// + /// 步骤运行耗时统计命令。 + /// + /// 命令库为纯 .NET 类库,不直接引用宿主的步骤管理器类型; + /// 实际的步骤数据查询能力由宿主(StepRunning)在执行测试步骤前通过 + /// 注入(依赖倒置), + /// 多台架并发运行时各作用域的查询器随异步流隔离,互不串扰。 + /// + /// + [ADPCommand] + public static class CommandRunningTime + { + /// + /// 计算测试步骤运行时间之和:统计步骤管理器主程序中, + /// 序号从起始序号到结束序号(含两端)范围内所有步骤的运行耗时总和。 + /// + /// 起始步骤序号(从 1 开始) + /// 结束步骤序号(含本身) + /// 范围内步骤运行时间之和(毫秒);序号超界、范围内无步骤或查询异常时输出 0 + public static double 获取步骤运行时间之和(int 起始序号, int 结束序号) + { + // 读取当前异步执行流注入的步骤耗时查询器(由 StepRunning 设置) + var query = StepRuntimeContext.StepTimeQuery.Value; + if (query == null) + { + Console.WriteLine($"[步骤耗时] 宿主未注入步骤耗时查询器,无法计算序号 {起始序号}~{结束序号} 的运行时间之和"); + return 0; + } + + try + { + return query(起始序号, 结束序号); + } + catch (Exception ex) + { + Console.WriteLine($"[步骤耗时] 计算序号 {起始序号}~{结束序号} 的运行时间之和失败: {ex.Message}"); + return 0; + } + } + } +} diff --git a/Common/Tool/StepRuntimeContext.cs b/Common/Tool/StepRuntimeContext.cs new file mode 100644 index 0000000..7dc618a --- /dev/null +++ b/Common/Tool/StepRuntimeContext.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel; + +namespace Common.Tools +{ + /// + /// 步骤运行时上下文桥接器(命令库与宿主之间的依赖倒置通道)。 + /// + /// 命令库(Command 项目)不引用宿主类型,无法直接访问步骤管理器的数据; + /// 宿主(StepRunning)在执行测试步骤前,将当前作用域的步骤耗时查询委托写入 + /// ;命令库中的耗时统计命令在执行时读取该委托完成查询。 + /// + /// + /// 使用 承载:委托随异步执行流(ExecutionContext)流动, + /// 多台架并发运行时各自持有自己作用域的查询器,天然隔离互不串扰。 + /// + /// + public static class StepRuntimeContext + { + /// + /// 当前异步执行流的步骤耗时查询委托:(起始序号, 结束序号) → 该范围内步骤运行时间之和(毫秒)。 + /// 由宿主(StepRunning)在每次执行测试步骤方法前设置;未设置时命令侧按未注入处理。 + /// + [Browsable(false)] + public static readonly AsyncLocal?> StepTimeQuery = new(); + } +} diff --git a/UIShare/GlobalVariable/StepRunning.cs b/UIShare/GlobalVariable/StepRunning.cs index c9ade2a..9dbe7bf 100644 --- a/UIShare/GlobalVariable/StepRunning.cs +++ b/UIShare/GlobalVariable/StepRunning.cs @@ -73,6 +73,8 @@ namespace UIShare ResetAllStepStatus(program.ErrorStepCollection); tmpParameters.Clear(); TestRoundID = Guid.NewGuid(); + // 注入步骤耗时查询器:AsyncLocal 随异步执行流流动,命令库(CommandRunningTime)据此查询当前作用域主程序的步骤耗时 + StepRuntimeContext.StepTimeQuery.Value = QueryStepRunTimeSum; } foreach (var item in program.Parameters) { @@ -261,6 +263,8 @@ namespace UIShare tmpParameters.Clear(); testItemStack.Clear(); TestRoundID = Guid.NewGuid(); + // 注入步骤耗时查询器:AsyncLocal 随异步执行流流动,命令库(CommandRunningTime)据此查询当前作用域主程序的步骤耗时 + StepRuntimeContext.StepTimeQuery.Value = QueryStepRunTimeSum; } int initialLoopStackCount = loopStack.Count; foreach (var item in program.Parameters) @@ -800,6 +804,30 @@ namespace UIShare } } + /// + /// 查询当前作用域主程序步骤集合中,序号位于 [起始序号, 结束序号](含两端)范围内步骤的运行时间之和(毫秒)。 + /// 由 ExecuteSteps / ExecuteErrorSteps 顶层启动时经 StepRuntimeContext 注入(AsyncLocal 随异步流流动), + /// 供命令库 CommandRunningTime.获取步骤运行时间之和 调用(依赖倒置:命令库不引用宿主类型)。 + /// 序号倒置、超界(范围内不存在步骤)时记录错误日志并返回 0;未执行步骤的耗时按 0 计入。 + /// + private double QueryStepRunTimeSum(int startIndex, int endIndex) + { + var steps = _scopedContext.Program.StepCollection; + if (startIndex > endIndex) + { + LoggerHelper.Error($"[步骤耗时] 查询失败:起始序号 {startIndex} 大于结束序号 {endIndex},返回 0"); + return 0; + } + var matched = steps.Where(s => s.Index >= startIndex && s.Index <= endIndex).ToList(); + if (matched.Count == 0) + { + LoggerHelper.Error($"[步骤耗时] 查询失败:序号 {startIndex}~{endIndex} 范围内不存在测试步骤(主程序共 {steps.Count} 步),返回 0"); + return 0; + } + // 尚未执行或未启用的步骤 RunTime 为 null,按 0 计入 + return matched.Sum(s => s.RunTime ?? 0); + } + private void UpdateCurrentStepResult(StepVM step, bool paraResult = true, bool stepResult = true, int depth = 0) { if (stepResult && paraResult)