fix(接口测试): 场景多次引用相同的场景,执行报错

This commit is contained in:
AgAngle 2024-04-02 20:39:50 +08:00 committed by jianxing
parent ffd19fae21
commit 0ab72dcf0e
9 changed files with 125 additions and 130 deletions

View File

@ -44,11 +44,6 @@ public class ExecutionQueue implements Serializable {
*/
private ApiRunModeConfigDTO runModeConfig;
/**
* 全部场景的请求请求总量用于计算执行各种指标
*/
private Long requestCount;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -24,9 +24,4 @@ public class ExecutionQueueDetail implements Serializable {
* 当前资源产生的执行报告id
*/
private String reportId;
/**
* 单个场景要执行的请求总量用于计算执行各种指标
*/
private Long requestCount;
}

View File

@ -79,4 +79,11 @@ public class ApiScenarioStepCommonDTO<T extends ApiScenarioStepCommonDTO> {
@Valid
@Schema(description = "子步骤")
private List<T> children;
/**
* 步骤的唯一ID
* 引用相同场景的情况原步骤ID可能会重复
*/
@Schema(description = "执行时需要的步骤唯一ID引用相同场景的情况原步骤ID可能会重复")
private String uniqueId;
}

View File

@ -57,5 +57,5 @@ public interface ExtApiScenarioMapper {
Long getPos(String projectId);
List<ApiScenario> getApiCaseExecuteInfoByIds(List<String> subIds);
List<ApiScenario> getScenarioExecuteInfoByIds(@Param("ids") List<String> ids);
}

View File

@ -520,7 +520,7 @@
ORDER BY pos DESC
LIMIT 1;
</select>
<select id="getApiCaseExecuteInfoByIds" resultType="io.metersphere.api.domain.ApiScenario">
<select id="getScenarioExecuteInfoByIds" resultType="io.metersphere.api.domain.ApiScenario">
select id, name, environment_id, project_id
from api_scenario
where id in

View File

@ -1,6 +1,5 @@
package io.metersphere.api.service;
import io.metersphere.api.dto.ApiBatchRunInitReportResult;
import io.metersphere.api.service.queue.ApiExecutionQueueService;
import io.metersphere.sdk.dto.api.task.ApiRunModeConfigDTO;
import io.metersphere.sdk.dto.queue.ExecutionQueue;
@ -26,31 +25,11 @@ public class ApiBatchRunBaseService {
*/
public ExecutionQueue initExecutionqueue(List<String> resourceIds, ApiRunModeConfigDTO runModeConfig, String resourceType, Map<String, String> caseReportMap, String userId) {
ExecutionQueue queue = getExecutionQueue(runModeConfig, resourceType, userId);
queue.setRequestCount(runModeConfig.isIntegratedReport() ? resourceIds.size() : 1L);
List<ExecutionQueueDetail> queueDetails = getExecutionQueueDetails(resourceIds, caseReportMap);
apiExecutionQueueService.insertQueue(queue, queueDetails);
return queue;
}
/**
* 初始化执行队列
*
* @param resourceIds
* @param runModeConfig
* @return
*/
public ExecutionQueue initExecutionqueue(List<String> resourceIds, ApiRunModeConfigDTO runModeConfig, String resourceType, ApiBatchRunInitReportResult reportResult, String userId) {
Map<String, Long> scenarioCountMap = reportResult.getScenarioCountMap();
ExecutionQueue queue = getExecutionQueue(runModeConfig, resourceType, userId);
queue.setRequestCount(reportResult.getRequestCount());
List<ExecutionQueueDetail> queueDetails = getExecutionQueueDetails(resourceIds, reportResult.getScenarioReportMap());
for (ExecutionQueueDetail queueDetail : queueDetails) {
queueDetail.setRequestCount(scenarioCountMap.get(queueDetail.getResourceId()));
}
apiExecutionQueueService.insertQueue(queue, queueDetails);
return queue;
}
public List<ExecutionQueueDetail> getExecutionQueueDetails(List<String> resourceIds, Map<String, String> caseReportMap) {
List<ExecutionQueueDetail> queueDetails = new ArrayList<>();
AtomicInteger sort = new AtomicInteger(1);
@ -58,7 +37,6 @@ public class ApiBatchRunBaseService {
ExecutionQueueDetail queueDetail = new ExecutionQueueDetail();
queueDetail.setResourceId(resourceId);
queueDetail.setSort(sort.getAndIncrement());
queueDetail.setRequestCount(1L);
// caseReportMap null 说明是集合报告生成一个虚拟的报告ID
queueDetail.setReportId(caseReportMap == null ? UUID.randomUUID().toString() : caseReportMap.get(resourceId));
queueDetails.add(queueDetail);

View File

@ -315,7 +315,7 @@ public class ApiTestCaseBatchRunService {
TaskRequestDTO taskRequest = getTaskRequestDTO(reportId, apiTestCase, runModeConfig);
taskRequest.setQueueId(queue.getQueueId());
taskRequest.setRequestCount(queue.getRequestCount());
taskRequest.setRequestCount(1l);
execute(taskRequest, apiTestCase, apiTestCaseBlob, BeanUtils.copyBean(new ApiDefinitionExecuteInfo(), apiDefinition));
}

View File

@ -5,12 +5,15 @@ import io.metersphere.api.domain.ApiScenario;
import io.metersphere.api.domain.ApiScenarioRecord;
import io.metersphere.api.domain.ApiScenarioReport;
import io.metersphere.api.domain.ApiScenarioReportStep;
import io.metersphere.api.dto.ApiBatchRunInitReportResult;
import io.metersphere.api.dto.ApiScenarioParamConfig;
import io.metersphere.api.dto.ApiScenarioParseTmpParam;
import io.metersphere.api.dto.debug.ApiResourceRunRequest;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.dto.scenario.ApiScenarioBatchRunRequest;
import io.metersphere.api.dto.scenario.ApiScenarioDetail;
import io.metersphere.api.dto.scenario.ApiScenarioParseParam;
import io.metersphere.api.dto.scenario.ApiScenarioStepDTO;
import io.metersphere.api.mapper.ExtApiScenarioMapper;
import io.metersphere.api.service.ApiBatchRunBaseService;
import io.metersphere.api.service.ApiExecuteService;
import io.metersphere.api.service.queue.ApiExecutionQueueService;
@ -24,19 +27,20 @@ import io.metersphere.sdk.dto.queue.ExecutionQueueDetail;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.DateUtils;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.SubListUtils;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@ -53,6 +57,8 @@ public class ApiScenarioBatchRunService {
private ApiScenarioReportService apiScenarioReportService;
@Resource
private ApiBatchRunBaseService apiBatchRunBaseService;
@Resource
private ExtApiScenarioMapper extApiScenarioMapper;
/**
* 异步批量执行
@ -95,13 +101,13 @@ public class ApiScenarioBatchRunService {
initIntegratedReport(runModeConfig, ids, userId, request.getProjectId());
}
ApiBatchRunInitReportResult reportResult = initReport(ids, runModeConfig, userId);
Map<String, String> scenarioReportMap = initReport(ids, runModeConfig, userId);
// 集成报告执行前先设置成 RUNNING
setRunningIntegrateReport(runModeConfig);
// 先初始化集成报告设置好报告ID再初始化执行队列
ExecutionQueue queue = apiBatchRunBaseService.initExecutionqueue(ids, runModeConfig, ApiExecuteResourceType.API_SCENARIO.name(), reportResult, userId);
ExecutionQueue queue = apiBatchRunBaseService.initExecutionqueue(ids, runModeConfig, ApiExecuteResourceType.API_SCENARIO.name(), scenarioReportMap, userId);
// 执行第一个任务
ExecutionQueueDetail nextDetail = apiExecutionQueueService.getNextDetail(queue.getQueueId());
executeNextTask(queue, nextDetail);
@ -124,7 +130,7 @@ public class ApiScenarioBatchRunService {
apiExecutionSetService.initSet(apiScenarioReport.getId(), ids);
}
ApiBatchRunInitReportResult reportResult = initReport(ids, runModeConfig, userId);
Map<String, String> scenarioReportMap = initReport(ids, runModeConfig, userId);
// 集成报告执行前先设置成 RUNNING
setRunningIntegrateReport(runModeConfig);
@ -132,6 +138,7 @@ public class ApiScenarioBatchRunService {
AtomicInteger errorCount = new AtomicInteger();
// 这里ID顺序和队列的ID顺序保持一致
for (String id : ids) {
String reportId = null;
try {
ApiScenarioDetail apiScenarioDetail = apiScenarioService.get(id);
@ -141,22 +148,18 @@ public class ApiScenarioBatchRunService {
apiExecutionSetService.removeItem(runModeConfig.getCollectionReport().getReportId(), id);
}
LogUtils.info("当前执行任务的用例已删除 {}", id);
break;
continue;
}
// 请求数量集合报告放总的数量独立报告放当前场景的请求数量
Long requestCount;
if (runModeConfig.isIntegratedReport()) {
// 集成报告生成虚拟的报告ID
reportId = IDGenerator.nextStr();
requestCount = reportResult.getRequestCount();
} else {
reportId = reportResult.getScenarioReportMap().get(id);
requestCount = Optional.ofNullable(reportResult.getScenarioCountMap().get(id)).orElse(0L);
reportId = scenarioReportMap.get(id);
}
TaskRequestDTO taskRequest = getTaskRequestDTO(reportId, apiScenarioDetail, runModeConfig);
taskRequest.setRequestCount(requestCount);
execute(taskRequest, apiScenarioDetail);
TaskRequestDTO taskRequest = getTaskRequestDTO(reportId, apiScenarioDetail, runModeConfig);
execute(taskRequest, apiScenarioDetail);
} catch (Exception e) {
LogUtils.error("执行用例失败 {}-{}", reportId, id);
LogUtils.error(e);
@ -168,40 +171,43 @@ public class ApiScenarioBatchRunService {
}
}
private ApiBatchRunInitReportResult initReport(List<String> ids, ApiRunModeConfigDTO runModeConfig, String userId) {
private Map<String, String> initReport(List<String> ids, ApiRunModeConfigDTO runModeConfig, String userId) {
Map<String, String> scenarioReportMap = new HashMap<>();
ApiBatchRunInitReportResult reportResult = new ApiBatchRunInitReportResult();
Map<String, Long> scenarioCountMap = reportResult.getScenarioCountMap();
Boolean isIntegratedReport = runModeConfig.isIntegratedReport();
AtomicInteger sort = new AtomicInteger(1);
List<ApiScenarioReportStep> apiScenarioReportSteps = new ArrayList<>(ids.size());
List<ApiScenario> apiScenarios = new ArrayList<>(ids.size());
// 分批查询
SubListUtils.dealForSubList(ids, 100, subIds -> apiScenarios.addAll(extApiScenarioMapper.getScenarioExecuteInfoByIds(subIds)));
Map<String, ApiScenario> apiScenarioMap = apiScenarios.stream()
.collect(Collectors.toMap(ApiScenario::getId, Function.identity()));
// 这里ID顺序和队列的ID顺序保持一致
for (String id : ids) {
ApiScenarioDetail apiScenarioDetail = apiScenarioService.get(id);
if (apiScenarioDetail == null) {
ApiScenario apiScenario = apiScenarioMap.get(id);
if (apiScenario == null) {
break;
}
// 记录请求数量
Long itemCount = getRequestCount(apiScenarioDetail.getSteps());
scenarioCountMap.put(id, itemCount);
reportResult.setRequestCount(reportResult.getRequestCount() + itemCount);
if (runModeConfig.isIntegratedReport()) {
// 初始化集成报告步骤
initIntegratedReportSteps(apiScenarioDetail, runModeConfig.getCollectionReport().getReportId(), sort.getAndIncrement());
// 集合报告初始化一级步骤
ApiScenarioReportStep apiScenarioReportStep = getApiScenarioReportStep(apiScenario, runModeConfig.getCollectionReport().getReportId(), sort.getAndIncrement());
apiScenarioReportSteps.add(apiScenarioReportStep);
} else {
// 初始化非集成报告
String reportId = initScenarioReport(runModeConfig, apiScenarioDetail, userId).getApiScenarioReportId();
// 初始化报告步骤
apiScenarioService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId);
// 非集合报告初始化独立报告执行时初始化步骤
String reportId = initScenarioReport(runModeConfig, apiScenario, userId).getApiScenarioReportId();
scenarioReportMap.put(id, reportId);
}
}
reportResult.setScenarioReportMap(isIntegratedReport ? null : scenarioReportMap);
return reportResult;
if (CollectionUtils.isNotEmpty(apiScenarioReportSteps)) {
apiScenarioReportService.insertApiScenarioReportStep(apiScenarioReportSteps);
}
return isIntegratedReport ? null : scenarioReportMap;
}
@ -226,18 +232,6 @@ public class ApiScenarioBatchRunService {
}
}
/**
* 初始化集成报告的报告步骤
*/
private void initIntegratedReportSteps(ApiScenarioDetail apiScenarioDetail, String reportId, long sort) {
// 将当前场景生成一级报告步骤
ApiScenarioReportStep apiScenarioReportStep = getApiScenarioReportStep(apiScenarioDetail, reportId, sort);
// 初始化报告步骤
List<ApiScenarioReportStep> scenarioReportSteps = apiScenarioService.getScenarioReportSteps(apiScenarioReportStep.getStepId(), apiScenarioDetail.getSteps(), reportId);
scenarioReportSteps.addFirst(apiScenarioReportStep);
apiScenarioReportService.insertApiScenarioReportStep(scenarioReportSteps);
}
private ApiScenarioReportStep getApiScenarioReportStep(ApiScenario apiScenario, String reportId, long sort) {
ApiScenarioReportStep apiReportStep = new ApiScenarioReportStep();
apiReportStep.setReportId(reportId);
@ -296,14 +290,6 @@ public class ApiScenarioBatchRunService {
}
TaskRequestDTO taskRequest = getTaskRequestDTO(queueDetail.getReportId(), apiScenarioDetail, queue.getRunModeConfig());
taskRequest.setQueueId(queue.getQueueId());
// 请求数量集合报告放总的数量独立报告放当前场景的请求数量
Long requestCount;
if (queue.getRunModeConfig().isIntegratedReport()) {
requestCount = queue.getRequestCount();
} else {
requestCount = Optional.ofNullable(queueDetail.getRequestCount()).orElse(0L);
}
taskRequest.setRequestCount(requestCount);
execute(taskRequest, apiScenarioDetail);
}
@ -328,7 +314,16 @@ public class ApiScenarioBatchRunService {
parseParam.setEnvironmentId(envId);
parseParam.setGrouped(envGroup);
// 初始化报告步骤
if (runModeConfig.isIntegratedReport()) {
apiScenarioService.initScenarioReportSteps(apiScenarioDetail.getId(), apiScenarioDetail.getSteps(), runModeConfig.getCollectionReport().getReportId());
} else {
apiScenarioService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId);
}
taskRequest.setReportId(reportId);
// 记录请求数量
taskRequest.setRequestCount(getRequestCount(apiScenarioDetail.getSteps()));
ApiScenarioParseTmpParam tmpParam = apiScenarioService.parse(msScenario, apiScenarioDetail.getSteps(), parseParam);
@ -340,7 +335,6 @@ public class ApiScenarioBatchRunService {
apiExecuteService.execute(runRequest, taskRequest, parseConfig);
}
private TaskRequestDTO getTaskRequestDTO(String reportId, ApiScenarioDetail apiScenarioDetail, ApiRunModeConfigDTO runModeConfig) {
TaskRequestDTO taskRequest = apiScenarioService.getTaskRequest(reportId, apiScenarioDetail.getId(), apiScenarioDetail.getProjectId(), ApiExecuteRunMode.RUN.name());
taskRequest.setSaveResult(true);

View File

@ -1379,7 +1379,11 @@ public class ApiScenarioService extends MoveNodeService {
* @param reportId
*/
public void initScenarioReportSteps(List<? extends ApiScenarioStepCommonDTO> steps, String reportId) {
List<ApiScenarioReportStep> scenarioReportSteps = getScenarioReportSteps(null, steps, reportId);
initScenarioReportSteps(null, steps, reportId);
}
public void initScenarioReportSteps(String parentId, List<? extends ApiScenarioStepCommonDTO> steps, String reportId) {
List<ApiScenarioReportStep> scenarioReportSteps = getScenarioReportSteps(parentId, steps, reportId);
apiScenarioReportService.insertApiScenarioReportStep(scenarioReportSteps);
}
@ -1393,12 +1397,16 @@ public class ApiScenarioService extends MoveNodeService {
AtomicLong sort = new AtomicLong(1);
List<ApiScenarioReportStep> scenarioReportSteps = new ArrayList<>();
for (ApiScenarioStepCommonDTO step : steps) {
if (StringUtils.isBlank(step.getUniqueId())) {
// 如果没有步骤唯一ID则生成唯一ID
step.setUniqueId(IDGenerator.nextStr());
}
ApiScenarioReportStep scenarioReportStep = getScenarioReportStep(step, reportId, sort.getAndIncrement());
scenarioReportStep.setParentId(parentId);
scenarioReportSteps.add(scenarioReportStep);
List<? extends ApiScenarioStepCommonDTO> children = step.getChildren();
if (CollectionUtils.isNotEmpty(children)) {
scenarioReportSteps.addAll(getScenarioReportSteps(step.getId(), children, reportId));
scenarioReportSteps.addAll(getScenarioReportSteps(step.getUniqueId(), children, reportId));
}
}
return scenarioReportSteps;
@ -1407,7 +1415,7 @@ public class ApiScenarioService extends MoveNodeService {
private ApiScenarioReportStep getScenarioReportStep(ApiScenarioStepCommonDTO step, String reportId, long sort) {
ApiScenarioReportStep scenarioReportStep = new ApiScenarioReportStep();
scenarioReportStep.setReportId(reportId);
scenarioReportStep.setStepId(step.getId());
scenarioReportStep.setStepId(step.getUniqueId());
scenarioReportStep.setSort(sort);
scenarioReportStep.setName(step.getName());
scenarioReportStep.setStepType(step.getStepType());
@ -1637,6 +1645,11 @@ public class ApiScenarioService extends MoveNodeService {
parseParam.getRequestCount().getAndIncrement();
}
if (StringUtils.isBlank(step.getUniqueId())) {
// 如果调试的时候前端没有传步骤唯一ID则生成唯一ID
step.setUniqueId(IDGenerator.nextStr());
}
// 将步骤详情解析生成对应的MsTestElement
AbstractMsTestElement msTestElement = stepParser.parseTestElement(step,
MapUtils.isNotEmpty(resourceDetailMap) ? resourceDetailMap.getOrDefault(step.getResourceId(), StringUtils.EMPTY) : StringUtils.EMPTY, stepDetailMap.get(step.getId()));
@ -1648,8 +1661,9 @@ public class ApiScenarioService extends MoveNodeService {
}
msTestElement.setProjectId(step.getProjectId());
msTestElement.setResourceId(step.getResourceId());
msTestElement.setStepId(step.getId());
msTestElement.setName(step.getName());
// 步骤ID设置为唯一ID
msTestElement.setStepId(step.getUniqueId());
// 记录引用的资源ID和项目ID下载执行文件时需要使用
parseParam.getRefProjectIds().add(step.getProjectId());
@ -1961,7 +1975,7 @@ public class ApiScenarioService extends MoveNodeService {
.stream()
.collect(Collectors.groupingBy(step -> Optional.ofNullable(step.getParentId()).orElse(StringUtils.EMPTY)));
List<ApiScenarioStepDTO> steps = buildStepTree(currentScenarioParentStepMap.get(StringUtils.EMPTY), currentScenarioParentStepMap, scenarioStepMap);
List<ApiScenarioStepDTO> steps = buildStepTree(currentScenarioParentStepMap.get(StringUtils.EMPTY), currentScenarioParentStepMap, scenarioStepMap, new HashSet<>());
// 设置部分引用的步骤的启用状态
setPartialRefStepsEnable(steps, stepDetailMap);
@ -2011,11 +2025,22 @@ public class ApiScenarioService extends MoveNodeService {
*/
private List<ApiScenarioStepDTO> buildStepTree(List<ApiScenarioStepDTO> steps,
Map<String, List<ApiScenarioStepDTO>> parentStepMap,
Map<String, List<ApiScenarioStepDTO>> scenarioStepMap) {
Map<String, List<ApiScenarioStepDTO>> scenarioStepMap,
Set<String> stepIdSet) {
if (CollectionUtils.isEmpty(steps)) {
return Collections.emptyList();
}
steps.forEach(step -> {
for (int i = 0; i < steps.size(); i++) {
ApiScenarioStepDTO step = steps.get(i);
if (stepIdSet.contains(step.getId())) {
// 如果步骤ID已存在说明引用了两个相同的场景其子步骤ID可能会重复导致引用的同一个对象
// 这里重新new一个对象避免执行时处理为同一个步骤
step = BeanUtils.copyBean(new ApiScenarioStepDTO(), step);
steps.set(i, step);
}
stepIdSet.add(step.getId());
// 获取当前步骤的子步骤
List<ApiScenarioStepDTO> children = Optional.ofNullable(parentStepMap.get(step.getId())).orElse(new ArrayList<>(0));
if (isRefOrPartialScenario(step)) {
@ -2029,21 +2054,22 @@ public class ApiScenarioService extends MoveNodeService {
});
if (CollectionUtils.isEmpty(children)) {
return;
continue;
}
// 如果当前步骤是引用的场景获取该场景的子步骤
Map<String, List<ApiScenarioStepDTO>> childStepMap = scenarioSteps
.stream()
.collect(Collectors.groupingBy(item -> Optional.ofNullable(item.getParentId()).orElse(StringUtils.EMPTY)));
step.setChildren(buildStepTree(children, childStepMap, scenarioStepMap));
step.setChildren(buildStepTree(children, childStepMap, scenarioStepMap, stepIdSet));
} else {
if (CollectionUtils.isEmpty(children)) {
return;
continue;
}
step.setChildren(buildStepTree(children, parentStepMap, scenarioStepMap));
step.setChildren(buildStepTree(children, parentStepMap, scenarioStepMap, stepIdSet));
}
});
}
// 排序
return steps.stream()
.sorted(Comparator.comparing(ApiScenarioStepDTO::getSort))