diff --git a/backend/src/main/java/io/metersphere/api/cache/TestPlanExecuteInfo.java b/backend/src/main/java/io/metersphere/api/cache/TestPlanExecuteInfo.java index 295ca7ebdc..6946e887de 100644 --- a/backend/src/main/java/io/metersphere/api/cache/TestPlanExecuteInfo.java +++ b/backend/src/main/java/io/metersphere/api/cache/TestPlanExecuteInfo.java @@ -9,6 +9,7 @@ import io.metersphere.base.mapper.ApiScenarioReportMapper; import io.metersphere.commons.constants.TestPlanApiExecuteStatus; import io.metersphere.commons.constants.TestPlanResourceType; import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.track.dto.TestPlanReportExecuteCheckResultDTO; import io.metersphere.utils.LoggerUtil; import lombok.Getter; import lombok.Setter; @@ -20,6 +21,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * @author song.tianyang @@ -30,17 +32,16 @@ import java.util.Map; public class TestPlanExecuteInfo { private String reportId; private String creator; - private Map apiCaseExecInfo = new HashMap<>(); - private Map apiScenarioCaseExecInfo = new HashMap<>(); - private Map loadCaseExecInfo = new HashMap<>(); + private Map apiCaseExecInfo = new ConcurrentHashMap<>(); + private Map apiScenarioCaseExecInfo = new ConcurrentHashMap<>(); + private Map loadCaseExecInfo = new ConcurrentHashMap<>(); - private Map apiCaseExecuteThreadMap = new HashMap<>(); - private Map apiScenarioThreadMap = new HashMap<>(); - private Map loadCaseReportIdMap = new HashMap<>(); - - private Map apiCaseReportMap = new HashMap<>(); - private Map apiScenarioReportMap = new HashMap<>(); + private Map apiCaseExecuteThreadMap = new ConcurrentHashMap<>(); + private Map apiScenarioThreadMap = new ConcurrentHashMap<>(); + private Map loadCaseReportIdMap = new ConcurrentHashMap<>(); + private Map apiCaseReportMap = new ConcurrentHashMap<>(); + private Map apiScenarioReportMap = new ConcurrentHashMap<>(); private boolean reportDataInDataBase; int lastUnFinishedNumCount = 0; @@ -83,7 +84,8 @@ public class TestPlanExecuteInfo { } } - public synchronized int countUnFinishedNum() { + public synchronized TestPlanReportExecuteCheckResultDTO countUnFinishedNum() { + TestPlanReportExecuteCheckResultDTO executeCheck = new TestPlanReportExecuteCheckResultDTO(); int unFinishedCount = 0; this.isApiCaseAllExecuted = true; @@ -129,8 +131,22 @@ public class TestPlanExecuteInfo { LoggerUtil.info("执行的报告还在队列中,重置超时时间"); lastUnFinishedNumCount = unFinishedCount; lastFinishedNumCountTime = System.currentTimeMillis(); + executeCheck.setFinishedCaseChanged(true); + } else if (unFinishedCount == 0) { + executeCheck.setFinishedCaseChanged(true); + } else { + executeCheck.setFinishedCaseChanged(false); } - return unFinishedCount; + + executeCheck.setTimeOut(false); + if (unFinishedCount > 0) { + //20分钟没有案例执行结果更新,则定位超时 + long nowTime = System.currentTimeMillis(); + if (nowTime - lastFinishedNumCountTime > 1200000) { + executeCheck.setTimeOut(true); + } + } + return executeCheck; } public Map> getExecutedResult() { @@ -228,7 +244,7 @@ public class TestPlanExecuteInfo { this.countUnFinishedNum(); } - public void updateReport(Map apiCaseExecResultInfo, Map apiScenarioCaseExecResultInfo) { + public synchronized void updateReport(Map apiCaseExecResultInfo, Map apiScenarioCaseExecResultInfo) { if (MapUtils.isNotEmpty(apiCaseExecResultInfo)) { this.apiCaseReportMap.putAll(apiCaseExecResultInfo); } @@ -236,6 +252,35 @@ public class TestPlanExecuteInfo { if (MapUtils.isNotEmpty(apiScenarioCaseExecResultInfo)) { this.apiScenarioReportMap.putAll(apiScenarioCaseExecResultInfo); } + } + public Map getRunningApiCaseReportMap() { + //key: reportId, value: testPlanApiCaseId + Map returnMap = new HashMap<>(); + for (Map.Entry entry : apiCaseExecInfo.entrySet()) { + String planCaseId = entry.getKey(); + String status = entry.getValue(); + if (StringUtils.equalsIgnoreCase(status, TestPlanApiExecuteStatus.RUNNING.name())) { + if (apiCaseExecuteThreadMap.containsKey(planCaseId)) { + returnMap.put(apiCaseExecuteThreadMap.get(planCaseId), planCaseId); + } + } + } + return returnMap; + } + + public Map getRunningScenarioReportMap() { + //key: reportId, value: testPlanApiScenarioId + Map returnMap = new HashMap<>(); + for (Map.Entry entry : apiScenarioCaseExecInfo.entrySet()) { + String planScenarioId = entry.getKey(); + String status = entry.getValue(); + if (StringUtils.equalsIgnoreCase(status, TestPlanApiExecuteStatus.RUNNING.name())) { + if (apiScenarioThreadMap.containsKey(planScenarioId)) { + returnMap.put(apiScenarioThreadMap.get(planScenarioId), planScenarioId); + } + } + } + return returnMap; } } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/api/cache/TestPlanReportExecuteCatch.java b/backend/src/main/java/io/metersphere/api/cache/TestPlanReportExecuteCatch.java index 7971a362de..3f4c680264 100644 --- a/backend/src/main/java/io/metersphere/api/cache/TestPlanReportExecuteCatch.java +++ b/backend/src/main/java/io/metersphere/api/cache/TestPlanReportExecuteCatch.java @@ -1,6 +1,7 @@ package io.metersphere.api.cache; import io.metersphere.commons.constants.TestPlanApiExecuteStatus; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +51,11 @@ public class TestPlanReportExecuteCatch { } public synchronized static boolean containsReport(String reportId) { - return testPlanReportMap != null && testPlanReportMap.containsKey(reportId); + if(StringUtils.isEmpty(reportId)){ + return false; + }else { + return testPlanReportMap != null && testPlanReportMap.containsKey(reportId); + } } public synchronized static void updateApiTestPlanExecuteInfo(String reportId, diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/ScenarioStatus.java b/backend/src/main/java/io/metersphere/api/dto/automation/ScenarioStatus.java index d2e33af03a..9697d77940 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/ScenarioStatus.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/ScenarioStatus.java @@ -1,5 +1,7 @@ package io.metersphere.api.dto.automation; +import org.junit.internal.runners.statements.Fail; + public enum ScenarioStatus { - Saved, Success, Fail, Trash,Underway + Saved, Success, Error, Timeout, Fail, Trash, Underway } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportUtil.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportUtil.java index b6ccfc2d6b..4452c4bfd6 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportUtil.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportUtil.java @@ -61,6 +61,7 @@ public class ApiDefinitionImportUtil { if (parentModule != null) { module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1); module.setParentId(parentModule.getId()); + module.setProtocol(parentModule.getProtocol()); } else { module = apiModuleService.getNewModule(name, projectId, 1); } @@ -121,6 +122,7 @@ public class ApiDefinitionImportUtil { if (parentModule != null) { module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1); module.setParentId(parentModule.getId()); + module.setProtocol(parentModule.getProtocol()); } else { module = apiModuleService.getNewModule(name, projectId, 1); } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/JMeterScriptUtil.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/JMeterScriptUtil.java index 22b132536e..fa81109cc8 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/JMeterScriptUtil.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/JMeterScriptUtil.java @@ -84,8 +84,8 @@ public class JMeterScriptUtil { } private static void addItemHashTree(MsTestElement element, HashTree samplerHashTree, ParameterConfig config, String enviromentId) { - if (element != null && element.getEnvironmentId() == null) { - element.setEnvironmentId(enviromentId); + if (element != null) { + element.setEnvironmentId(element.getEnvironmentId() == null ? enviromentId : element.getEnvironmentId()); element.toHashTree(samplerHashTree, element.getHashTree(), config); } } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java index 7a6b50d732..309c955533 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java @@ -246,8 +246,8 @@ public class Swagger3Parser extends SwaggerAbstractParser { msResponse.setType(RequestType.HTTP); // todo 状态码要调整? msResponse.setStatusCode(new ArrayList<>()); - ApiResponse apiResponse = responses.get("200"); if (responses != null) { + ApiResponse apiResponse = responses.get("200"); if (apiResponse == null) { responses.forEach((responseCode, response) -> { parseResponseHeader(response, msResponse.getHeaders()); @@ -257,10 +257,10 @@ public class Swagger3Parser extends SwaggerAbstractParser { parseResponseHeader(apiResponse, msResponse.getHeaders()); parseResponseBody(apiResponse, msResponse.getBody()); } + responses.forEach((responseCode, response) -> { + parseResponseCode(msResponse.getStatusCode(), responseCode, response); + }); } - responses.forEach((responseCode, response) -> { - parseResponseCode(msResponse.getStatusCode(), responseCode, response); - }); return msResponse; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java index 82b6c52f23..3d79dd00b4 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java @@ -164,7 +164,7 @@ public class MsTCPSampler extends MsTestElement { }); } //根据配置将脚本放置在私有脚本之后 - JMeterScriptUtil.setScript(envConfig, samplerHashTree, GlobalScriptFilterRequest.TCP.name(), enviromentId, config, false); + JMeterScriptUtil.setScript(envConfig, samplerHashTree, GlobalScriptFilterRequest.TCP.name(), enviromentId, config, true); } private void addItemHashTree(MsTestElement element, HashTree samplerHashTree, ParameterConfig config) { diff --git a/backend/src/main/java/io/metersphere/api/exec/api/TestPlanApiExecuteService.java b/backend/src/main/java/io/metersphere/api/exec/api/TestPlanApiExecuteService.java index a1ec72b6b0..708e7bb2f3 100644 --- a/backend/src/main/java/io/metersphere/api/exec/api/TestPlanApiExecuteService.java +++ b/backend/src/main/java/io/metersphere/api/exec/api/TestPlanApiExecuteService.java @@ -136,8 +136,13 @@ public class TestPlanApiExecuteService { JmeterRunRequestDTO runRequest = new JmeterRunRequestDTO(testPlanApiCase.getId(), reportId, request.getTriggerMode(), hashTree); if (request.getConfig() != null) { runRequest.setPool(GenerateHashTreeUtil.isResourcePool(request.getConfig().getResourcePoolId())); + runRequest.setPoolId(request.getConfig().getResourcePoolId()); } runRequest.setTestPlanReportId(request.getPlanReportId()); + runRequest.setReportType(executionQueue.getReportType()); + runRequest.setTestPlanReportId(request.getPlanReportId()); + runRequest.setRunType(RunModeConstants.PARALLEL.toString()); + runRequest.setQueueId(executionQueue.getId()); jMeterService.run(runRequest); } catch (Exception e) { executeErrorList.add(testPlanApiCase.getId()); diff --git a/backend/src/main/java/io/metersphere/api/exec/queue/ExecTask.java b/backend/src/main/java/io/metersphere/api/exec/queue/ExecTask.java index 95159107db..36db9dd81c 100644 --- a/backend/src/main/java/io/metersphere/api/exec/queue/ExecTask.java +++ b/backend/src/main/java/io/metersphere/api/exec/queue/ExecTask.java @@ -1,6 +1,7 @@ package io.metersphere.api.exec.queue; import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.JmeterThreadUtils; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.dto.JmeterRunRequestDTO; import io.metersphere.utils.LoggerUtil; @@ -23,7 +24,7 @@ public class ExecTask implements Runnable { jMeterService.addQueue(request); if (request.getPool() == null || !request.getPool().isPool()) { Object res = PoolExecBlockingQueueUtil.take(request.getReportId()); - if (res == null) { + if (res == null && !JmeterThreadUtils.isRunning(request.getReportId(), request.getTestId())) { LoggerUtil.info("执行报告:【 " + request.getReportId() + " 】,资源ID【 " + request.getTestId() + " 】执行超时"); } } diff --git a/backend/src/main/java/io/metersphere/api/exec/queue/PoolExecBlockingQueueUtil.java b/backend/src/main/java/io/metersphere/api/exec/queue/PoolExecBlockingQueueUtil.java index 1b8f30dcef..d0bfecc378 100644 --- a/backend/src/main/java/io/metersphere/api/exec/queue/PoolExecBlockingQueueUtil.java +++ b/backend/src/main/java/io/metersphere/api/exec/queue/PoolExecBlockingQueueUtil.java @@ -33,7 +33,7 @@ public class PoolExecBlockingQueueUtil { if (StringUtils.isNotEmpty(key) && !queue.containsKey(key)) { BlockingQueue blockingQueue = new ArrayBlockingQueue<>(QUEUE_SIZE); queue.put(key, blockingQueue); - return blockingQueue.poll(5, TimeUnit.MINUTES); + return blockingQueue.poll(10, TimeUnit.MINUTES); } } catch (Exception e) { LogUtil.error("初始化队列失败:" + e.getMessage()); diff --git a/backend/src/main/java/io/metersphere/api/exec/utils/ApiDefinitionExecResultUtil.java b/backend/src/main/java/io/metersphere/api/exec/utils/ApiDefinitionExecResultUtil.java index 0fced9c750..dd91cc2794 100644 --- a/backend/src/main/java/io/metersphere/api/exec/utils/ApiDefinitionExecResultUtil.java +++ b/backend/src/main/java/io/metersphere/api/exec/utils/ApiDefinitionExecResultUtil.java @@ -91,7 +91,6 @@ public class ApiDefinitionExecResultUtil { apiResult.setStartTime(System.currentTimeMillis()); apiResult.setType(ApiRunMode.DEFINITION.name()); apiResult.setStatus(status); - return apiResult; } } diff --git a/backend/src/main/java/io/metersphere/api/exec/utils/GenerateHashTreeUtil.java b/backend/src/main/java/io/metersphere/api/exec/utils/GenerateHashTreeUtil.java index 8d1dd393b0..c32accc134 100644 --- a/backend/src/main/java/io/metersphere/api/exec/utils/GenerateHashTreeUtil.java +++ b/backend/src/main/java/io/metersphere/api/exec/utils/GenerateHashTreeUtil.java @@ -31,9 +31,12 @@ import java.util.Map; public class GenerateHashTreeUtil { public static MsScenario parseScenarioDefinition(String scenarioDefinition) { - MsScenario scenario = JSONObject.parseObject(scenarioDefinition, MsScenario.class); - parse(scenarioDefinition, scenario, scenario.getId(), null); - return scenario; + if(StringUtils.isNotEmpty(scenarioDefinition)) { + MsScenario scenario = JSONObject.parseObject(scenarioDefinition, MsScenario.class); + parse(scenarioDefinition, scenario, scenario.getId(), null); + return scenario; + } + return null; } public static void parse(String scenarioDefinition, MsScenario scenario, String id, String reportType) { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java b/backend/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java index e43f3dc980..0ebcb56c85 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java @@ -5,10 +5,12 @@ import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil; import io.metersphere.api.service.ApiExecutionQueueService; import io.metersphere.api.service.MsResultService; import io.metersphere.api.service.TestResultService; +import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.dto.ResultDTO; import io.metersphere.jmeter.MsExecListener; import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; import java.util.Map; @@ -18,13 +20,19 @@ public class APISingleResultListener extends MsExecListener { LoggerUtil.info("处理单条执行结果报告【" + dto.getReportId() + " 】,资源【 " + dto.getTestId() + " 】"); dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId())); CommonBeanFactory.getBean(TestResultService.class).saveResults(dto); + + // 更新报告最后接收到请求的时间 + if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCENARIO.name(), + ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), + ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { + CommonBeanFactory.getBean(TestResultService.class).editReportTime(dto); + } } @Override public void testEnded(ResultDTO dto, Map kafkaConfig) { try { LoggerUtil.info("进入TEST-END处理报告【" + dto.getReportId() + " 】整体执行完成;" + dto.getRunMode()); - // 全局并发队列 PoolExecBlockingQueueUtil.offer(dto.getReportId()); dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId())); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index c085e13a3c..ff5deffc33 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -68,9 +68,9 @@ public class JMeterService { } private void addDebugListener(String testId, HashTree testPlan) { - MsDebugListener resultCollector = new MsDebugListener(); + MsResultCollector resultCollector = new MsResultCollector(); resultCollector.setName(testId); - resultCollector.setProperty(TestElement.TEST_CLASS, MsDebugListener.class.getName()); + resultCollector.setProperty(TestElement.TEST_CLASS, MsResultCollector.class.getName()); resultCollector.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ViewResultsFullVisualizer")); resultCollector.setEnabled(true); testPlan.add(testPlan.getArray()[0], resultCollector); @@ -139,8 +139,8 @@ public class JMeterService { JvmInfoDTO jvmInfoDTO = resources.get(index); TestResourceDTO testResource = jvmInfoDTO.getTestResource(); String configuration = testResource.getConfiguration(); - request.setCorePoolSize(MessageCache.corePoolSize); NodeDTO node = JSON.parseObject(configuration, NodeDTO.class); + request.setCorePoolSize(node.getMaxConcurrency()); String nodeIp = node.getIp(); Integer port = node.getPort(); String uri = String.format(BASE_URL + "/jmeter/api/start", nodeIp, port); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JmeterThreadUtils.java b/backend/src/main/java/io/metersphere/api/jmeter/JmeterThreadUtils.java index 84eac7d47e..b088d39571 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JmeterThreadUtils.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JmeterThreadUtils.java @@ -7,9 +7,7 @@ public class JmeterThreadUtils { private final static String THREAD_SPLIT = " "; public static String stop(String name) { - ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); - int noThreads = currentGroup.activeCount(); Thread[] lstThreads = new Thread[noThreads]; currentGroup.enumerate(lstThreads); @@ -24,4 +22,19 @@ public class JmeterThreadUtils { } return threadNames.toString(); } + + public static boolean isRunning(String reportId, String testId) { + ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); + int noThreads = currentGroup.activeCount(); + Thread[] lstThreads = new Thread[noThreads]; + currentGroup.enumerate(lstThreads); + for (int i = 0; i < noThreads; i++) { + if (StringUtils.isNotEmpty(reportId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(reportId)) { + return true; + } else if (StringUtils.isNotEmpty(testId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(testId)) { + return true; + } + } + return false; + } } diff --git a/backend/src/main/java/io/metersphere/api/jmeter/MsKafkaListener.java b/backend/src/main/java/io/metersphere/api/jmeter/MsKafkaListener.java index 104b07b6e9..0ebbf92243 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/MsKafkaListener.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/MsKafkaListener.java @@ -7,6 +7,7 @@ import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil; import io.metersphere.api.service.ApiEnvironmentRunningParamService; import io.metersphere.api.service.ApiExecutionQueueService; import io.metersphere.api.service.TestResultService; +import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.config.KafkaConfig; import io.metersphere.dto.ResultDTO; @@ -34,6 +35,13 @@ public class MsKafkaListener { // 全局并发队列 PoolExecBlockingQueueUtil.offer(testResult.getReportId()); } else { + // 更新报告最后接收到请求的时间 + if (StringUtils.equalsAny(testResult.getRunMode(), ApiRunMode.SCENARIO.name(), + ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), + ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { + CommonBeanFactory.getBean(TestResultService.class).editReportTime(testResult); + } + testResultService.saveResults(testResult); } LoggerUtil.info("执行内容存储结束"); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/MsDebugListener.java b/backend/src/main/java/io/metersphere/api/jmeter/MsResultCollector.java similarity index 84% rename from backend/src/main/java/io/metersphere/api/jmeter/MsDebugListener.java rename to backend/src/main/java/io/metersphere/api/jmeter/MsResultCollector.java index 10c21bd9e4..a6add2bd63 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/MsDebugListener.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/MsResultCollector.java @@ -23,11 +23,10 @@ import io.metersphere.api.service.MsResultService; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.LogUtil; import io.metersphere.dto.RequestResult; -import io.metersphere.dto.ResultDTO; import io.metersphere.jmeter.JMeterBase; import io.metersphere.utils.JMeterVars; import io.metersphere.utils.LoggerUtil; -import io.metersphere.websocket.c.to.c.MsWebSocketClient; +import io.metersphere.websocket.c.to.c.WebSocketUtils; import io.metersphere.websocket.c.to.c.util.MsgDto; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -44,7 +43,7 @@ import java.util.Map; /** * 实时结果监听 */ -public class MsDebugListener extends AbstractListenerElement implements SampleListener, Clearable, Serializable, +public class MsResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, TestStateListener, Remoteable, NoThreadClone { private static final String ERROR_LOGGING = "MsResultCollector.error_logging"; // $NON-NLS-1$ @@ -55,11 +54,9 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi public static final String TEST_END = "MS_TEST_END"; - private MsWebSocketClient client; - @Override public Object clone() { - MsDebugListener clone = (MsDebugListener) super.clone(); + MsResultCollector clone = (MsResultCollector) super.clone(); return clone; } @@ -100,28 +97,20 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi @Override public void testEnded(String host) { LoggerUtil.debug("TestEnded " + this.getName()); - SampleResult result = new SampleResult(); - result.setResponseCode(TEST_END); - ResultDTO dto = new ResultDTO(); - dto.setReportId(this.getName()); - try { - if (client != null) { - client.close(); - } - } catch (Exception e) { - LogUtil.error(e); - } + MsgDto dto = new MsgDto(); + dto.setExecEnd(false); + dto.setContent(TEST_END); + dto.setReportId("send." + this.getName()); + dto.setToReport(this.getName()); + LoggerUtil.debug("send. " + this.getName()); + WebSocketUtils.sendMessageSingle(dto); + WebSocketUtils.onClose(this.getName()); } @Override public void testStarted(String host) { LogUtil.debug("TestStarted " + this.getName()); - try { - client = new MsWebSocketClient("ws://127.0.0.1:8081/ws/" + "send." + this.getName()); - client.connect(); - } catch (Exception e) { - LogUtil.error(e); - } + } @Override @@ -142,10 +131,9 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi dto.setReportId("send." + this.getName()); dto.setToReport(this.getName()); LoggerUtil.debug("send. " + this.getName()); - if (client != null) { - client.send(JSON.toJSONString(dto)); - } + WebSocketUtils.sendMessageSingle(dto); } catch (Exception ex) { + LoggerUtil.error("消息推送失败:" + ex.getMessage()); } } @@ -179,9 +167,7 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi dto.setReportId("send." + this.getName()); dto.setToReport(this.getName()); LoggerUtil.debug("send. " + this.getName()); - if (client != null) { - client.send(JSON.toJSONString(dto)); - } + WebSocketUtils.sendMessageSingle(dto); } } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java index b4607c0f08..334c80965c 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java @@ -116,8 +116,8 @@ public class ApiDefinitionExecResultService { .operator(SessionUtils.getUserId()) .context(context) .subject("接口用例通知") - .successMailTemplate("api/CaseResult") - .failedMailTemplate("api/CaseResult") + .successMailTemplate("api/CaseResultSuccess") + .failedMailTemplate("api/CaseResultFailed") .paramMap(paramMap) .event(event) .build(); @@ -187,8 +187,6 @@ public class ApiDefinitionExecResultService { * 定时任务时,userID要改为定时任务中的用户 */ public void saveApiResultByScheduleTask(List requestResults, ResultDTO dto) { - Map apiIdResultMap = new HashMap<>(); - Map caseReportMap = new HashMap<>(); boolean isFirst = true; int countExpectProcessResultCount = 0; if (CollectionUtils.isNotEmpty(requestResults)) { @@ -201,7 +199,7 @@ public class ApiDefinitionExecResultService { for (RequestResult item : requestResults) { if (!StringUtils.startsWithAny(item.getName(), "PRE_PROCESSOR_ENV_", "POST_PROCESSOR_ENV_")) { - ApiDefinitionExecResult saveResult = this.save(item, dto.getReportId(), dto.getConsole(), countExpectProcessResultCount, dto.getRunMode(), dto.getTestId(), isFirst); + this.save(item, dto.getReportId(), dto.getConsole(), countExpectProcessResultCount, dto.getRunMode(), dto.getTestId(), isFirst); String status = item.isSuccess() ? "success" : "error"; if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) { TestPlanApiCase apiCase = testPlanApiCaseService.getById(dto.getTestId()); @@ -214,19 +212,19 @@ public class ApiDefinitionExecResultService { testPlanApiCaseService.setExecResult(dto.getTestId(), status, item.getStartTime()); testCaseReviewApiCaseService.setExecResult(dto.getTestId(), status, item.getStartTime()); } - if (StringUtils.isNotEmpty(dto.getTestId())) { - apiIdResultMap.put(dto.getTestId(), item.isSuccess() ? TestPlanApiExecuteStatus.SUCCESS.name() : TestPlanApiExecuteStatus.FAILD.name()); - } - //更新报告ID - caseReportMap.put(dto.getTestId(), saveResult.getId()); isFirst = false; } } } updateTestCaseStates(dto.getTestId()); + Map apiIdResultMap = new HashMap<>(); + long errorSize = requestResults.stream().filter(requestResult -> requestResult.getError() > 0).count(); + String status = errorSize > 0 || requestResults.isEmpty() ? TestPlanApiExecuteStatus.FAILD.name() : TestPlanApiExecuteStatus.SUCCESS.name(); + if (StringUtils.isNotEmpty(dto.getReportId())) { + apiIdResultMap.put(dto.getReportId(), status); + } testPlanLog.info("TestPlanReportId[" + dto.getTestPlanReportId() + "] APICASE OVER. API CASE STATUS:" + JSONObject.toJSONString(apiIdResultMap)); TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(dto.getTestPlanReportId(), apiIdResultMap, null, null); - TestPlanReportExecuteCatch.updateTestPlanReport(dto.getTestPlanReportId(), caseReportMap, null); } /** @@ -348,13 +346,13 @@ public class ApiDefinitionExecResultService { ApiDefinitionExecResult prevResult = extApiDefinitionExecResultMapper.selectMaxResultByResourceIdAndType(item.getName(), type); if (prevResult != null) { prevResult.setContent(null); - apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(prevResult); + apiDefinitionExecResultMapper.updateByPrimaryKeySelective(prevResult); } if (StringUtils.isNotEmpty(saveResult.getTriggerMode()) && saveResult.getTriggerMode().equals("CASE")) { saveResult.setTriggerMode(TriggerMode.MANUAL.name()); } - apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(saveResult); + apiDefinitionExecResultMapper.updateByPrimaryKeySelective(saveResult); return saveResult; } return null; diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java index d6bf975adc..415a3a916a 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java @@ -22,6 +22,7 @@ import io.metersphere.api.dto.scenario.request.RequestType; import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult; import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest; import io.metersphere.api.exec.api.ApiExecuteService; +import io.metersphere.api.exec.utils.ApiDefinitionExecResultUtil; import io.metersphere.api.parse.ApiImportParser; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; @@ -102,16 +103,12 @@ public class ApiDefinitionService { @Resource private ApiTestCaseMapper apiTestCaseMapper; @Resource - private ApiTestEnvironmentService environmentService; - @Resource private EsbApiParamService esbApiParamService; @Resource private TcpApiParamService tcpApiParamService; @Resource private ApiModuleMapper apiModuleMapper; @Resource - private SystemParameterService systemParameterService; - @Resource private TestPlanMapper testPlanMapper; @Resource private NoticeSendService noticeSendService; @@ -855,6 +852,15 @@ public class ApiDefinitionService { * @return */ public MsExecResponseDTO run(RunDefinitionRequest request, List bodyFiles) { + if (!request.isDebug()) { + String testId = request.getTestElement() != null && + CollectionUtils.isNotEmpty(request.getTestElement().getHashTree()) && + CollectionUtils.isNotEmpty(request.getTestElement().getHashTree().get(0).getHashTree()) ? + request.getTestElement().getHashTree().get(0).getHashTree().get(0).getName() : request.getId(); + ApiDefinitionExecResult result = ApiDefinitionExecResultUtil.add(testId, APITestStatus.Running.name(), request.getId()); + result.setTriggerMode(TriggerMode.MANUAL.name()); + apiDefinitionExecResultMapper.insert(result); + } return apiExecuteService.debug(request, bodyFiles); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiExecutionQueueService.java b/backend/src/main/java/io/metersphere/api/service/ApiExecutionQueueService.java index d53877f287..652c3209e0 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiExecutionQueueService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiExecutionQueueService.java @@ -2,6 +2,7 @@ package io.metersphere.api.service; import com.alibaba.fastjson.JSON; import io.metersphere.api.dto.RunModeDataDTO; +import io.metersphere.api.dto.automation.ScenarioStatus; import io.metersphere.api.exec.queue.DBTestQueue; import io.metersphere.api.exec.scenario.ApiScenarioSerialService; import io.metersphere.base.domain.*; @@ -179,9 +180,8 @@ public class ApiExecutionQueueService { public void timeOut() { final int SECOND_MILLIS = 1000; final int MINUTE_MILLIS = 60 * SECOND_MILLIS; - long now = System.currentTimeMillis(); - // 八分钟前的数据 - now = now - 8 * MINUTE_MILLIS; + // 二十分钟前的超时报告 + final long now = System.currentTimeMillis() - (20 * MINUTE_MILLIS); ApiExecutionQueueDetailExample example = new ApiExecutionQueueDetailExample(); example.createCriteria().andCreateTimeLessThan(now); List queueDetails = executionQueueDetailMapper.selectByExample(example); @@ -190,14 +190,14 @@ public class ApiExecutionQueueService { queueDetails.forEach(item -> { if (StringUtils.equalsAny(item.getType(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(item.getReportId()); - if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name())) { - report.setStatus("timeout"); + if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name()) && report.getUpdateTime() < now) { + report.setStatus(ScenarioStatus.Timeout.name()); apiScenarioReportMapper.updateByPrimaryKeySelective(report); } } else { ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByPrimaryKey(item.getReportId()); if (result != null && StringUtils.equalsAny(result.getStatus(), TestPlanReportStatus.RUNNING.name())) { - result.setStatus("timeout"); + result.setStatus(ScenarioStatus.Timeout.name()); apiDefinitionExecResultMapper.updateByPrimaryKeySelective(result); } } @@ -211,8 +211,8 @@ public class ApiExecutionQueueService { if (CollectionUtils.isNotEmpty(executionQueues)) { executionQueues.forEach(item -> { ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(item.getReportId()); - if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name())) { - report.setStatus("timeout"); + if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name()) && report.getUpdateTime() < now) { + report.setStatus(ScenarioStatus.Timeout.name()); apiScenarioReportMapper.updateByPrimaryKeySelective(report); } }); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java b/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java index af249440e9..a101e84404 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java @@ -87,6 +87,7 @@ public class ApiScenarioReportService { apiScenarioReportResultService.save(dto.getReportId(), requestResults); } + public ApiScenarioReport testEnded(ResultDTO dto) { if (!StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.toString())) { // 更新控制台信息 @@ -96,6 +97,15 @@ public class ApiScenarioReportService { example.createCriteria().andReportIdEqualTo(dto.getReportId()); List requestResults = apiScenarioReportResultMapper.selectByExample(example); + if (StringUtils.isNotEmpty(dto.getTestPlanReportId())) { + String status = getStatus(requestResults, dto); + Map reportMap = new HashMap() {{ + this.put(dto.getReportId(), status); + }}; + testPlanLog.info("TestPlanReportId" + JSONArray.toJSONString(dto.getReportId()) + " EXECUTE OVER. SCENARIO STATUS : " + JSONObject.toJSONString(reportMap)); + TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(dto.getTestPlanReportId(), null, reportMap, null); + } + ApiScenarioReport scenarioReport; if (StringUtils.equals(dto.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) { scenarioReport = updatePlanCase(requestResults, dto); @@ -226,7 +236,7 @@ public class ApiScenarioReportService { } public ApiScenarioReport updatePlanCase(List requestResults, ResultDTO dto) { - long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Error")).count(); + long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count(); TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId()); if (testPlanApiScenario != null) { if (errorSize > 0) { @@ -234,7 +244,7 @@ public class ApiScenarioReportService { } else { testPlanApiScenario.setLastResult(ScenarioStatus.Success.name()); } - long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count(); + long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Success.name())).count(); String passRate = new DecimalFormat("0%").format((float) successSize / requestResults.size()); testPlanApiScenario.setPassRate(passRate); @@ -245,7 +255,7 @@ public class ApiScenarioReportService { // 更新场景状态 ApiScenario scenario = apiScenarioMapper.selectByPrimaryKey(testPlanApiScenario.getApiScenarioId()); if (scenario != null) { - scenario.setLastResult(errorSize > 0 ? "Fail" : "Success"); + scenario.setLastResult(errorSize > 0 ? "Fail" : ScenarioStatus.Success.name()); scenario.setPassRate(passRate); scenario.setReportId(dto.getReportId()); int executeTimes = 0; @@ -256,11 +266,7 @@ public class ApiScenarioReportService { apiScenarioMapper.updateByPrimaryKey(scenario); } } - String status = errorSize > 0 || requestResults.isEmpty() ? "Error" : "Success"; - if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) { - LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时"); - status = "Timeout"; - } + String status = getStatus(requestResults, dto); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); return report; } @@ -269,15 +275,8 @@ public class ApiScenarioReportService { List testPlanReportIdList = new ArrayList<>(); StringBuilder scenarioNames = new StringBuilder(); - Map scenarioAndErrorMap = new HashMap<>(); - Map planScenarioReportMap = new HashMap<>(); - long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Error")).count(); - - String status = errorSize > 0 || requestResults.isEmpty() ? "Error" : "Success"; - if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) { - LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时"); - status = "Timeout"; - } + long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count(); + String status = getStatus(requestResults, dto); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); if (report != null) { if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) { @@ -288,15 +287,12 @@ public class ApiScenarioReportService { report.setScenarioId(testPlanApiScenario.getApiScenarioId()); report.setEndTime(System.currentTimeMillis()); apiScenarioReportMapper.updateByPrimaryKeySelective(report); - planScenarioReportMap.put(dto.getTestId(), report.getId()); if (errorSize > 0) { - scenarioAndErrorMap.put(testPlanApiScenario.getId(), TestPlanApiExecuteStatus.FAILD.name()); testPlanApiScenario.setLastResult(ScenarioStatus.Fail.name()); } else { - scenarioAndErrorMap.put(testPlanApiScenario.getId(), TestPlanApiExecuteStatus.SUCCESS.name()); testPlanApiScenario.setLastResult(ScenarioStatus.Success.name()); } - long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count(); + long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Success.name())).count(); String passRate = new DecimalFormat("0%").format((float) successSize / requestResults.size()); testPlanApiScenario.setPassRate(passRate); @@ -312,7 +308,7 @@ public class ApiScenarioReportService { if (errorSize > 0) { scenario.setLastResult("Fail"); } else { - scenario.setLastResult("Success"); + scenario.setLastResult(ScenarioStatus.Success.name()); } scenario.setPassRate(passRate); scenario.setReportId(report.getId()); @@ -325,11 +321,6 @@ public class ApiScenarioReportService { apiScenarioMapper.updateByPrimaryKey(scenario); } } - testPlanLog.info("TestPlanReportId" + JSONArray.toJSONString(testPlanReportIdList) + " EXECUTE OVER. SCENARIO STATUS : " + JSONObject.toJSONString(scenarioAndErrorMap)); - for (String item : testPlanReportIdList) { - TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(item, null, scenarioAndErrorMap, null); - TestPlanReportExecuteCatch.updateTestPlanReport(item, null, planScenarioReportMap); - } } return report; } @@ -339,9 +330,9 @@ public class ApiScenarioReportService { if (report != null) { // 更新场景状态 ApiScenarioReportResultExample example = new ApiScenarioReportResultExample(); - example.createCriteria().andReportIdEqualTo(reportId).andStatusEqualTo("Error"); + example.createCriteria().andReportIdEqualTo(reportId).andStatusEqualTo(ScenarioStatus.Error.name()); long size = apiScenarioReportResultMapper.countByExample(example); - report.setStatus(size > 0 ? "Error" : "Success"); + report.setStatus(size > 0 ? ScenarioStatus.Error.name() : ScenarioStatus.Success.name()); report.setEndTime(System.currentTimeMillis()); // 更新控制台信息 apiScenarioReportStructureService.update(reportId, resultService.getJmeterLogger(reportId)); @@ -351,13 +342,10 @@ public class ApiScenarioReportService { } public ApiScenarioReport updateScenario(List requestResults, ResultDTO dto) { - long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Error")).count(); + long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count(); // 更新报告状态 - String status = errorSize > 0 || requestResults.isEmpty() ? "Error" : "Success"; - if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) { - LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时"); - status = "Timeout"; - } + String status = getStatus(requestResults, dto); + ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); // 更新场景状态 ApiScenarioWithBLOBs scenario = apiScenarioMapper.selectByPrimaryKey(dto.getTestId()); @@ -365,8 +353,8 @@ public class ApiScenarioReportService { scenario = apiScenarioMapper.selectByPrimaryKey(report.getScenarioId()); } if (scenario != null) { - scenario.setLastResult(errorSize > 0 ? "Fail" : "Success"); - long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count(); + scenario.setLastResult(errorSize > 0 ? "Fail" : ScenarioStatus.Success.name()); + long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Success.name())).count(); scenario.setPassRate(new DecimalFormat("0%").format((float) successSize / requestResults.size())); scenario.setReportId(dto.getReportId()); int executeTimes = 0; @@ -417,7 +405,7 @@ public class ApiScenarioReportService { String event; String status; - if (StringUtils.equals(scenario.getLastResult(), "Success")) { + if (StringUtils.equals(scenario.getLastResult(), ScenarioStatus.Success.name())) { event = NoticeConstants.Event.EXECUTE_SUCCESSFUL; status = "成功"; } else { @@ -437,8 +425,8 @@ public class ApiScenarioReportService { .operator(userId) .context(context) .subject("接口自动化通知") - .successMailTemplate("api/ScenarioResult") - .failedMailTemplate("api/ScenarioResult") + .successMailTemplate("api/ScenarioResultSuccess") + .failedMailTemplate("api/ScenarioResultFailed") .paramMap(paramMap) .event(event) .build(); @@ -707,4 +695,14 @@ public class ApiScenarioReportService { report.setScenarioId(scenarioId); return report; } + + private String getStatus(List requestResults, ResultDTO dto) { + long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count(); + String status = errorSize > 0 || requestResults.isEmpty() ? ScenarioStatus.Error.name() : ScenarioStatus.Success.name(); + if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) { + LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时"); + status = ScenarioStatus.Timeout.name(); + } + return status; + } } diff --git a/backend/src/main/java/io/metersphere/api/service/TestResultService.java b/backend/src/main/java/io/metersphere/api/service/TestResultService.java index 8bf1da1f51..f65e71a03a 100644 --- a/backend/src/main/java/io/metersphere/api/service/TestResultService.java +++ b/backend/src/main/java/io/metersphere/api/service/TestResultService.java @@ -3,6 +3,7 @@ package io.metersphere.api.service; import io.metersphere.api.dto.automation.ApiTestReportVariable; import io.metersphere.api.jmeter.ExecutedHandleSingleton; import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.ApiScenarioReportMapper; import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.constants.NoticeConstants; import io.metersphere.commons.constants.ReportTriggerMode; @@ -47,6 +48,8 @@ public class TestResultService { private TestPlanTestCaseService testPlanTestCaseService; @Resource private ApiTestCaseService apiTestCaseService; + @Resource + private ApiScenarioReportMapper apiScenarioReportMapper; public void saveResults(ResultDTO dto) { // 处理环境 @@ -70,6 +73,14 @@ public class TestResultService { updateTestCaseStates(requestResults, dto.getRunMode()); } + public void editReportTime(ResultDTO dto) { + ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(dto.getReportId()); + if (report != null) { + report.setUpdateTime(System.currentTimeMillis()); + apiScenarioReportMapper.updateByPrimaryKey(report); + } + } + public void testEnded(ResultDTO dto) { if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { ApiScenarioReport scenarioReport = apiScenarioReportService.testEnded(dto); diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssuesMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssuesMapper.xml index c5a69c1ef2..15a70c9dc2 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssuesMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtIssuesMapper.xml @@ -114,6 +114,9 @@ and issues.platform = #{request.platform} + + and issues.id = #{request.id} + diff --git a/backend/src/main/java/io/metersphere/job/sechedule/TestPlanTestJob.java b/backend/src/main/java/io/metersphere/job/sechedule/TestPlanTestJob.java index 1f03186e9c..589fbf9812 100644 --- a/backend/src/main/java/io/metersphere/job/sechedule/TestPlanTestJob.java +++ b/backend/src/main/java/io/metersphere/job/sechedule/TestPlanTestJob.java @@ -3,6 +3,7 @@ package io.metersphere.job.sechedule; import io.metersphere.commons.constants.ReportTriggerMode; import io.metersphere.commons.constants.ScheduleGroup; import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; import io.metersphere.track.service.TestPlanService; import org.quartz.*; @@ -16,25 +17,10 @@ import org.quartz.*; public class TestPlanTestJob extends MsScheduleJob { private String projectID; - - // private PerformanceTestService performanceTestService; -// private TestPlanScenarioCaseService testPlanScenarioCaseService; -// private TestPlanApiCaseService testPlanApiCaseService; -// private ApiTestCaseService apiTestCaseService; -// private TestPlanReportService testPlanReportService; -// private TestPlanLoadCaseService testPlanLoadCaseService; private TestPlanService testPlanService; public TestPlanTestJob() { -// this.performanceTestService = CommonBeanFactory.getBean(PerformanceTestService.class); -// this.testPlanScenarioCaseService = CommonBeanFactory.getBean(TestPlanScenarioCaseService.class); -// this.testPlanApiCaseService = CommonBeanFactory.getBean(TestPlanApiCaseService.class); -// this.apiTestCaseService = CommonBeanFactory.getBean(ApiTestCaseService.class); -// this.testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class); -// this.testPlanLoadCaseService = CommonBeanFactory.getBean(TestPlanLoadCaseService.class); this.testPlanService = CommonBeanFactory.getBean(TestPlanService.class); - - } /** @@ -63,7 +49,17 @@ public class TestPlanTestJob extends MsScheduleJob { JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); String config = jobDataMap.getString("config"); - testPlanService.run(this.resourceId, this.projectID, this.userId, ReportTriggerMode.SCHEDULE.name(),config); + String runResourceId = this.resourceId; + String runProjectId = this.projectID; + String runUserId = this.userId; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + LogUtil.info("Start test_plan_scehdule. test_plan_id:" + runResourceId); + testPlanService.run(runResourceId, runProjectId, runUserId, ReportTriggerMode.SCHEDULE.name(),config); + } + }); + thread.start(); } public static JobKey getJobKey(String testId) { diff --git a/backend/src/main/java/io/metersphere/listener/AppStartListener.java b/backend/src/main/java/io/metersphere/listener/AppStartListener.java index 7385d38b26..e6289d31f3 100644 --- a/backend/src/main/java/io/metersphere/listener/AppStartListener.java +++ b/backend/src/main/java/io/metersphere/listener/AppStartListener.java @@ -68,6 +68,14 @@ public class AppStartListener implements ApplicationListener originalValueArray = JSON.parseArray(originalValue.toString(), String.class); + String oldTags = null; + if (originalValue != null && !StringUtils.equals("null", originalValue.toString())) { + List originalValueArray = JSON.parseArray(originalValue.toString(), String.class); + Collections.sort(originalValueArray); + Object originalObject = JSON.toJSON(originalValueArray); + oldTags = ApiDefinitionDiffUtil.JSON_START + ((originalColumns.get(i) != null && originalObject != null) ? originalObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END; + } List newValueArray = JSON.parseArray(newValue.toString(), String.class); - Collections.sort(originalValueArray); Collections.sort(newValueArray); - Object originalObject = JSON.toJSON(originalValueArray); Object newObject = JSON.toJSON(newValueArray); - String oldTags = ApiDefinitionDiffUtil.JSON_START + ((originalColumns.get(i) != null && originalObject != null) ? originalObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END; String newTags = ApiDefinitionDiffUtil.JSON_START + ((newColumns.get(i) != null && newObject != null) ? newObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END; - String diffStr = diff.diff(oldTags, newTags); - String diffValue = diff.apply(newTags, diffStr); + String diffValue; + if (oldTags != null) { + String diffStr = diff.diff(oldTags, newTags); + diffValue = diff.apply(newTags, diffStr); + } else { + int indexAdd = newTags.indexOf("["); + String substring = newTags.substring(0, indexAdd + 2); + String substring1 = newTags.substring(indexAdd + 2); + diffValue = substring + "++" + substring1; + } column.setDiffValue(diffValue); } // 深度对比 diff --git a/backend/src/main/java/io/metersphere/notice/sender/AbstractNoticeSender.java b/backend/src/main/java/io/metersphere/notice/sender/AbstractNoticeSender.java index 472702b181..7ded94b36b 100644 --- a/backend/src/main/java/io/metersphere/notice/sender/AbstractNoticeSender.java +++ b/backend/src/main/java/io/metersphere/notice/sender/AbstractNoticeSender.java @@ -19,11 +19,10 @@ import io.metersphere.track.service.TestCaseReviewService; import io.metersphere.track.service.TestCaseService; import io.metersphere.track.service.TestPlanService; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.commons.text.StringSubstitutor; import org.springframework.context.annotation.Lazy; import javax.annotation.Resource; @@ -128,29 +127,31 @@ public abstract class AbstractNoticeSender implements NoticeSender { } protected String getContent(String template, Map context) { - if (MapUtils.isNotEmpty(context)) { - for (String k : context.keySet()) { - if (context.get(k) != null) { - String value = handleTime(k, context); - template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", value); - } else { - template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", ""); - } + // 处理 null + context.forEach((k, v) -> { + if (v == null) { + context.put(k, ""); } - } - return template; + }); + // 处理时间格式的数据 + handleTime(context); + StringSubstitutor sub = new StringSubstitutor(context); + return sub.replace(template); } - private String handleTime(String k, Map context) { - String value = context.get(k).toString(); - if (StringUtils.endsWithIgnoreCase(k, "Time")) { - try { - long time = Long.parseLong(value); - value = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss"); - } catch (Exception ignore) { + private void handleTime(Map context) { + context.forEach((k, v) -> { + if (StringUtils.endsWithIgnoreCase(k, "Time")) { + try { + String value = v.toString(); + long time = Long.parseLong(value); + v = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss"); + context.put(k, v); + } catch (Exception ignore) { + } } - } - return value; + }); + } protected List getUserDetails(List userIds) { diff --git a/backend/src/main/java/io/metersphere/notice/sender/SendNoticeAspect.java b/backend/src/main/java/io/metersphere/notice/sender/SendNoticeAspect.java index dd62d85463..d69a4ce34a 100644 --- a/backend/src/main/java/io/metersphere/notice/sender/SendNoticeAspect.java +++ b/backend/src/main/java/io/metersphere/notice/sender/SendNoticeAspect.java @@ -35,6 +35,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static com.alibaba.fastjson.serializer.SerializerFeature.WriteMapNullValue; + @Aspect @Component public class SendNoticeAspect { @@ -81,7 +83,7 @@ public class SendNoticeAspect { Expression titleExp = parser.parseExpression(target); Object v = titleExp.getValue(context, Object.class); Map memberValues = (Map) value.get(invocationHandler); - memberValues.put("source", JSON.toJSONString(v)); + memberValues.put("source", JSON.toJSONString(v, WriteMapNullValue)); } } catch (Exception e) { LogUtil.error(e.getMessage(), e); @@ -119,7 +121,7 @@ public class SendNoticeAspect { Object v = titleExp.getValue(context, Object.class); if (v != null) { Map memberValues = (Map) value.get(invocationHandler); - memberValues.put("source", JSON.toJSONString(v)); + memberValues.put("source", JSON.toJSONString(v, WriteMapNullValue)); } } diff --git a/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java b/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java index 2c17313cea..58e28bb79d 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java +++ b/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java @@ -3,7 +3,6 @@ package io.metersphere.performance.engine; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import io.metersphere.api.dto.RunRequest; import io.metersphere.base.domain.LoadTestReportWithBLOBs; import io.metersphere.base.domain.TestResource; import io.metersphere.base.domain.TestResourcePool; @@ -13,6 +12,7 @@ import io.metersphere.commons.constants.ResourceStatusEnum; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.config.JmeterProperties; +import io.metersphere.dto.JmeterRunRequestDTO; import io.metersphere.performance.service.PerformanceTestService; import io.metersphere.service.TestResourcePoolService; import io.metersphere.service.TestResourceService; @@ -42,7 +42,7 @@ public abstract class AbstractEngine implements Engine { GC_ALGO = CommonBeanFactory.getBean(JmeterProperties.class).getGcAlgo(); } - protected void initApiConfig(RunRequest runRequest) { + protected void initApiConfig(JmeterRunRequestDTO runRequest) { String resourcePoolId = runRequest.getPoolId(); resourcePool = testResourcePoolService.getResourcePool(resourcePoolId); if (resourcePool == null || StringUtils.equals(resourcePool.getStatus(), ResourceStatusEnum.DELETE.name())) { diff --git a/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java b/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java index c0f4f11cca..73e98af37b 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java +++ b/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java @@ -300,12 +300,20 @@ public class EngineFactory { rootDocument = docBuilder.parse(inputSource); Element jmeterTestPlan = rootDocument.getDocumentElement(); NodeList childNodes = jmeterTestPlan.getChildNodes(); + + outer: for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node instanceof Element) { // jmeterTestPlan的子元素肯定是 - hashTree = (Element) node; - break; + NodeList childNodes1 = node.getChildNodes(); + for (int j = 0; j < childNodes1.getLength(); j++) { + Node item = childNodes1.item(j); + if (StringUtils.equalsIgnoreCase("hashTree", item.getNodeName())) { + hashTree = (Element) node; + break outer; + } + } } } } else { @@ -320,9 +328,19 @@ public class EngineFactory { NodeList secondChildNodes = secondHashTree.getChildNodes(); for (int j = 0; j < secondChildNodes.getLength(); j++) { Node item = secondChildNodes.item(j); - Node newNode = item.cloneNode(true); - rootDocument.adoptNode(newNode); - hashTree.appendChild(newNode); + if (StringUtils.equalsIgnoreCase("TestPlan", item.getNodeName())) { + continue; + } + if (StringUtils.equalsIgnoreCase("hashTree", item.getNodeName())) { + NodeList itemChildNodes = item.getChildNodes(); + for (int k = 0; k < itemChildNodes.getLength(); k++) { + Node item1 = itemChildNodes.item(k); + Node newNode = item1.cloneNode(true); + rootDocument.adoptNode(newNode); + hashTree.appendChild(newNode); + } + } + } } } diff --git a/backend/src/main/java/io/metersphere/service/EnvironmentGroupService.java b/backend/src/main/java/io/metersphere/service/EnvironmentGroupService.java index 28e064ff2f..9ddad71015 100644 --- a/backend/src/main/java/io/metersphere/service/EnvironmentGroupService.java +++ b/backend/src/main/java/io/metersphere/service/EnvironmentGroupService.java @@ -11,6 +11,7 @@ import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.controller.request.EnvironmentGroupRequest; import io.metersphere.dto.EnvironmentGroupDTO; +import io.metersphere.i18n.Translator; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -161,7 +162,7 @@ public class EnvironmentGroupService { private void checkEnvironmentGroup(EnvironmentGroupRequest request) { String name = request.getName(); if (StringUtils.isBlank(name)) { - MSException.throwException("environment group name is null."); + MSException.throwException(Translator.get("null_environment_group_name")); } EnvironmentGroupExample environmentGroupExample = new EnvironmentGroupExample(); @@ -173,7 +174,7 @@ public class EnvironmentGroupService { } if (environmentGroupMapper.countByExample(environmentGroupExample) > 0) { - MSException.throwException("环境组名称 " + request.getName() + " 已存在!"); + MSException.throwException(Translator.get("environment_group_name")+request.getName()+Translator.get("environment_group_exist")); } } diff --git a/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java b/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java index 64092d8d73..a9fe5c6d0e 100644 --- a/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java +++ b/backend/src/main/java/io/metersphere/track/controller/IssueCommentController.java @@ -29,7 +29,7 @@ public class IssueCommentController { @PostMapping("/save") @RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT) @SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#targetClass.get(#request.issuesId)", targetClass = IssuesService.class, - event = NoticeConstants.Event.COMMENT, mailTemplate = "track/IssuesCommentUpdate", subject = "缺陷评论更新通知") + event = NoticeConstants.Event.COMMENT, mailTemplate = "track/IssuesCommentUpdate", subject = "缺陷") public IssueComment saveComment(@RequestBody IssuesRelevanceRequest request) { request.setId(UUID.randomUUID().toString()); return issueCommentService.saveComment(request); diff --git a/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java b/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java index d650310393..a6b0db84de 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java @@ -133,7 +133,7 @@ public class TestPlanController { @PostMapping("/delete/{testPlanId}") @RequiresPermissions(PermissionConstants.PROJECT_TRACK_PLAN_READ_DELETE) @MsAuditLog(module = "track_test_plan", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#testPlanId)", msClass = TestPlanService.class) - @SendNotice(taskType = NoticeConstants.TaskType.TEST_PLAN_TASK, target = "#targetClass.getTestPlan(#testPlanId)", targetClass = TestPlanService.class, + @SendNotice(taskType = NoticeConstants.TaskType.TEST_PLAN_TASK, target = "#targetClass.get(#testPlanId)", targetClass = TestPlanService.class, event = NoticeConstants.Event.DELETE, mailTemplate = "track/TestPlanDelete", subject = "测试计划通知") public int deleteTestPlan(@PathVariable String testPlanId) { checkPermissionService.checkTestPlanOwner(testPlanId); diff --git a/backend/src/main/java/io/metersphere/track/dto/TestPlanReportExecuteCheckResultDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestPlanReportExecuteCheckResultDTO.java new file mode 100644 index 0000000000..6b88167b9f --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/dto/TestPlanReportExecuteCheckResultDTO.java @@ -0,0 +1,11 @@ +package io.metersphere.track.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TestPlanReportExecuteCheckResultDTO { + private boolean isTimeOut; + private boolean isFinishedCaseChanged; +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java index d98d399b27..d6a6ebc3f9 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java @@ -81,7 +81,9 @@ public class TestPlanReportService { @Resource ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper; @Resource - ApiTestCaseMapper apiTestCaseMapper; + ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper; + @Resource + ExtApiScenarioReportMapper extApiScenarioReportMapper; @Resource LoadTestReportMapper loadTestReportMapper; @Resource @@ -551,8 +553,6 @@ public class TestPlanReportService { boolean scenarioIsOk = executeInfo.isScenarioAllExecuted(); boolean performanceIsOk = executeInfo.isLoadCaseAllExecuted(); - testPlanLog.info("ReportId[" + testPlanReport.getId() + "] count over. Testplan Execute Result: Api is over ->" + apiCaseIsOk + "; scenario is over ->" + scenarioIsOk + "; performance is over ->" + performanceIsOk); - if (apiCaseIsOk) { testPlanReport.setIsApiCaseExecuting(false); } @@ -1075,12 +1075,16 @@ public class TestPlanReportService { } public void countReport(String planReportId) { - boolean isTimeOut = this.checkTestPlanReportIsTimeOut(planReportId); - if (isTimeOut) { + TestPlanReportExecuteCheckResultDTO checkResult = this.checkTestPlanReportIsTimeOut(planReportId); + testPlanLog.info("Check PlanReport:" + planReportId + "; result: "+ JSON.toJSONString(checkResult)); + if (checkResult.isTimeOut()) { //判断是否超时。超时时强行停止任务 TestPlanReportExecuteCatch.finishAllTask(planReportId); + checkResult.setFinishedCaseChanged(true); + } + if(checkResult.isFinishedCaseChanged()){ + this.updateExecuteApis(planReportId); } - this.updateExecuteApis(planReportId); } public TestPlanSimpleReportDTO getReport(String reportId) { @@ -1216,19 +1220,61 @@ public class TestPlanReportService { testPlanReportContentMapper.updateByExampleSelective(bloBs,example); } - private boolean checkTestPlanReportIsTimeOut(String planReportId) { + private TestPlanReportExecuteCheckResultDTO checkTestPlanReportIsTimeOut(String planReportId) { + //同步数据库更新状态信息 + try { + this.syncReportStatus(planReportId); + } catch (Exception e) { + LogUtil.info("联动数据库同步执行状态失败! " + e.getMessage()); + LogUtil.error(e); + } TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId); - int unFinishNum = executeInfo.countUnFinishedNum(); - if (unFinishNum > 0) { - //20分钟没有案例执行结果更新,则定位超时 - long lastCountTime = executeInfo.getLastFinishedNumCountTime(); - long nowTime = System.currentTimeMillis(); - testPlanLog.info("ReportId: ["+planReportId+"]; timeCount:"+ (nowTime - lastCountTime)); - if (nowTime - lastCountTime > 1200000) { - return true; + TestPlanReportExecuteCheckResultDTO checkResult = executeInfo.countUnFinishedNum(); + return checkResult; + } + + private void syncReportStatus(String planReportId) { + if (TestPlanReportExecuteCatch.containsReport(planReportId)) { + TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId); + if (executeInfo != null) { + //同步接口案例结果 + Map updateCaseStatusMap = new HashMap<>(); + Map apiCaseReportMap = executeInfo.getRunningApiCaseReportMap(); + if (MapUtils.isNotEmpty(apiCaseReportMap)) { + List execList = extApiDefinitionExecResultMapper.selectStatusByIdList(apiCaseReportMap.keySet()); + for (ApiDefinitionExecResult report : execList) { + String reportId = report.getId(); + String status = report.getStatus(); + if (!StringUtils.equalsAnyIgnoreCase(status, "Running", "Waiting")) { + String planCaseId = apiCaseReportMap.get(reportId); + if (StringUtils.isNotEmpty(planCaseId)) { + updateCaseStatusMap.put(planCaseId, status); + } + } + } + } + //同步场景结果 + Map updateScenarioStatusMap = new HashMap<>(); + Map scenarioReportMap = executeInfo.getRunningScenarioReportMap(); + if (MapUtils.isNotEmpty(scenarioReportMap)) { + List reportList = extApiScenarioReportMapper.selectStatusByIds(scenarioReportMap.keySet()); + for (ApiScenarioReport report : reportList) { + String reportId = report.getId(); + String status = report.getStatus(); + if (!StringUtils.equalsAnyIgnoreCase(status, "Running", "Waiting")) { + String planScenarioId = scenarioReportMap.get(reportId); + if (StringUtils.isNotEmpty(planScenarioId)) { + updateScenarioStatusMap.put(planScenarioId, status); + } + } + } + } + testPlanLog.info("ReportID:"+planReportId+" 本次数据库同步,案例ID:"+JSON.toJSONString(apiCaseReportMap.keySet())+";场景ID:"+JSON.toJSONString(scenarioReportMap.keySet())+"; 同步结果,案例:"+JSON.toJSONString(updateCaseStatusMap)+";场景:"+JSON.toJSONString(updateScenarioStatusMap)); + TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(planReportId, updateCaseStatusMap, updateScenarioStatusMap, null); + }else { + testPlanLog.info("同步数据库查询执行信息失败! 报告ID在缓存中未找到!"+planReportId); } } - return false; } private void finishTestPlanReport(String planReportId) { @@ -1239,5 +1285,4 @@ public class TestPlanReportService { } TestPlanReportExecuteCatch.remove(planReportId); } - } diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java index 01c62a9203..22f654d33a 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java @@ -243,6 +243,10 @@ public class TestPlanService { return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlanWithBLOBs()); } + public TestPlanWithBLOBs get(String testPlanId) { + return testPlanMapper.selectByPrimaryKey(testPlanId); + } + public TestPlan editTestPlanWithRequest(AddTestPlanRequest request) { List principals = request.getPrincipals(); if (!CollectionUtils.isEmpty(principals)) { @@ -1116,8 +1120,8 @@ public class TestPlanService { testPlanLoadCaseService.update(testPlanLoadCase); LogUtil.error(e); } - performaneThreadIDMap.put(performanceRequest.getTestPlanLoadId(), reportId); if (StringUtils.isNotEmpty(reportId)) { + performaneThreadIDMap.put(performanceRequest.getTestPlanLoadId(), reportId); executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.RUNNING.name()); } else { executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.PREPARE.name()); diff --git a/backend/src/main/java/io/metersphere/websocket/c/to/c/IndexWebSocket.java b/backend/src/main/java/io/metersphere/websocket/c/to/c/IndexWebSocket.java index 8c1fa4484a..143a9cb9b8 100644 --- a/backend/src/main/java/io/metersphere/websocket/c/to/c/IndexWebSocket.java +++ b/backend/src/main/java/io/metersphere/websocket/c/to/c/IndexWebSocket.java @@ -22,7 +22,7 @@ public class IndexWebSocket { public void openSession(@PathParam("reportId") String reportId, Session session) { WebSocketUtils.ONLINE_USER_SESSIONS.put(reportId, session); log.info("客户端: [" + reportId + "] : 连接成功!"); - WebSocketUtils.sendMessageAll("客户端: [" + reportId + "] : 连接成功!"); + //WebSocketUtils.sendMessageAll("客户端: [" + reportId + "] : 连接成功!"); } /** @@ -45,7 +45,7 @@ public class IndexWebSocket { WebSocketUtils.ONLINE_USER_SESSIONS.remove(reportId); log.info("[" + reportId + "] : 断开连接!"); //并且通知其他人当前用户已经断开连接了 - WebSocketUtils.sendMessageAll("[" + reportId + "] : 断开连接!"); + //WebSocketUtils.sendMessageAll("[" + reportId + "] : 断开连接!"); session.close(); } diff --git a/backend/src/main/java/io/metersphere/websocket/c/to/c/WebSocketUtils.java b/backend/src/main/java/io/metersphere/websocket/c/to/c/WebSocketUtils.java index 4a9a20d952..3d2c127bb9 100644 --- a/backend/src/main/java/io/metersphere/websocket/c/to/c/WebSocketUtils.java +++ b/backend/src/main/java/io/metersphere/websocket/c/to/c/WebSocketUtils.java @@ -1,5 +1,6 @@ package io.metersphere.websocket.c.to.c; +import io.metersphere.utils.LoggerUtil; import io.metersphere.websocket.c.to.c.util.MsgDto; import javax.websocket.RemoteEndpoint; @@ -12,9 +13,13 @@ public class WebSocketUtils { // 单用户推送 public static void sendMessage(Session session, String message) { - if (session == null) { return; } + if (session == null) { + return; + } RemoteEndpoint.Async async = session.getAsyncRemote(); - if (async == null) { return; } + if (async == null) { + return; + } async.sendText(message); } @@ -30,4 +35,20 @@ public class WebSocketUtils { sendMessage(session, message); }); } + + //当前的Session 移除 + public static void onClose(String reportId) { + try { + if (WebSocketUtils.ONLINE_USER_SESSIONS.containsKey(reportId)) { + WebSocketUtils.ONLINE_USER_SESSIONS.get(reportId).close(); + WebSocketUtils.ONLINE_USER_SESSIONS.remove(reportId); + } + if (WebSocketUtils.ONLINE_USER_SESSIONS.containsKey(("send." + reportId))) { + WebSocketUtils.ONLINE_USER_SESSIONS.get(("send." + reportId)).close(); + WebSocketUtils.ONLINE_USER_SESSIONS.remove(("send." + reportId)); + } + } catch (Exception e) { + LoggerUtil.error("关闭socket失败:" + e.getMessage()); + } + } } diff --git a/backend/src/main/java/io/metersphere/websocket/c/to/c/util/SocketClient.java b/backend/src/main/java/io/metersphere/websocket/c/to/c/util/SocketClient.java deleted file mode 100644 index e879fb0788..0000000000 --- a/backend/src/main/java/io/metersphere/websocket/c/to/c/util/SocketClient.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.metersphere.websocket.c.to.c.util; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * @author: jason - * @Date: 2020-12-23 - */ -@AllArgsConstructor -@NoArgsConstructor -@Builder -@Data -public class SocketClient { - private Integer userId; - private String username; -} diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index a1e39f9c1b..3cac4fd4c1 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit a1e39f9c1baf30f52da50ab34ea1bacbfbeff60c +Subproject commit 3cac4fd4c1cb2b3dfb4bd82a6a09cc55ae2f2d34 diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4757f780f5..3960b21085 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -12,7 +12,7 @@ server.ssl.key-alias=localhost # Hikari spring.datasource.type=com.zaxxer.hikari.HikariDataSource -spring.datasource.hikari.maximum-pool-size=100 +spring.datasource.hikari.maximum-pool-size=200 spring.datasource.hikari.auto-commit=true spring.datasource.hikari.idle-timeout=10000 spring.datasource.hikari.pool-name=DatebookHikariCP @@ -23,7 +23,7 @@ spring.datasource.hikari.connection-test-query=SELECT 1 spring.datasource.quartz.url=${spring.datasource.url} spring.datasource.quartz.username=${spring.datasource.username} spring.datasource.quartz.password=${spring.datasource.password} -spring.datasource.quartz.hikari.maximum-pool-size=50 +spring.datasource.quartz.hikari.maximum-pool-size=200 spring.datasource.quartz.hikari.auto-commit=true spring.datasource.quartz.hikari.idle-timeout=10000 spring.datasource.quartz.hikari.pool-name=DatebookHikariCP @@ -92,7 +92,8 @@ jmeter.home=/opt/jmeter # quartz quartz.enabled=true quartz.scheduler-name=msServerJob -quartz.thread-count=30 +quartz.thread-count=60 +quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true # file upload spring.servlet.multipart.max-file-size=500MB spring.servlet.multipart.max-request-size=500MB diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 9f48995a84..2aa95b95e6 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -285,6 +285,10 @@ test_case_status=Case status id_not_rightful=ID is not rightful # mock mock_warning=No matching Mock expectation was found -zentao_test_type_error=请求方式错误 +zentao_test_type_error=invalid Zentao request #项目报告 enterprise_test_report=Enterprise report +#环境组 +null_environment_group_name = Environment group name is null +environment_group_name = Environment group name +environment_group_exist = already exists diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index dc06fd2460..5e09e3f76d 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -284,6 +284,10 @@ test_case_status=用例状态 id_not_rightful=ID 不合法 # mock mock_warning=未找到匹配的Mock期望 -zentao_test_type_error=invalid Zentao request +zentao_test_type_error=无效的 Zentao 请求 #项目报告 enterprise_test_report=项目报告 +#环境组 +null_environment_group_name = 环境组名称不存在 +environment_group_name = 环境组名称 +environment_group_exist = 已存在 \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 86c613adfc..b7f0e41ea0 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -287,3 +287,7 @@ mock_warning=未找到匹配的Mock期望 zentao_test_type_error=請求方式錯誤 #项目报告 enterprise_test_report=項目報告 +#环境组 +null_environment_group_name = 環境組名稱不存在 +environment_group_name = 環境組名稱 +environment_group_exist = 已存在 \ No newline at end of file diff --git a/backend/src/main/resources/mail/api/CaseResult.html b/backend/src/main/resources/mail/api/CaseResultFailed.html similarity index 63% rename from backend/src/main/resources/mail/api/CaseResult.html rename to backend/src/main/resources/mail/api/CaseResultFailed.html index 5596cebbe6..5fccb612c3 100644 --- a/backend/src/main/resources/mail/api/CaseResult.html +++ b/backend/src/main/resources/mail/api/CaseResultFailed.html @@ -6,7 +6,7 @@
-

${operator}执行接口用例: ${name}, 结果: ${status}

+

${operator}执行接口用例失败: ${name}

\ No newline at end of file diff --git a/backend/src/main/resources/mail/api/ScenarioResult.html b/backend/src/main/resources/mail/api/CaseResultSuccess.html similarity index 62% rename from backend/src/main/resources/mail/api/ScenarioResult.html rename to backend/src/main/resources/mail/api/CaseResultSuccess.html index 96b56287be..2721a527da 100644 --- a/backend/src/main/resources/mail/api/ScenarioResult.html +++ b/backend/src/main/resources/mail/api/CaseResultSuccess.html @@ -6,7 +6,7 @@
-

${operator}执行接口自动化: ${name}, 结果: ${status}

+

${operator}执行接口用例成功: ${name}

\ No newline at end of file diff --git a/backend/src/main/resources/mail/api/ScenarioResultFailed.html b/backend/src/main/resources/mail/api/ScenarioResultFailed.html new file mode 100644 index 0000000000..f71c57f7cd --- /dev/null +++ b/backend/src/main/resources/mail/api/ScenarioResultFailed.html @@ -0,0 +1,12 @@ + + + + + MeterSphere + + +
+

${operator}执行接口自动化失败: ${name}

+
+ + \ No newline at end of file diff --git a/backend/src/main/resources/mail/api/ScenarioResultSuccess.html b/backend/src/main/resources/mail/api/ScenarioResultSuccess.html new file mode 100644 index 0000000000..4c2f3fad52 --- /dev/null +++ b/backend/src/main/resources/mail/api/ScenarioResultSuccess.html @@ -0,0 +1,12 @@ + + + + + MeterSphere + + +
+

${operator}执行接口自动化成功: ${name}

+
+ + \ No newline at end of file diff --git a/frontend/src/business/components/api/automation/report/ApiReportDetail.vue b/frontend/src/business/components/api/automation/report/ApiReportDetail.vue index 34b41f173c..82df2a7688 100644 --- a/frontend/src/business/components/api/automation/report/ApiReportDetail.vue +++ b/frontend/src/business/components/api/automation/report/ApiReportDetail.vue @@ -324,16 +324,21 @@ export default { if (data) { this.report = data; if (this.report.reportVersion && this.report.reportVersion > 1) { - if (data.content) { - let report = JSON.parse(data.content); - this.content = report; - this.fullTreeNodes = report.steps; - this.content.console = report.console; - this.content.error = report.error; - this.content.success = (report.total - report.error); - this.totalTime = report.totalTime; + this.report.status = data.status; + if (!this.isNotRunning) { + setTimeout(this.getReport, 2000) + } else { + if (data.content) { + let report = JSON.parse(data.content); + this.content = report; + this.fullTreeNodes = report.steps; + this.content.console = report.console; + this.content.error = report.error; + this.content.success = (report.total - report.error); + this.totalTime = report.totalTime; + } + this.loading = false; } - this.loading = false; } else { this.buildReport(); } @@ -528,10 +533,12 @@ export default { .report-container .is-active .fail { color: inherit; } + .report-console { height: calc(100vh - 270px); overflow-y: auto; } + .export-button { float: right; } diff --git a/frontend/src/business/components/api/automation/report/SysnApiReportDetail.vue b/frontend/src/business/components/api/automation/report/SysnApiReportDetail.vue index 0cbdb50478..5771b9fd46 100644 --- a/frontend/src/business/components/api/automation/report/SysnApiReportDetail.vue +++ b/frontend/src/business/components/api/automation/report/SysnApiReportDetail.vue @@ -356,7 +356,7 @@ export default { if (e.data) { this.runningEvaluation(e.data); } - if (e.data && e.data.indexOf("断开连接") !== -1) { + if (e.data && e.data.indexOf("MS_TEST_END") !== -1) { this.getReport(); this.messageWebSocket.close(); scenario.$emit('hide', this.scenarioId); diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index 3a020f670f..b8d4b994a6 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -454,7 +454,8 @@ export default { permissions: ['PROJECT_API_SCENARIO:READ+DELETE'] }, { - name: "批量恢复", handleClick: this.handleBatchRestore + name: this.$t('commons.batch_restore'), + handleClick: this.handleBatchRestore }, ], unTrashButtons: [ @@ -794,7 +795,7 @@ export default { // todo 选取全部数据 if (this.condition.selectAll) { - this.$warning("暂不支持批量添加所有场景到测试计划!"); + this.$warning(this.$t('api_test.scenario.warning_context')); } this.planVisible = false; @@ -1010,7 +1011,7 @@ export default { this.$get("/api/automation/checkScenarioEnv/" + this.currentScenario.id, res => { let data = res.data; if (!data) { - this.$warning("请为场景选择环境!"); + this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario')); return false; } this.reportId = getUUID().substring(0, 8); diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 9a68628940..79bd9b4ef8 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -789,7 +789,7 @@ export default { } this.runningEvaluation(e.data); this.message = getUUID(); - if (e.data && e.data.indexOf("断开连接") !== -1) { + if (e.data && e.data.indexOf("MS_TEST_END") !== -1) { this.runScenario = undefined; this.debugLoading = false; this.message = "stop"; diff --git a/frontend/src/business/components/api/automation/scenario/api/RelevanceApiList.vue b/frontend/src/business/components/api/automation/scenario/api/RelevanceApiList.vue index 29ba45dede..9131b25ee1 100644 --- a/frontend/src/business/components/api/automation/scenario/api/RelevanceApiList.vue +++ b/frontend/src/business/components/api/automation/scenario/api/RelevanceApiList.vue @@ -148,7 +148,7 @@ }, getConditions() { let sampleSelectRows = this.selectRows; - let param = buildBatchParam(this); + let param = buildBatchParam(this, undefined, this.projectId); param.ids = Array.from(sampleSelectRows).map(row => row.id); return param; }, diff --git a/frontend/src/business/components/api/automation/scenario/api/RelevanceCaseList.vue b/frontend/src/business/components/api/automation/scenario/api/RelevanceCaseList.vue index a6ec775e05..9a239b66a8 100644 --- a/frontend/src/business/components/api/automation/scenario/api/RelevanceCaseList.vue +++ b/frontend/src/business/components/api/automation/scenario/api/RelevanceCaseList.vue @@ -282,7 +282,7 @@ export default { }, getConditions() { let sampleSelectRows = this.$refs.table.getSelectRows(); - let batchParam = buildBatchParam(this); + let batchParam = buildBatchParam(this, undefined, this.projectId); let param = {}; if (batchParam.condition) { param = batchParam.condition; diff --git a/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue b/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue index 8d50114218..c8c91768ca 100644 --- a/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue +++ b/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue @@ -364,7 +364,7 @@ export default { } } } - if (databaseConfigsOptions.length > 0) { + if (databaseConfigsOptions.length > 0 && this.request.environmentId !== this.environment.id) { this.request.dataSourceId = databaseConfigsOptions[0].id; this.request.environmentId = this.environment.id; } @@ -639,7 +639,7 @@ export default { clickResource(resource) { if (resource.refType && resource.refType === 'API') { - if(resource.protocol==='dubbo://'){ + if (resource.protocol === 'dubbo://') { resource.protocol = 'DUBBO' } let definitionData = this.$router.resolve({ diff --git a/frontend/src/business/components/api/definition/components/basis/SetEnvironment.vue b/frontend/src/business/components/api/definition/components/basis/SetEnvironment.vue index e79ad35929..10af03914d 100644 --- a/frontend/src/business/components/api/definition/components/basis/SetEnvironment.vue +++ b/frontend/src/business/components/api/definition/components/basis/SetEnvironment.vue @@ -13,7 +13,7 @@ :placeholder="$t('api_test.definition.request.run_env')" clearable>