162 lines
5.6 KiB
Go
162 lines
5.6 KiB
Go
package workflows
|
||
|
||
import (
|
||
"encoding/json"
|
||
"go.temporal.io/sdk/temporal"
|
||
"sort"
|
||
"time"
|
||
|
||
"beacon/activities" // 假设你的 activity 包在这个路径
|
||
"beacon/pkg/pb" // 你的 Protobuf 路径
|
||
"go.temporal.io/sdk/workflow"
|
||
// 假设你有数据库访问层,Workflow 不能直接访问 DB,需要通过 Activity 或预加载数据
|
||
// "your_module_path/go-server/dal" // 例如 Data Access Layer
|
||
)
|
||
|
||
// DynamicTestSuiteWorkflow 是通用的工作流,根据配置动态执行测试
|
||
func DynamicTestSuiteWorkflow(ctx workflow.Context, input *pb.DynamicTestRunInput) (*pb.TestRunOutput, error) {
|
||
logger := workflow.GetLogger(ctx)
|
||
logger.Info("DynamicTestSuiteWorkflow started", "runID", input.RunId, "compositeCaseID", input.CompositeCaseId)
|
||
|
||
// Workflow 不能直接访问数据库。
|
||
// 方式一 (推荐): 在 Workflow 启动时,由 Go Client 将完整的复合案例步骤数据作为 DynamicTestRunInput 的一部分传入。
|
||
// 方式二 (如果数据太大): Workflow 通过一个 "LoadCompositeCaseSteps" Activity 来从 DB 获取数据。
|
||
// 这里假设 input 包含了所有步骤信息,或者我们在 Workflow 开始时通过 Activity 加载了
|
||
|
||
// 假设我们通过 Activity 加载步骤定义
|
||
var caseSteps []*pb.CompositeCaseStepDefinition // 你需要为这个结构定义一个 Protobuf 消息
|
||
err := workflow.ExecuteActivity(ctx, activities.LoadCompositeCaseSteps, input.CompositeCaseId).Get(ctx, &caseSteps)
|
||
if err != nil {
|
||
logger.Error("Failed to load composite case steps", "error", err)
|
||
return nil, err
|
||
}
|
||
|
||
// 按 step_order 排序
|
||
sort.Slice(caseSteps, func(i, j int) bool {
|
||
return caseSteps[i].StepOrder < caseSteps[j].StepOrder
|
||
})
|
||
|
||
var (
|
||
overallSuccess = true
|
||
apiResults []*pb.ApiTestResult
|
||
uiResults []*pb.UiTestResult
|
||
stepResults = make(map[string]bool) // 存储每个步骤的成功状态
|
||
currentStepOrder = 0
|
||
)
|
||
|
||
// 循环执行步骤
|
||
for currentStepOrder < len(caseSteps) {
|
||
step := caseSteps[currentStepOrder]
|
||
logger.Info("Executing step", "stepOrder", step.StepOrder, "activityName", step.ActivityName)
|
||
|
||
// 动态解析 Activity 参数
|
||
var activityInput interface{} // 使用 interface{} 来存储 Protobuf Activity Input
|
||
var activityResult interface{} // 使用 interface{} 来存储 Protobuf Activity Output
|
||
|
||
// 将 JSON 参数字符串反序列化到对应 Activity 的 Protobuf 结构
|
||
// 这是最复杂的部分,需要一个映射表或工厂函数来根据 activity_name 决定用哪个 Protobuf 结构
|
||
// 例如:
|
||
switch step.ActivityName {
|
||
case "RunApiTest":
|
||
apiReq := &pb.ApiTestRequest{}
|
||
if err := json.Unmarshal([]byte(step.ParametersJson), apiReq); err != nil {
|
||
logger.Error("Failed to unmarshal API test parameters", "error", err)
|
||
overallSuccess = false
|
||
break // 跳出当前步骤
|
||
}
|
||
activityInput = apiReq
|
||
activityResult = &pb.ApiTestResult{}
|
||
case "RunUiTest":
|
||
uiReq := &pb.UiTestRequest{}
|
||
if err := json.Unmarshal([]byte(step.ParametersJson), uiReq); err != nil {
|
||
logger.Error("Failed to unmarshal UI test parameters", "error", err)
|
||
overallSuccess = false
|
||
break // 跳出当前步骤
|
||
}
|
||
activityInput = uiReq
|
||
activityResult = &pb.UiTestResult{}
|
||
case "PrepareEnvironment":
|
||
// ...
|
||
/*activityInput = &pb.PrepareEnvRequest{} // 假设有这个
|
||
activityResult = &pb.PrepareEnvResult{}*/
|
||
break
|
||
default:
|
||
logger.Error("Unknown activity name", "activityName", step.ActivityName)
|
||
overallSuccess = false
|
||
break
|
||
}
|
||
|
||
if activityInput == nil {
|
||
overallSuccess = false
|
||
logger.Error("Activity input could not be constructed for step", "stepOrder", step.StepOrder)
|
||
break
|
||
}
|
||
|
||
// 设置 Activity Options
|
||
ao := workflow.ActivityOptions{
|
||
StartToCloseTimeout: 10 * time.Minute,
|
||
HeartbeatTimeout: 30 * time.Second,
|
||
RetryPolicy: &temporal.RetryPolicy{
|
||
InitialInterval: time.Second,
|
||
MaximumAttempts: 3,
|
||
},
|
||
}
|
||
stepCtx := workflow.WithActivityOptions(ctx, ao)
|
||
|
||
// 动态执行 Activity
|
||
err = workflow.ExecuteActivity(stepCtx, step.ActivityName, activityInput).Get(stepCtx, activityResult)
|
||
|
||
stepPassed := true
|
||
if err != nil {
|
||
logger.Error("Activity execution failed", "activityName", step.ActivityName, "error", err)
|
||
stepPassed = false
|
||
overallSuccess = false // 标记整个流程失败
|
||
} else {
|
||
// 根据 activityResult 提取 BaseTestResult.Success
|
||
// 这里需要类型断言来获取具体的结果
|
||
switch res := activityResult.(type) {
|
||
case *pb.ApiTestResult:
|
||
apiResults = append(apiResults, res)
|
||
if !res.BaseResult.Success {
|
||
stepPassed = false
|
||
overallSuccess = false
|
||
}
|
||
case *pb.UiTestResult:
|
||
uiResults = append(uiResults, res)
|
||
if !res.BaseResult.Success {
|
||
stepPassed = false
|
||
overallSuccess = false
|
||
}
|
||
}
|
||
logger.Info("Activity execution finished", "activityName", step.ActivityName, "success", stepPassed)
|
||
}
|
||
|
||
stepResults[step.StepId] = stepPassed
|
||
|
||
// 根据结果和条件确定下一步
|
||
nextStep := currentStepOrder + 1 // 默认顺序执行
|
||
|
||
if stepPassed && step.SuccessNextStepOrder != nil {
|
||
nextStep = int(*step.SuccessNextStepOrder) - 1 // Adjust for 0-indexed slice
|
||
} else if !stepPassed && step.FailureNextStepOrder != nil {
|
||
nextStep = int(*step.FailureNextStepOrder) - 1
|
||
}
|
||
|
||
// 确保跳转的 nextStep 索引有效
|
||
if nextStep < 0 || nextStep >= len(caseSteps) {
|
||
break // 超出范围,退出循环
|
||
}
|
||
|
||
currentStepOrder = nextStep
|
||
}
|
||
|
||
// 最终返回结果
|
||
return &pb.TestRunOutput{
|
||
RunId: input.RunId,
|
||
OverallSuccess: overallSuccess,
|
||
ApiResults: apiResults,
|
||
UiResults: uiResults,
|
||
CompletionMessage: "Dynamic test suite finished.",
|
||
}, nil
|
||
}
|