From a020a8e5954b986f717e40ec0049b04f6e201dff Mon Sep 17 00:00:00 2001 From: Jianguo-Genius Date: Tue, 18 Jun 2024 16:14:20 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan/service/TestPlanExecuteService.java | 320 +++++++----------- .../TestPlanExecuteSupportService.java | 169 +++++++++ .../plan/controller/TestPlanExecuteTests.java | 3 - 3 files changed, 292 insertions(+), 200 deletions(-) create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteSupportService.java diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java index ec30fd0d26..99a1b7445c 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java @@ -2,12 +2,14 @@ package io.metersphere.plan.service; import com.esotericsoftware.minlog.Log; import io.metersphere.plan.domain.*; -import io.metersphere.plan.dto.TestPlanReportPostParam; import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest; import io.metersphere.plan.dto.request.TestPlanExecuteRequest; import io.metersphere.plan.dto.request.TestPlanReportGenRequest; import io.metersphere.plan.mapper.*; -import io.metersphere.sdk.constants.*; +import io.metersphere.sdk.constants.ApiBatchRunMode; +import io.metersphere.sdk.constants.CaseType; +import io.metersphere.sdk.constants.TaskTriggerMode; +import io.metersphere.sdk.constants.TestPlanConstants; import io.metersphere.sdk.dto.queue.TestPlanExecutionQueue; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.JSON; @@ -17,7 +19,6 @@ import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import static io.metersphere.plan.service.TestPlanExecuteSupportService.*; + @Service @Transactional(rollbackFor = Exception.class) public class TestPlanExecuteService { @@ -36,8 +39,6 @@ public class TestPlanExecuteService { @Resource private ExtTestPlanReportMapper extTestPlanReportMapper; @Resource - private ExtTestPlanMapper extTestPlanMapper; - @Resource private TestPlanConfigMapper testPlanConfigMapper; @Resource private TestPlanService testPlanService; @@ -55,14 +56,10 @@ public class TestPlanExecuteService { @Resource private RedisTemplate redisTemplate; - public static final String QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE = "test-plan-batch-execute:"; - public static final String QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE = "test-plan-group-execute:"; - public static final String QUEUE_PREFIX_TEST_PLAN_CASE_TYPE = "test-plan-case-type-execute:"; - public static final String QUEUE_PREFIX_TEST_PLAN_COLLECTION = "test-plan-collection-execute:"; - - public static final String LAST_QUEUE_PREFIX = "last-queue:"; @Resource private TestPlanReportMapper testPlanReportMapper; + @Resource + private TestPlanExecuteSupportService testPlanExecuteSupportService; // 停止测试计划的执行 public void stopTestPlanRunning(String testPlanReportId) { @@ -85,19 +82,19 @@ public class TestPlanExecuteService { 继续执行 test-plan-batch-execute:randomId 队列的下一条 */ // 获取下一个要执行的测试计划节点,目的是得到最后一条的queueId - TestPlanExecutionQueue nextTestPlanQueue = this.getNextQueue(testPlanReportId, QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE); + TestPlanExecutionQueue nextTestPlanQueue = testPlanExecuteSupportService.getNextQueue(testPlanReportId, QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE); if (nextTestPlanQueue == null || !StringUtils.equalsIgnoreCase(nextTestPlanQueue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) { return; } - String groupExecuteQueueId = genQueueKey(testPlanReportId, QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE); - this.deleteRedisKey(groupExecuteQueueId); + String groupExecuteQueueId = testPlanExecuteSupportService.genQueueKey(testPlanReportId, QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE); + testPlanExecuteSupportService.deleteRedisKey(groupExecuteQueueId); testPlanItemReport.forEach(item -> { this.deepDeleteTestPlanCaseType(item); //统计子测试计划报告 - summaryTestPlanReport(item.getId(), false, true); + testPlanExecuteSupportService.summaryTestPlanReport(item.getId(), false, true); }); - summaryTestPlanReport(testPlanReportId, true, true); + testPlanExecuteSupportService.summaryTestPlanReport(testPlanReportId, true, true); this.testPlanExecuteQueueFinish(nextTestPlanQueue.getParentQueueId(), nextTestPlanQueue.getParentQueueType()); } else { /* @@ -110,27 +107,26 @@ public class TestPlanExecuteService { 进行当前报告结算 继续执行 队列的下一条 */ - TestPlanExecutionQueue nextTestPlanQueue = this.getNextQueue(testPlanReportId, QUEUE_PREFIX_TEST_PLAN_CASE_TYPE); + TestPlanExecutionQueue nextTestPlanQueue = testPlanExecuteSupportService.getNextQueue(testPlanReportId, QUEUE_PREFIX_TEST_PLAN_CASE_TYPE); + testPlanExecuteSupportService.summaryTestPlanReport(testPlanReportId, false, true); if (nextTestPlanQueue == null || !StringUtils.equalsAnyIgnoreCase(nextTestPlanQueue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE, QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) { - return; + testPlanExecuteSupportService.updateReportStopped(testPlanReportId); + } else { + this.deepDeleteTestPlanCaseType(testPlanReport); + this.testPlanExecuteQueueFinish(nextTestPlanQueue.getParentQueueId(), nextTestPlanQueue.getParentQueueType()); } - this.deepDeleteTestPlanCaseType(testPlanReport); - summaryTestPlanReport(testPlanReportId, false, true); - this.testPlanExecuteQueueFinish(nextTestPlanQueue.getParentQueueId(), nextTestPlanQueue.getParentQueueType()); } - - // todo @wxg 是在 deepDeleteTestPlanCaseType()方法中删除用例执行队列时,同步到执行机停止执行任务,还是其它操作, 由你来决定了 } private void deepDeleteTestPlanCaseType(TestPlanReport report) { - this.deleteRedisKey(genQueueKey(report.getId(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)); + testPlanExecuteSupportService.deleteRedisKey( + testPlanExecuteSupportService.genQueueKey(report.getId(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)); TestPlanCollectionExample collectionExample = new TestPlanCollectionExample(); collectionExample.createCriteria().andTestPlanIdEqualTo(report.getTestPlanId()).andParentIdEqualTo(TestPlanConstants.DEFAULT_PARENT_ID); List parentTestPlanCollectionList = testPlanCollectionMapper.selectByExample(collectionExample); parentTestPlanCollectionList.forEach(parentCollection -> { - - this.deleteRedisKey(genQueueKey(report.getId() + "_" + parentCollection.getId(), QUEUE_PREFIX_TEST_PLAN_COLLECTION)); - + testPlanExecuteSupportService.deleteRedisKey( + testPlanExecuteSupportService.genQueueKey(report.getId() + "_" + parentCollection.getId(), QUEUE_PREFIX_TEST_PLAN_COLLECTION)); //todo @Chen-Jianxing 这里要同步清理用例/场景的执行队列 }); } @@ -154,34 +150,23 @@ public class TestPlanExecuteService { System.currentTimeMillis(), executionQueue.getQueueId(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE, - null, - null, + executionQueue.getQueueId(), + executionQueue.getQueueType(), request.getExecuteId(), executionQueue.getRunMode(), executionQueue.getExecutionSource(), IDGenerator.nextStr() ); - String redisKey = genQueueKey(executionQueue.getQueueId(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE); - redisTemplate.opsForList().rightPush(genQueueKey(executionQueue.getQueueId(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE), JSON.toJSONString(singleExecuteRootQueue)); - redisTemplate.expire(genQueueKey(executionQueue.getQueueId(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE), 1, TimeUnit.DAYS); + String redisKey = testPlanExecuteSupportService.genQueueKey(executionQueue.getQueueId(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE); + redisTemplate.opsForList().rightPush(redisKey, JSON.toJSONString(singleExecuteRootQueue)); + redisTemplate.expire(redisKey, 1, TimeUnit.DAYS); LogUtils.info("测试计划(组)的单独执行start!计划报告[{}] , 资源ID[{}]", singleExecuteRootQueue.getPrepareReportId(), singleExecuteRootQueue.getSourceID()); return executeTestPlanOrGroup(executionQueue); } - private void setRedisForList(String key, List list) { - redisTemplate.opsForList().rightPushAll(key, list); - redisTemplate.expire(key, 1, TimeUnit.DAYS); - } - - private void deleteRedisKey(String redisKey) { - //清除list的key 和 last key节点 - redisTemplate.delete(redisKey); - redisTemplate.delete(genQueueKey(redisKey, LAST_QUEUE_PREFIX)); - } - //批量执行测试计划组 public void batchExecuteTestPlan(TestPlanBatchExecuteRequest request, String userId) { List rightfulIds = testPlanService.selectRightfulIds(request.getExecuteIds()); @@ -213,11 +198,12 @@ public class TestPlanExecuteService { ); } } - this.setRedisForList(genQueueKey(queueId, queueType), testPlanExecutionQueues.stream().map(JSON::toJSONString).toList()); + testPlanExecuteSupportService.setRedisForList( + testPlanExecuteSupportService.genQueueKey(queueId, queueType), testPlanExecutionQueues.stream().map(JSON::toJSONString).toList()); LogUtils.info("测试计划(组)的批量执行start!队列ID[{}] ,队列类型[{}] , 资源ID[{}]", queueId, queueType, JSON.toJSONString(rightfulIds)); if (StringUtils.equalsIgnoreCase(request.getRunMode(), ApiBatchRunMode.SERIAL.name())) { //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueId, queueType); executeTestPlanOrGroup(nextQueue); } else { //并行 @@ -243,7 +229,6 @@ public class TestPlanExecuteService { testPlanService.setActualStartTime(executionQueue.getSourceID()); testPlanService.setTestPlanUnderway(executionQueue.getSourceID()); - List children = testPlanService.selectNotArchivedChildren(testPlan.getId()); // 预生成计划组报告 Map reportMap = testPlanReportService.genReportByExecution(executionQueue.getPrepareReportId(), genReportRequest, executionQueue.getCreateUser()); @@ -276,7 +261,7 @@ public class TestPlanExecuteService { //本次的测试计划组执行完成 this.testPlanGroupQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); + testPlanExecuteSupportService.setRedisForList(testPlanExecuteSupportService.genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); // 更新报告的执行时间 if (MapUtils.isNotEmpty(reportMap)) { @@ -285,7 +270,7 @@ public class TestPlanExecuteService { if (StringUtils.equalsIgnoreCase(executionQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueId, queueType); executeTestPlan(nextQueue); } else { //并行 @@ -309,57 +294,63 @@ public class TestPlanExecuteService { //执行测试计划里不同类型的用例 回调:caseTypeExecuteQueueFinish public void executeTestPlan(TestPlanExecutionQueue executionQueue) { - testPlanService.setActualStartTime(executionQueue.getSourceID()); - testPlanService.setTestPlanUnderway(executionQueue.getSourceID()); - TestPlan testPlan = testPlanMapper.selectByPrimaryKey(executionQueue.getSourceID()); - TestPlanCollectionExample testPlanCollectionExample = new TestPlanCollectionExample(); - testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(testPlan.getId()).andParentIdEqualTo("NONE"); - testPlanCollectionExample.setOrderByClause("pos asc"); - //过滤掉功能用例的测试集 - List testPlanCollectionList = testPlanCollectionMapper.selectByExample(testPlanCollectionExample).stream().filter( - testPlanCollection -> !StringUtils.equalsIgnoreCase(testPlanCollection.getType(), CaseType.FUNCTIONAL_CASE.getKey()) - ).toList(); - - int pos = 0; - TestPlanConfig testPlanConfig = testPlanConfigMapper.selectByPrimaryKey(testPlan.getId()); - String runMode = StringUtils.isBlank(testPlanConfig.getCaseRunMode()) ? ApiBatchRunMode.SERIAL.name() : testPlanConfig.getCaseRunMode(); - - String queueId = executionQueue.getPrepareReportId(); - String queueType = QUEUE_PREFIX_TEST_PLAN_CASE_TYPE; - List childrenQueue = new ArrayList<>(); - for (TestPlanCollection collection : testPlanCollectionList) { - childrenQueue.add( - new TestPlanExecutionQueue( - pos++, - executionQueue.getCreateUser(), - System.currentTimeMillis(), - queueId, - queueType, - executionQueue.getQueueId(), - executionQueue.getQueueType(), - collection.getId(), - runMode, - executionQueue.getExecutionSource(), - executionQueue.getPrepareReportId()) - ); - } - LogUtils.info("测试计划执行节点 --- 队列ID[{}],队列类型[{}],父队列ID[{}],父队列类型[{}],执行模式[{}]", queueId, queueType, executionQueue.getParentQueueId(), executionQueue.getParentQueueType(), runMode); - if (CollectionUtils.isEmpty(childrenQueue)) { - //本次的测试计划组执行完成 + boolean testPlanStopped = testPlanExecuteSupportService.checkTestPlanStopped(executionQueue.getPrepareReportId()); + if (testPlanStopped) { + //测试计划报告状态已停止的话便不再执行。执行下一个队列。 this.testPlanExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); + testPlanService.setActualStartTime(executionQueue.getSourceID()); + testPlanService.setTestPlanUnderway(executionQueue.getSourceID()); + TestPlan testPlan = testPlanMapper.selectByPrimaryKey(executionQueue.getSourceID()); + TestPlanCollectionExample testPlanCollectionExample = new TestPlanCollectionExample(); + testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(testPlan.getId()).andParentIdEqualTo("NONE"); + testPlanCollectionExample.setOrderByClause("pos asc"); + //过滤掉功能用例的测试集 + List testPlanCollectionList = testPlanCollectionMapper.selectByExample(testPlanCollectionExample).stream().filter( + testPlanCollection -> !StringUtils.equalsIgnoreCase(testPlanCollection.getType(), CaseType.FUNCTIONAL_CASE.getKey()) + ).toList(); - //开始根据测试计划集合执行测试用例 - if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) { - //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); - this.executeByTestPlanCollection(nextQueue); + int pos = 0; + TestPlanConfig testPlanConfig = testPlanConfigMapper.selectByPrimaryKey(testPlan.getId()); + String runMode = StringUtils.isBlank(testPlanConfig.getCaseRunMode()) ? ApiBatchRunMode.SERIAL.name() : testPlanConfig.getCaseRunMode(); + + String queueId = executionQueue.getPrepareReportId(); + String queueType = QUEUE_PREFIX_TEST_PLAN_CASE_TYPE; + List childrenQueue = new ArrayList<>(); + for (TestPlanCollection collection : testPlanCollectionList) { + childrenQueue.add( + new TestPlanExecutionQueue( + pos++, + executionQueue.getCreateUser(), + System.currentTimeMillis(), + queueId, + queueType, + executionQueue.getQueueId(), + executionQueue.getQueueType(), + collection.getId(), + runMode, + executionQueue.getExecutionSource(), + executionQueue.getPrepareReportId()) + ); + } + LogUtils.info("测试计划执行节点 --- 队列ID[{}],队列类型[{}],父队列ID[{}],父队列类型[{}],执行模式[{}]", queueId, queueType, executionQueue.getParentQueueId(), executionQueue.getParentQueueType(), runMode); + if (CollectionUtils.isEmpty(childrenQueue)) { + //本次的测试计划组执行完成 + this.testPlanExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - //并行 - childrenQueue.forEach(childQueue -> { - this.executeByTestPlanCollection(childQueue); - }); + testPlanExecuteSupportService.setRedisForList(testPlanExecuteSupportService.genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); + + //开始根据测试计划集合执行测试用例 + if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) { + //串行 + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueId, queueType); + this.executeByTestPlanCollection(nextQueue); + } else { + //并行 + childrenQueue.forEach(childQueue -> { + this.executeByTestPlanCollection(childQueue); + }); + } } } } @@ -400,10 +391,11 @@ public class TestPlanExecuteService { //本次的测试集执行完成 this.caseTypeExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); + testPlanExecuteSupportService.setRedisForList( + testPlanExecuteSupportService.genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) { //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueId, queueType); this.executeCase(nextQueue); } else { //并行 @@ -423,30 +415,31 @@ public class TestPlanExecuteService { private void executeCase(TestPlanExecutionQueue testPlanExecutionQueue) { String queueId = testPlanExecutionQueue.getQueueId(); LogUtils.info("测试集执行节点 --- 队列ID[{}],队列类型[{}],父队列ID[{}],父队列类型[{}],执行模式[{}]", queueId, testPlanExecutionQueue.getQueueType(), testPlanExecutionQueue.getParentQueueId(), testPlanExecutionQueue.getParentQueueType(), testPlanExecutionQueue.getRunMode()); + boolean execOver = false; try { - boolean isFinish = false; TestPlanCollection collection = JSON.parseObject(testPlanExecutionQueue.getTestPlanCollectionJson(), TestPlanCollection.class); TestPlanCollection extendedRootCollection = testPlanApiBatchRunBaseService.getExtendedRootCollection(collection); String executeMethod = extendedRootCollection == null ? collection.getExecuteMethod() : extendedRootCollection.getExecuteMethod(); if (StringUtils.equalsIgnoreCase(collection.getType(), CaseType.API_CASE.getKey())) { if (StringUtils.equals(executeMethod, ApiBatchRunMode.PARALLEL.name())) { - isFinish = planRunTestPlanApiCaseService.parallelExecute(testPlanExecutionQueue); + execOver = planRunTestPlanApiCaseService.parallelExecute(testPlanExecutionQueue); } else { - isFinish = planRunTestPlanApiCaseService.serialExecute(testPlanExecutionQueue); + execOver = planRunTestPlanApiCaseService.serialExecute(testPlanExecutionQueue); } } else if (StringUtils.equalsIgnoreCase(collection.getType(), CaseType.SCENARIO_CASE.getKey())) { if (StringUtils.equals(executeMethod, ApiBatchRunMode.PARALLEL.name())) { - isFinish = planRunTestPlanApiScenarioService.parallelExecute(testPlanExecutionQueue); + execOver = planRunTestPlanApiScenarioService.parallelExecute(testPlanExecutionQueue); } else { - isFinish = planRunTestPlanApiScenarioService.serialExecute(testPlanExecutionQueue); + execOver = planRunTestPlanApiScenarioService.serialExecute(testPlanExecutionQueue); } } - if (isFinish) { - // 如果没有要执行的用例(可能会出现空测试集的情况),直接调用回调 - collectionExecuteQueueFinish(queueId); - } } catch (Exception e) { Log.error("按测试集执行失败!", e); + execOver = true; + } + + if (execOver) { + // 如果没有要执行的用例(可能会出现空测试集的情况),直接调用回调 collectionExecuteQueueFinish(queueId); } } @@ -455,14 +448,19 @@ public class TestPlanExecuteService { public void collectionExecuteQueueFinish(String queueID) { String queueType = QUEUE_PREFIX_TEST_PLAN_COLLECTION; LogUtils.info("收到测试集执行完成的信息: 队列ID[{}],队列类型[{}],下一个节点的执行工作准备中...", queueID, queueType); - TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueID, queueType); if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { //串行时,由于是先拿出节点再判断执行,所以要判断节点的isExecuteFinish if (!nextQueue.isExecuteFinish()) { + boolean execError = false; try { LogUtils.info("测试集该节点的串行执行完成! --- 队列ID[{}],队列类型[{}],开始执行下一个队列:ID[{}],类型[{}]", queueID, queueType, nextQueue.getQueueId(), nextQueue.getQueueType()); this.executeNextNode(nextQueue); } catch (Exception e) { + Log.error("测试集下一个节点执行失败!", e); + execError = true; + } + if (execError) { this.collectionExecuteQueueFinish(nextQueue.getQueueId()); } } else { @@ -480,14 +478,18 @@ public class TestPlanExecuteService { //测试计划中当前用例类型的全部执行完成 private void caseTypeExecuteQueueFinish(String queueID, String queueType) { LogUtils.info("收到用例类型执行队列的执行完成的信息: 队列ID[{}],队列类型[{}],下一个节点的执行工作准备中...", queueID, queueType); - TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueID, queueType); if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { //串行时,由于是先拿出节点再判断执行,所以要判断节点的isExecuteFinish if (!nextQueue.isExecuteFinish()) { + boolean execError = false; try { LogUtils.info("用例类型该节点的串行执行完成! --- 队列ID[{}],队列类型[{}],开始执行下一个队列:ID[{}],类型[{}]", queueID, queueType, nextQueue.getQueueId(), nextQueue.getQueueType()); this.executeNextNode(nextQueue); } catch (Exception e) { + execError = true; + } + if (execError) { this.caseTypeExecuteQueueFinish(nextQueue.getQueueId(), nextQueue.getQueueType()); } } else { @@ -505,13 +507,17 @@ public class TestPlanExecuteService { //测试计划执行完成 private void testPlanExecuteQueueFinish(String queueID, String queueType) { LogUtils.info("收到测试计划执行完成的信息: 队列ID[{}],队列类型[{}],下一个节点的执行工作准备中...", queueID, queueType); - TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueID, queueType); if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { if (!nextQueue.isExecuteFinish()) { + boolean execError = false; try { LogUtils.info("测试计划该节点的串行执行完成! --- 队列ID[{}],队列类型[{}],开始执行下一个队列:ID[{}],类型[{}]", queueID, queueType, nextQueue.getQueueId(), nextQueue.getQueueType()); this.executeNextNode(nextQueue); } catch (Exception e) { + execError = true; + } + if (execError) { this.testPlanExecuteQueueFinish(nextQueue.getQueueId(), nextQueue.getQueueType()); } } else { @@ -528,16 +534,20 @@ public class TestPlanExecuteService { //测试计划批量执行队列节点执行完成 private void testPlanGroupQueueFinish(String queueID, String queueType) { LogUtils.info("收到计划组执行完成的信息: 队列ID[{}],队列类型[{}],下一个节点的执行工作准备中...", queueID, queueType); - TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); + TestPlanExecutionQueue nextQueue = testPlanExecuteSupportService.getNextQueue(queueID, queueType); if (nextQueue == null) { return; } if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { if (!nextQueue.isExecuteFinish()) { + boolean execError = false; try { LogUtils.info("计划组该节点的串行执行完成! --- 队列ID[{}],队列类型[{}],开始执行下一个队列:ID[{}],类型[{}]", queueID, queueType, nextQueue.getQueueId(), nextQueue.getQueueType()); this.executeNextNode(nextQueue); } catch (Exception e) { + execError = true; + } + if (execError) { this.testPlanGroupQueueFinish(queueID, queueType); } } else { @@ -564,111 +574,27 @@ public class TestPlanExecuteService { } } - private void summaryTestPlanReport(String reportId, boolean isGroupReport, boolean isStop) { - LogUtils.info("开始合并报告: --- 报告ID[{}],是否是报告组[{}]", reportId, isGroupReport); - try { - if (isGroupReport) { - testPlanReportService.summaryGroupReport(reportId); - } else { - testPlanReportService.summaryPlanReport(reportId); - } - - TestPlanReportPostParam postParam = new TestPlanReportPostParam(); - postParam.setReportId(reportId); - // 执行生成报告, 执行状态为已完成, 执行及结束时间为当前时间 - postParam.setEndTime(System.currentTimeMillis()); - postParam.setExecStatus(isStop ? ExecStatus.STOPPED.name() : ExecStatus.COMPLETED.name()); - testPlanReportService.postHandleReport(postParam, false); - - if (!isGroupReport) { - TestPlanReport testPlanReport = testPlanReportService.selectById(reportId); - if (testPlanReport != null) { - testPlanService.refreshTestPlanStatus(testPlanReport.getTestPlanId()); - } - } - } catch (Exception e) { - LogUtils.error("Cannot find test plan report for " + reportId, e); - } - } - private void queueExecuteFinish(TestPlanExecutionQueue queue) { LogUtils.info("当前节点执行完成: --- 队列ID[{}],队列类型[{}],父队列ID[{}],父队列类型[{}]", queue.getQueueId(), queue.getQueueType(), queue.getParentQueueId(), queue.getParentQueueType()); if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) { if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)) { // 计划组报告汇总并统计 - this.summaryTestPlanReport(queue.getQueueId(), true, false); + testPlanExecuteSupportService.summaryTestPlanReport(queue.getQueueId(), true, false); } else if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)) { /* 此时处于批量勾选执行中的游离态测试计划执行。所以队列顺序为:QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE -> QUEUE_PREFIX_TEST_PLAN_CASE_TYPE。 此时queue节点为testPlanCollection的节点。 而测试计划节点(串行状态下)在执行之前就被弹出了。 所以获取报告ID的方式为读取queueId (caseType队列和collection队列的queueId都是报告ID) */ - this.summaryTestPlanReport(queue.getQueueId(), false, false); + testPlanExecuteSupportService.summaryTestPlanReport(queue.getQueueId(), false, false); } this.testPlanGroupQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)) { // 计划报告汇总并统计 - this.summaryTestPlanReport(queue.getQueueId(), false, false); + testPlanExecuteSupportService.summaryTestPlanReport(queue.getQueueId(), false, false); this.testPlanExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)) { this.caseTypeExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); } } - - /** - * 获取下一个队列节点 - */ - private TestPlanExecutionQueue getNextQueue(String queueId, String queueType) { - if (StringUtils.isAnyBlank(queueId, queueType)) { - return null; - } - - String queueKey = this.genQueueKey(queueId, queueType); - ListOperations listOps = redisTemplate.opsForList(); - String queueDetail = listOps.leftPop(queueKey); - if (StringUtils.isBlank(queueDetail)) { - // 重试1次获取 - try { - Thread.sleep(1000); - } catch (Exception ignore) { - } - queueDetail = redisTemplate.opsForList().leftPop(queueKey); - } - - if (StringUtils.isNotBlank(queueDetail)) { - TestPlanExecutionQueue returnQueue = JSON.parseObject(queueDetail, TestPlanExecutionQueue.class); - Long size = listOps.size(queueKey); - if (size == null || size == 0) { - returnQueue.setLastOne(true); - if (StringUtils.equalsIgnoreCase(returnQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { - //串行的执行方式意味着最后一个节点要单独存储 - redisTemplate.opsForValue().setIfAbsent(genQueueKey(queueKey, LAST_QUEUE_PREFIX), JSON.toJSONString(returnQueue), 1, TimeUnit.DAYS); - } - // 最后一个节点清理队列 - deleteQueue(queueKey); - } - return returnQueue; - } else { - String lastQueueJson = redisTemplate.opsForValue().getAndDelete(genQueueKey(queueKey, LAST_QUEUE_PREFIX)); - if (StringUtils.isNotBlank(lastQueueJson)) { - TestPlanExecutionQueue nextQueue = JSON.parseObject(lastQueueJson, TestPlanExecutionQueue.class); - nextQueue.setExecuteFinish(true); - return nextQueue; - } - } - - // 整体获取完,清理队列 - deleteQueue(queueKey); - return null; - } - - - private void deleteQueue(String queueKey) { - redisTemplate.delete(queueKey); - } - - //生成队列key - private String genQueueKey(String queueId, String queueType) { - return queueType + queueId; - } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteSupportService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteSupportService.java new file mode 100644 index 0000000000..46bf63d619 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteSupportService.java @@ -0,0 +1,169 @@ +package io.metersphere.plan.service; + +import io.metersphere.plan.domain.*; +import io.metersphere.plan.dto.TestPlanReportPostParam; +import io.metersphere.plan.mapper.TestPlanReportApiCaseMapper; +import io.metersphere.plan.mapper.TestPlanReportApiScenarioMapper; +import io.metersphere.plan.mapper.TestPlanReportMapper; +import io.metersphere.sdk.constants.ApiBatchRunMode; +import io.metersphere.sdk.constants.ExecStatus; +import io.metersphere.sdk.dto.queue.TestPlanExecutionQueue; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.LogUtils; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestPlanExecuteSupportService { + + @Resource + private TestPlanService testPlanService; + @Resource + private TestPlanReportService testPlanReportService; + @Resource + private RedisTemplate redisTemplate; + @Resource + private TestPlanReportApiCaseMapper testPlanReportApiCaseMapper; + @Resource + private TestPlanReportApiScenarioMapper testPlanReportApiScenarioMapper; + + public static final String QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE = "test-plan-batch-execute:"; + public static final String QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE = "test-plan-group-execute:"; + public static final String QUEUE_PREFIX_TEST_PLAN_CASE_TYPE = "test-plan-case-type-execute:"; + public static final String QUEUE_PREFIX_TEST_PLAN_COLLECTION = "test-plan-collection-execute:"; + + public static final String LAST_QUEUE_PREFIX = "last-queue:"; + @Resource + private TestPlanReportMapper testPlanReportMapper; + + + public void setRedisForList(String key, List list) { + redisTemplate.opsForList().rightPushAll(key, list); + redisTemplate.expire(key, 1, TimeUnit.DAYS); + } + + public void deleteRedisKey(String redisKey) { + //清除list的key 和 last key节点 + redisTemplate.delete(redisKey); + redisTemplate.delete(genQueueKey(redisKey, LAST_QUEUE_PREFIX)); + } + + + public void summaryTestPlanReport(String reportId, boolean isGroupReport, boolean isStop) { + LogUtils.info("开始合并报告: --- 报告ID[{}],是否是报告组[{}]", reportId, isGroupReport); + try { + if (isGroupReport) { + testPlanReportService.summaryGroupReport(reportId); + } else { + testPlanReportService.summaryPlanReport(reportId); + } + + TestPlanReportPostParam postParam = new TestPlanReportPostParam(); + postParam.setReportId(reportId); + // 执行生成报告, 执行状态为已完成, 执行及结束时间为当前时间 + postParam.setEndTime(System.currentTimeMillis()); + postParam.setExecStatus(isStop ? ExecStatus.STOPPED.name() : ExecStatus.COMPLETED.name()); + testPlanReportService.postHandleReport(postParam, false); + + if (!isGroupReport) { + TestPlanReport testPlanReport = testPlanReportService.selectById(reportId); + if (testPlanReport != null) { + testPlanService.refreshTestPlanStatus(testPlanReport.getTestPlanId()); + } + } + } catch (Exception e) { + LogUtils.error("Cannot find test plan report for " + reportId, e); + } + } + + + /** + * 获取下一个队列节点 + */ + public TestPlanExecutionQueue getNextQueue(String queueId, String queueType) { + if (StringUtils.isAnyBlank(queueId, queueType)) { + return null; + } + + String queueKey = this.genQueueKey(queueId, queueType); + ListOperations listOps = redisTemplate.opsForList(); + String queueDetail = listOps.leftPop(queueKey); + if (StringUtils.isBlank(queueDetail)) { + // 重试1次获取 + try { + Thread.sleep(1000); + } catch (Exception ignore) { + } + queueDetail = redisTemplate.opsForList().leftPop(queueKey); + } + + if (StringUtils.isNotBlank(queueDetail)) { + TestPlanExecutionQueue returnQueue = JSON.parseObject(queueDetail, TestPlanExecutionQueue.class); + Long size = listOps.size(queueKey); + if (size == null || size == 0) { + returnQueue.setLastOne(true); + if (StringUtils.equalsIgnoreCase(returnQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { + //串行的执行方式意味着最后一个节点要单独存储 + redisTemplate.opsForValue().setIfAbsent(genQueueKey(queueKey, LAST_QUEUE_PREFIX), JSON.toJSONString(returnQueue), 1, TimeUnit.DAYS); + } + // 最后一个节点清理队列 + deleteQueue(queueKey); + } + return returnQueue; + } else { + String lastQueueJson = redisTemplate.opsForValue().getAndDelete(genQueueKey(queueKey, LAST_QUEUE_PREFIX)); + if (StringUtils.isNotBlank(lastQueueJson)) { + TestPlanExecutionQueue nextQueue = JSON.parseObject(lastQueueJson, TestPlanExecutionQueue.class); + nextQueue.setExecuteFinish(true); + return nextQueue; + } + } + + // 整体获取完,清理队列 + deleteQueue(queueKey); + return null; + } + + + public void deleteQueue(String queueKey) { + redisTemplate.delete(queueKey); + } + + //生成队列key + public String genQueueKey(String queueId, String queueType) { + return queueType + queueId; + } + + public boolean checkTestPlanStopped(String prepareReportId) { + TestPlanReportExample reportExample = new TestPlanReportExample(); + reportExample.createCriteria().andIdEqualTo(prepareReportId).andExecStatusEqualTo(ExecStatus.STOPPED.name()); + return testPlanReportMapper.countByExample(reportExample) > 0; + } + + public void updateReportStopped(String testPlanReportId) { + TestPlanReport testPlanReport = new TestPlanReport(); + testPlanReport.setId(testPlanReportId); + testPlanReport.setExecStatus(ExecStatus.STOPPED.name()); + testPlanReportMapper.updateByPrimaryKeySelective(testPlanReport); + + TestPlanReportApiCaseExample apiCaseExample = new TestPlanReportApiCaseExample(); + apiCaseExample.createCriteria().andTestPlanReportIdEqualTo(testPlanReportId).andApiCaseExecuteResultIsNull(); + TestPlanReportApiCase testPlanReportApiCase = new TestPlanReportApiCase(); + testPlanReportApiCase.setApiCaseExecuteResult(ExecStatus.STOPPED.name()); + testPlanReportApiCaseMapper.updateByExampleSelective(testPlanReportApiCase, apiCaseExample); + + TestPlanReportApiScenarioExample scenarioExample = new TestPlanReportApiScenarioExample(); + scenarioExample.createCriteria().andTestPlanReportIdEqualTo(testPlanReportId).andApiScenarioExecuteResultIsNull(); + TestPlanReportApiScenario testPlanReportApiScenario = new TestPlanReportApiScenario(); + testPlanReportApiScenario.setApiScenarioExecuteResult(ExecStatus.STOPPED.name()); + testPlanReportApiScenarioMapper.updateByExampleSelective(testPlanReportApiScenario, scenarioExample); + } +} diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanExecuteTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanExecuteTests.java index 3b9a8e3b39..d45690e0b1 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanExecuteTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanExecuteTests.java @@ -256,9 +256,6 @@ public class TestPlanExecuteTests extends BaseTest { } } } -// Assertions.assertTrue(!collectionQueueIdList.isEmpty()); -// Assertions.assertTrue(!allQueueIds.isEmpty()); - this.checkRedisKeyEmpty(allQueueIds, collectionQueueIdList); }