From db9a72604838dbf8996a844adb0693eb03f16f4b Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 25 Oct 2022 14:44:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95):=20?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C=E5=AE=8C=E6=88=90=E5=90=8E?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E7=8A=B6=E6=80=81=E7=BB=9F=E8=AE=A1=E4=B8=8D?= =?UTF-8?q?=E5=87=86=E7=A1=AE=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1018829 --user=赵勇 【接口测试】场景批量执行-集合报告,报告状态是error了,点开查看报告详情,还有好多pending https://www.tapd.cn/55049933/s/1276340 --- .../api/jmeter/MsApiBackendListener.java | 16 +- .../api/jmeter/utils/ReportStatusUtil.java | 90 ++++++++++++ .../commons/constants/CommonConstants.java | 4 + .../scenario/ApiScenarioReportService.java | 137 +++--------------- 4 files changed, 132 insertions(+), 115 deletions(-) create mode 100644 api-test/backend/src/main/java/io/metersphere/api/jmeter/utils/ReportStatusUtil.java diff --git a/api-test/backend/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java b/api-test/backend/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java index fe80a20c72..e2d1c5fc20 100644 --- a/api-test/backend/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java +++ b/api-test/backend/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java @@ -2,7 +2,9 @@ package io.metersphere.api.jmeter; import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil; +import io.metersphere.api.jmeter.utils.ReportStatusUtil; import io.metersphere.cache.JMeterEngineCache; +import io.metersphere.commons.constants.CommonConstants; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.FixedCapacityUtil; @@ -22,6 +24,7 @@ import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; import java.io.Serializable; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -31,6 +34,8 @@ public class MsApiBackendListener extends AbstractBackendListenerClient implemen private TestResultService testResultService; private List queues; private ResultDTO dto; + // 当前场景报告/用例结果状态 + private String status; /** * 参数初始化方法 @@ -61,6 +66,9 @@ public class MsApiBackendListener extends AbstractBackendListenerClient implemen sampleResults = RetryResultUtil.clearLoops(sampleResults); JMeterBase.resultFormatting(sampleResults, dto); testResultService.saveResults(dto); + + status = ReportStatusUtil.getStatus(dto, status); + dto.getArbitraryData().put(CommonConstants.LOCAL_STATUS_KEY, status); sampleResults.clear(); } } @@ -85,6 +93,8 @@ public class MsApiBackendListener extends AbstractBackendListenerClient implemen LoggerUtil.info("执行结果入库存储", dto.getReportId()); testResultService.saveResults(dto); + status = ReportStatusUtil.getStatus(dto, status); + dto.getArbitraryData().put(CommonConstants.LOCAL_STATUS_KEY, status); LoggerUtil.info("重试结果处理结束", dto.getReportId()); } // 全局并发队列 @@ -133,7 +143,9 @@ public class MsApiBackendListener extends AbstractBackendListenerClient implemen } dto.setQueueId(context.getParameter(BackendListenerConstants.QUEUE_ID.name())); dto.setRunType(context.getParameter(BackendListenerConstants.RUN_TYPE.name())); - + if (dto.getArbitraryData() == null) { + dto.setArbitraryData(new LinkedHashMap<>()); + } String ept = context.getParameter(BackendListenerConstants.EPT.name()); if (StringUtils.isNotEmpty(ept)) { dto.setExtendedParameters(JSON.parseObject(context.getParameter(BackendListenerConstants.EPT.name()), Map.class)); @@ -149,4 +161,6 @@ public class MsApiBackendListener extends AbstractBackendListenerClient implemen } return reportId; } + + } diff --git a/api-test/backend/src/main/java/io/metersphere/api/jmeter/utils/ReportStatusUtil.java b/api-test/backend/src/main/java/io/metersphere/api/jmeter/utils/ReportStatusUtil.java new file mode 100644 index 0000000000..c7e7002b2d --- /dev/null +++ b/api-test/backend/src/main/java/io/metersphere/api/jmeter/utils/ReportStatusUtil.java @@ -0,0 +1,90 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.commons.constants.CommonConstants; +import io.metersphere.commons.enums.ApiReportStatus; +import io.metersphere.dto.RequestResult; +import io.metersphere.dto.ResultDTO; +import io.metersphere.utils.LoggerUtil; +import io.metersphere.utils.RetryResultUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ReportStatusUtil { + public static List filterRetryResults(List results) { + List list = new LinkedList<>(); + if (CollectionUtils.isNotEmpty(results)) { + Map> resultMap = results.stream().collect(Collectors.groupingBy(RequestResult::getResourceId)); + resultMap.forEach((k, v) -> { + if (CollectionUtils.isNotEmpty(v)) { + // 校验是否含重试结果 + List isRetryResults = v.stream().filter(c -> StringUtils.isNotEmpty(c.getName()) && c.getName().startsWith(RetryResultUtil.RETRY_CN)).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(isRetryResults)) { + list.add(isRetryResults.get(isRetryResults.size() - 1)); + } else { + // 成功的结果 + list.addAll(v); + } + } + }); + } + return list; + } + + /** + * 返回正确的报告状态 + * + * @param dto jmeter返回 + * @return + */ + public static String getStatus(ResultDTO dto, String status) { + if (StringUtils.equals(status, ApiReportStatus.ERROR.name())) { + return status; + } + if (MapUtils.isNotEmpty(dto.getArbitraryData()) && dto.getArbitraryData().containsKey(CommonConstants.REPORT_STATUS)) { + // 资源池执行整体传输失败,单条传输内容,获取资源池执行统计的状态 + return String.valueOf(dto.getArbitraryData().get(CommonConstants.REPORT_STATUS)); + } + // 过滤掉重试结果后进行统计 + List requestResults = filterRetryResults(dto.getRequestResults()); + long errorSize = requestResults.stream().filter(requestResult -> + StringUtils.equalsIgnoreCase(requestResult.getStatus(), ApiReportStatus.ERROR.name())).count(); + // 误报 + long errorReportResultSize = dto.getRequestResults().stream(). filter( + requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ApiReportStatus.FAKE_ERROR.name())).count(); + // 默认状态 + status = dto.getRequestResults().isEmpty() && StringUtils.isEmpty(status) + ? ApiReportStatus.PENDING.name() + : StringUtils.defaultIfEmpty(status, ApiReportStatus.SUCCESS.name()); + if (errorSize > 0) { + status = ApiReportStatus.ERROR.name(); + } else if (errorReportResultSize > 0) { + status = ApiReportStatus.FAKE_ERROR.name(); + } + // 超时状态 + if (dto != null && dto.getArbitraryData() != null + && dto.getArbitraryData().containsKey(ApiReportStatus.TIMEOUT.name()) + && (Boolean) dto.getArbitraryData().get(ApiReportStatus.TIMEOUT.name())) { + LoggerUtil.info("资源 " + dto.getTestId() + " 执行超时", dto.getReportId()); + status = ApiReportStatus.ERROR.name(); + } + return status; + } + + public static String getStatus(ResultDTO dto) { + if (MapUtils.isNotEmpty(dto.getArbitraryData()) && dto.getArbitraryData().containsKey(CommonConstants.LOCAL_STATUS_KEY)) { + // 本地执行状态 + return String.valueOf(dto.getArbitraryData().get(CommonConstants.LOCAL_STATUS_KEY)); + } + if (MapUtils.isNotEmpty(dto.getArbitraryData()) && dto.getArbitraryData().containsKey(CommonConstants.REPORT_STATUS)) { + // 资源池执行整体传输失败,单条传输内容,获取资源池执行统计的状态 + return String.valueOf(dto.getArbitraryData().get(CommonConstants.REPORT_STATUS)); + } + return getStatus(dto, ""); + } +} diff --git a/api-test/backend/src/main/java/io/metersphere/commons/constants/CommonConstants.java b/api-test/backend/src/main/java/io/metersphere/commons/constants/CommonConstants.java index 1491d8410a..b7524f662c 100644 --- a/api-test/backend/src/main/java/io/metersphere/commons/constants/CommonConstants.java +++ b/api-test/backend/src/main/java/io/metersphere/commons/constants/CommonConstants.java @@ -3,4 +3,8 @@ package io.metersphere.commons.constants; public class CommonConstants { public static final String TrashStatus = "Trash"; public static final String PROJECT_TEMPLATE = "PROJECT_TEMPLATE"; + public static final String LOCAL_STATUS_KEY = "LOCAL_STATUS_KEY"; + public static final String REPORT_STATUS = "REPORT_STATUS"; + public static final String TRIGGER_MODE = "trigger_mode"; + } diff --git a/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java b/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java index 8b1d647bea..252442dc52 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java @@ -1,63 +1,25 @@ package io.metersphere.service.scenario; -import io.metersphere.api.dto.ApiReportBatchRequest; -import io.metersphere.api.dto.ApiScenarioReportDTO; -import io.metersphere.api.dto.DeleteAPIReportRequest; -import io.metersphere.api.dto.EnvironmentType; -import io.metersphere.api.dto.QueryAPIReportRequest; -import io.metersphere.api.dto.RunModeDataDTO; +import io.metersphere.api.dto.*; import io.metersphere.api.dto.automation.ApiScenarioReportInitDTO; import io.metersphere.api.dto.automation.ApiScenarioReportResult; import io.metersphere.api.dto.automation.ExecuteType; import io.metersphere.api.dto.automation.RunScenarioRequest; import io.metersphere.api.dto.datacount.ApiDataCountResult; import io.metersphere.api.dto.definition.RunDefinitionRequest; -import io.metersphere.base.domain.ApiDefinitionExecResultExample; -import io.metersphere.base.domain.ApiDefinitionExecResultWithBLOBs; -import io.metersphere.base.domain.ApiScenario; -import io.metersphere.base.domain.ApiScenarioReport; -import io.metersphere.base.domain.ApiScenarioReportDetail; -import io.metersphere.base.domain.ApiScenarioReportDetailExample; -import io.metersphere.base.domain.ApiScenarioReportExample; -import io.metersphere.base.domain.ApiScenarioReportResultExample; -import io.metersphere.base.domain.ApiScenarioReportStructureExample; -import io.metersphere.base.domain.ApiScenarioReportWithBLOBs; -import io.metersphere.base.domain.ApiScenarioWithBLOBs; -import io.metersphere.base.domain.ApiTestEnvironment; -import io.metersphere.base.domain.ApiTestEnvironmentExample; -import io.metersphere.base.domain.EnvironmentGroup; -import io.metersphere.base.domain.Project; -import io.metersphere.base.domain.TestPlanApiScenario; -import io.metersphere.base.domain.User; -import io.metersphere.base.mapper.ApiDefinitionExecResultMapper; -import io.metersphere.base.mapper.ApiScenarioMapper; -import io.metersphere.base.mapper.ApiScenarioReportDetailMapper; -import io.metersphere.base.mapper.ApiScenarioReportMapper; -import io.metersphere.base.mapper.ApiScenarioReportResultMapper; -import io.metersphere.base.mapper.ApiScenarioReportStructureMapper; -import io.metersphere.base.mapper.ApiTestEnvironmentMapper; -import io.metersphere.base.mapper.EnvironmentGroupMapper; -import io.metersphere.base.mapper.ProjectMapper; -import io.metersphere.base.mapper.plan.TestPlanApiScenarioMapper; +import io.metersphere.api.jmeter.utils.ReportStatusUtil; +import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioReportResultMapper; +import io.metersphere.base.mapper.plan.TestPlanApiScenarioMapper; import io.metersphere.commons.constants.*; import io.metersphere.commons.enums.ApiReportStatus; import io.metersphere.commons.exception.MSException; -import io.metersphere.commons.utils.BeanUtils; -import io.metersphere.commons.utils.CommonBeanFactory; -import io.metersphere.commons.utils.DateUtils; -import io.metersphere.commons.utils.JSON; -import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.commons.utils.*; import io.metersphere.constants.RunModeConstants; -import io.metersphere.dto.ApiReportCountDTO; -import io.metersphere.dto.BaseSystemConfigDTO; -import io.metersphere.dto.MsExecResponseDTO; -import io.metersphere.dto.PlanReportCaseDTO; -import io.metersphere.dto.RequestResult; -import io.metersphere.dto.ResultDTO; -import io.metersphere.dto.UserDTO; +import io.metersphere.dto.*; import io.metersphere.i18n.Translator; import io.metersphere.log.utils.ReflexObjectUtil; import io.metersphere.log.vo.DetailColumn; @@ -68,10 +30,6 @@ import io.metersphere.notice.service.NoticeSendService; import io.metersphere.service.BaseUserService; import io.metersphere.service.ServiceUtils; import io.metersphere.service.SystemParameterService; -import io.metersphere.commons.utils.FixedCapacityUtil; -import io.metersphere.commons.utils.JSONUtil; -import io.metersphere.utils.LoggerUtil; -import io.metersphere.utils.RetryResultUtil; import org.apache.commons.beanutils.BeanMap; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; @@ -84,14 +42,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; @Service @@ -217,8 +168,11 @@ public class ApiScenarioReportService { public QueryAPIReportRequest initRequest(QueryAPIReportRequest request) { if (request != null) { //初始化triggerMode的查询条件: 如果查询API的话,增加 JENKINS_RUN_TEST_PLAN(jenkins调用测试计划时执行的场景) 查询条件 - if (MapUtils.isNotEmpty(request.getFilters()) && request.getFilters().containsKey("trigger_mode") && CollectionUtils.isNotEmpty(request.getFilters().get("trigger_mode")) && request.getFilters().get("trigger_mode").contains("API") && !request.getFilters().get("trigger_mode").contains(ReportTriggerMode.JENKINS_RUN_TEST_PLAN.name())) { - request.getFilters().get("trigger_mode").add(ReportTriggerMode.JENKINS_RUN_TEST_PLAN.name()); + if (MapUtils.isNotEmpty(request.getFilters()) && request.getFilters().containsKey(CommonConstants.TRIGGER_MODE) + && CollectionUtils.isNotEmpty(request.getFilters().get(CommonConstants.TRIGGER_MODE)) + && request.getFilters().get(CommonConstants.TRIGGER_MODE).contains("API") + && !request.getFilters().get(CommonConstants.TRIGGER_MODE).contains(ReportTriggerMode.JENKINS_RUN_TEST_PLAN.name())) { + request.getFilters().get(CommonConstants.TRIGGER_MODE).add(ReportTriggerMode.JENKINS_RUN_TEST_PLAN.name()); } } return request; @@ -241,7 +195,11 @@ public class ApiScenarioReportService { private void checkNameExist(ApiScenarioReportResult request) { ApiScenarioReportExample example = new ApiScenarioReportExample(); - example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andExecuteTypeEqualTo(ExecuteType.Saved.name()).andIdNotEqualTo(request.getId()); + example.createCriteria().andNameEqualTo(request.getName()). + andProjectIdEqualTo(request.getProjectId()). + andExecuteTypeEqualTo(ExecuteType.Saved.name()). + andIdNotEqualTo(request.getId()); + if (apiScenarioReportMapper.countByExample(example) > 0) { MSException.throwException(Translator.get("load_test_already_exists")); } @@ -320,7 +278,7 @@ public class ApiScenarioReportService { } public ApiScenarioReport updatePlanCase(ResultDTO dto) { - String status = getStatus(dto); + String status = ReportStatusUtil.getStatus(dto); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId()); if (testPlanApiScenario != null) { @@ -330,7 +288,8 @@ public class ApiScenarioReportService { } else { testPlanApiScenario.setLastResult(status); } - long successSize = dto.getRequestResults().stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ApiReportStatus.SUCCESS.name())).count(); + long successSize = dto.getRequestResults().stream().filter(requestResult -> + StringUtils.equalsIgnoreCase(requestResult.getStatus(), ApiReportStatus.SUCCESS.name())).count(); String passRate = new DecimalFormat("0%").format((float) successSize / dto.getRequestResults().size()); testPlanApiScenario.setPassRate(passRate); @@ -359,7 +318,7 @@ public class ApiScenarioReportService { List testPlanReportIdList = new ArrayList<>(); StringBuilder scenarioNames = new StringBuilder(); - String status = getStatus(dto); + String status = ReportStatusUtil.getStatus(dto); ApiScenarioReportWithBLOBs report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); if (report != null) { if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) { @@ -467,7 +426,7 @@ public class ApiScenarioReportService { public ApiScenarioReport updateScenario(ResultDTO dto) { // 更新报告状态 - String status = getStatus(dto); + String status = ReportStatusUtil.getStatus(dto); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); // 更新场景状态 ApiScenarioWithBLOBs scenario = apiScenarioMapper.selectByPrimaryKey(dto.getTestId()); @@ -883,56 +842,6 @@ public class ApiScenarioReportService { return report; } - public List filterRetryResults(List results) { - List list = new LinkedList<>(); - if (CollectionUtils.isNotEmpty(results)) { - Map> resultMap = results.stream().collect(Collectors.groupingBy(RequestResult::getResourceId)); - resultMap.forEach((k, v) -> { - if (CollectionUtils.isNotEmpty(v)) { - // 校验是否含重试结果 - List isRetryResults = v.stream().filter(c -> StringUtils.isNotEmpty(c.getName()) && c.getName().startsWith(RetryResultUtil.RETRY_CN)).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(isRetryResults)) { - list.add(isRetryResults.get(isRetryResults.size() - 1)); - } else { - // 成功的结果 - list.addAll(v); - } - } - }); - } - return list; - } - - /** - * 返回正确的报告状态 - * - * @param dto jmeter返回 - * @return - */ - private String getStatus(ResultDTO dto) { - if (MapUtils.isNotEmpty(dto.getArbitraryData()) && dto.getArbitraryData().containsKey("REPORT_STATUS")) { - // 资源池执行整体传输失败,单条传输内容,获取资源池执行统计的状态 - return String.valueOf(dto.getArbitraryData().get("REPORT_STATUS")); - } - // 过滤掉重试结果后进行统计 - List requestResults = filterRetryResults(dto.getRequestResults()); - long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ApiReportStatus.ERROR.name())).count(); - // 误报 - long errorReportResultSize = dto.getRequestResults().stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ApiReportStatus.FAKE_ERROR.name())).count(); - String status = dto.getRequestResults().isEmpty() ? ApiReportStatus.PENDING.name() : ApiReportStatus.SUCCESS.name(); - if (errorSize > 0) { - status = ApiReportStatus.ERROR.name(); - } else if (errorReportResultSize > 0) { - status = ApiReportStatus.FAKE_ERROR.name(); - } - // 超时状态 - if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey(ApiReportStatus.TIMEOUT.name()) && (Boolean) dto.getArbitraryData().get(ApiReportStatus.TIMEOUT.name())) { - LoggerUtil.info("资源 " + dto.getTestId() + " 执行超时", dto.getReportId()); - status = ApiReportStatus.ERROR.name(); - } - return status; - } - public void cleanUpReport(long time, String projectId) { List ids = extApiScenarioReportMapper.selectByProjectIdAndLessThanTime(projectId, time); if (CollectionUtils.isNotEmpty(ids)) {