From 5d97da2a78994b91bf842c29acec4955102775d9 Mon Sep 17 00:00:00 2001 From: Jianguo-Genius Date: Wed, 12 Jun 2024 17:24:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):=20?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C=E6=B5=8B=E8=AF=95=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E6=B5=8B=E8=AF=95=E9=98=9F=E5=88=97=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sdk/dto/queue/TestPlanExecutionQueue.java | 19 +- .../dto/request/TestPlanCreateRequest.java | 3 - .../plan/service/TestPlanExecuteService.java | 196 +++++++----- .../plan/service/TestPlanService.java | 5 +- .../plan/controller/TestPlanExecuteTests.java | 293 ++++++++++++++++++ .../plan/controller/TestPlanTests.java | 24 -- 6 files changed, 432 insertions(+), 108 deletions(-) create mode 100644 backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanExecuteTests.java diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/queue/TestPlanExecutionQueue.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/queue/TestPlanExecutionQueue.java index dc968299ec..c4611a19b8 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/queue/TestPlanExecutionQueue.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/queue/TestPlanExecutionQueue.java @@ -31,5 +31,22 @@ public class TestPlanExecutionQueue { //预生成报告ID private String prepareReportId; - private boolean lastFinished = false; + // 是否是队列的最后一个 + private boolean isLastOne = false; + // 是否执行完毕 + private boolean executeFinish = false; + + public TestPlanExecutionQueue(long pos, String createUser, long createTime, String queueId, String queueType, String parentQueueId, String parentQueueType, String sourceID, String runMode, String executionSource, String prepareReportId) { + this.pos = pos; + this.createUser = createUser; + this.createTime = createTime; + this.queueId = queueId; + this.queueType = queueType; + this.parentQueueId = parentQueueId; + this.parentQueueType = parentQueueType; + this.sourceID = sourceID; + this.runMode = runMode; + this.executionSource = executionSource; + this.prepareReportId = prepareReportId; + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCreateRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCreateRequest.java index 0f63eca208..df6c1d4838 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCreateRequest.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCreateRequest.java @@ -49,9 +49,6 @@ public class TestPlanCreateRequest { @Schema(description = "描述") private String description; - @Schema(description = "是否开启测试规划", requiredMode = Schema.RequiredMode.REQUIRED) - private boolean testPlanning; - @Schema(description = "是否自定更新功能用例状态", requiredMode = Schema.RequiredMode.REQUIRED) private boolean automaticStatusUpdate; 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 d15af93d7b..e0937c4086 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 @@ -21,7 +21,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -44,12 +43,10 @@ public class TestPlanExecuteService { @Resource private RedisTemplate redisTemplate; - @Resource - private StringRedisTemplate stringRedisTemplate; - public static final String QUEUE_PREFIX_TEST_PLAN_GROUP = "test-plan-group-execute:"; - public static final String QUEUE_PREFIX_TEST_PLAN = "test-plan-execute:"; - public static final String QUEUE_PREFIX_TEST_PLAN_CASE_TYPE = "test-plan-case-type:"; + 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:"; @@ -73,7 +70,7 @@ public class TestPlanExecuteService { if (CollectionUtils.isNotEmpty(rightfulIds)) { String runMode = request.getRunMode(); String queueId = IDGenerator.nextStr(); - String queueType = QUEUE_PREFIX_TEST_PLAN_GROUP; + String queueType = QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE; long pos = 0; List testPlanExecutionQueues = new ArrayList<>(); @@ -92,7 +89,7 @@ public class TestPlanExecuteService { testPlanId, runMode, request.getExecutionSource(), - IDGenerator.nextStr(), false + IDGenerator.nextStr() ) ); } @@ -124,7 +121,7 @@ public class TestPlanExecuteService { long pos = 0; List childrenQueue = new ArrayList<>(); String queueId = IDGenerator.nextStr(); - String queueType = QUEUE_PREFIX_TEST_PLAN; + String queueType = QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE; for (TestPlan child : children) { childrenQueue.add( new TestPlanExecutionQueue( @@ -138,23 +135,32 @@ public class TestPlanExecuteService { child.getId(), executionQueue.getRunMode(), executionQueue.getExecutionSource(), - IDGenerator.nextStr(), false + IDGenerator.nextStr() ) ); } - childrenQueue.forEach(childQueue -> { - redisTemplate.opsForList().rightPush(childQueue.getQueueType() + childQueue.getQueueId(), JSON.toJSONString(childQueue)); - }); - if (StringUtils.equalsIgnoreCase(executionQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { - //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); - executeTestPlanOrGroup(nextQueue); + if (CollectionUtils.isEmpty(childrenQueue)) { + //本次的测试计划组执行完成 + this.testPlanGroupQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - //并行 childrenQueue.forEach(childQueue -> { - executeTestPlanOrGroup(childQueue); + redisTemplate.opsForList().rightPush(childQueue.getQueueType() + childQueue.getQueueId(), JSON.toJSONString(childQueue)); }); + + // todo Song-cc 这里是否要生成测试计划组的集合报告,并且记录测试计划里用例的执行信息? + + if (StringUtils.equalsIgnoreCase(executionQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { + //串行 + TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); + executeTestPlanOrGroup(nextQueue); + } else { + //并行 + childrenQueue.forEach(childQueue -> { + executeTestPlanOrGroup(childQueue); + }); + } } + return executionQueue.getPrepareReportId(); } else { return this.executeTestPlan(executionQueue); @@ -165,7 +171,7 @@ public class TestPlanExecuteService { public String executeTestPlan(TestPlanExecutionQueue executionQueue) { TestPlan testPlan = testPlanMapper.selectByPrimaryKey(executionQueue.getSourceID()); TestPlanCollectionExample testPlanCollectionExample = new TestPlanCollectionExample(); - testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(testPlan.getId()); + testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(testPlan.getId()).andParentIdEqualTo("NONE"); testPlanCollectionExample.setOrderByClause("pos asc"); //过滤掉功能用例的测试集 List testPlanCollectionList = testPlanCollectionMapper.selectByExample(testPlanCollectionExample).stream().filter( @@ -190,34 +196,41 @@ public class TestPlanExecuteService { executionQueue.getQueueId(), executionQueue.getQueueType(), collection.getId(), - executionQueue.getRunMode(), + runMode, executionQueue.getExecutionSource(), - IDGenerator.nextStr(), false) + IDGenerator.nextStr()) ); } - childrenQueue.forEach(childQueue -> { - redisTemplate.opsForList().rightPush(childQueue.getQueueType() + childQueue.getQueueId(), JSON.toJSONString(childQueue)); - }); - // todo Song-cc 这里是否要生成测试计划报告,并且记录测试计划里用例的执行信息? - - //开始根据测试计划集合执行测试用例 - if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) { - //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); - this.executeByTestPlanCollection(nextQueue); + if (CollectionUtils.isEmpty(childrenQueue)) { + //本次的测试计划组执行完成 + this.testPlanExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - //并行 childrenQueue.forEach(childQueue -> { - this.executeByTestPlanCollection(childQueue); + redisTemplate.opsForList().rightPush(childQueue.getQueueType() + childQueue.getQueueId(), JSON.toJSONString(childQueue)); }); + + // todo Song-cc 这里是否要生成测试计划报告,并且记录测试计划里用例的执行信息? + + //开始根据测试计划集合执行测试用例 + if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) { + //串行 + TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); + this.executeByTestPlanCollection(nextQueue); + } else { + //并行 + childrenQueue.forEach(childQueue -> { + this.executeByTestPlanCollection(childQueue); + }); + } } + return executionQueue.getPrepareReportId(); } //执行测试集 -- 回调:collectionExecuteQueueFinish private void executeByTestPlanCollection(TestPlanExecutionQueue executionQueue) { - TestPlanCollection parentCollection = testPlanCollectionMapper.selectByPrimaryKey(executionQueue.getParentQueueId()); + TestPlanCollection parentCollection = testPlanCollectionMapper.selectByPrimaryKey(executionQueue.getSourceID()); TestPlanCollectionExample example = new TestPlanCollectionExample(); example.createCriteria().andParentIdEqualTo(executionQueue.getSourceID()); List childrenList = testPlanCollectionMapper.selectByExample(example); @@ -238,24 +251,30 @@ public class TestPlanExecuteService { executionQueue.getQueueId(), executionQueue.getQueueType(), collection.getId(), - executionQueue.getRunMode(), + collection.getExecuteMethod(), executionQueue.getExecutionSource(), - IDGenerator.nextStr(), false) + IDGenerator.nextStr()) ); } - childrenQueue.forEach(childQueue -> { - redisTemplate.opsForList().rightPush(childQueue.getQueueType() + childQueue.getQueueId(), JSON.toJSONString(childQueue)); - }); - if (StringUtils.equalsIgnoreCase(parentCollection.getExecuteMethod(), ApiBatchRunMode.SERIAL.name())) { - //串行 - TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); - this.executeCase(nextQueue); + if (CollectionUtils.isEmpty(childrenQueue)) { + //本次的测试集执行完成 + this.caseTypeExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); } else { - //并行 childrenQueue.forEach(childQueue -> { - this.executeCase(childQueue); + redisTemplate.opsForList().rightPush(childQueue.getQueueType() + childQueue.getQueueId(), JSON.toJSONString(childQueue)); }); + if (StringUtils.equalsIgnoreCase(parentCollection.getExecuteMethod(), ApiBatchRunMode.SERIAL.name())) { + //串行 + TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); + this.executeCase(nextQueue); + } else { + //并行 + childrenQueue.forEach(childQueue -> { + this.executeCase(childQueue); + }); + } } + } // todo @Chen jianxing 执行用例 @@ -277,8 +296,9 @@ public class TestPlanExecuteService { //测试集执行完成 public void collectionExecuteQueueFinish(String queueID, String queueType) { TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); - if (StringUtils.isNotBlank(nextQueue.getQueueId())) { - if (!nextQueue.isLastFinished()) { + if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { + //串行时,由于是先拿出节点再判断执行,所以要判断节点的isExecuteFinish + if (!nextQueue.isExecuteFinish()) { try { this.executeNextNode(nextQueue); } catch (Exception e) { @@ -286,17 +306,20 @@ public class TestPlanExecuteService { } } else { //当前测试集执行完毕 - this.caseTypeExecuteQueueFinish(nextQueue.getParentQueueId(), nextQueue.getParentQueueType()); + this.queueExecuteFinish(nextQueue); } - + } else if (nextQueue.isLastOne()) { + //并行时,调用回调时意味着执行结束,所以判断是否是当前队列最后一个从而结束队列 + this.queueExecuteFinish(nextQueue); } } //测试计划中当前用例类型的全部执行完成 private void caseTypeExecuteQueueFinish(String queueID, String queueType) { TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); - if (StringUtils.isNotBlank(nextQueue.getQueueId())) { - if (!nextQueue.isLastFinished()) { + if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { + //串行时,由于是先拿出节点再判断执行,所以要判断节点的isExecuteFinish + if (!nextQueue.isExecuteFinish()) { try { this.executeNextNode(nextQueue); } catch (Exception e) { @@ -304,43 +327,58 @@ public class TestPlanExecuteService { } } else { //当前测试计划执行完毕 - this.testPlanExecuteQueueFinish(nextQueue.getParentQueueId(), nextQueue.getParentQueueType()); + this.queueExecuteFinish(nextQueue); } + } else if (nextQueue.isLastOne()) { + //并行时,调用回调时意味着执行结束,所以判断是否是当前队列最后一个从而结束队列 + this.queueExecuteFinish(nextQueue); } } //测试计划执行完成 private void testPlanExecuteQueueFinish(String queueID, String queueType) { TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); - if (StringUtils.isNotBlank(nextQueue.getQueueId())) { - if (!nextQueue.isLastFinished()) { + if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { + if (!nextQueue.isExecuteFinish()) { try { this.executeNextNode(nextQueue); } catch (Exception e) { this.testPlanExecuteQueueFinish(nextQueue.getQueueId(), nextQueue.getQueueType()); } } else { - this.testPlanGroupQueueFinish(nextQueue.getParentQueueId(), nextQueue.getParentQueueType()); + this.queueExecuteFinish(nextQueue); } + } else if (nextQueue.isLastOne()) { + //并行时,调用回调时意味着执行结束,所以判断是否是当前队列最后一个从而结束队列 + this.queueExecuteFinish(nextQueue); } } //测试计划批量执行队列节点执行完成 - private void testPlanGroupQueueFinish(String queueId, String queueType) { - TestPlanExecutionQueue nextQueue = getNextQueue(queueId, queueType); - if (nextQueue != null) { - try { - this.executeNextNode(nextQueue); - } catch (Exception e) { - this.testPlanGroupQueueFinish(queueId, queueType); - } + private void testPlanGroupQueueFinish(String queueID, String queueType) { + TestPlanExecutionQueue nextQueue = getNextQueue(queueID, queueType); + if (nextQueue == null) { + return; } + if (StringUtils.equalsIgnoreCase(nextQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { + if (!nextQueue.isExecuteFinish()) { + try { + this.executeNextNode(nextQueue); + } catch (Exception e) { + this.testPlanGroupQueueFinish(queueID, queueType); + } + } + } else { + //并行时,调用回调时意味着执行结束,所以判断是否是当前队列最后一个从而结束队列 + this.queueExecuteFinish(nextQueue); + } + } private void executeNextNode(TestPlanExecutionQueue queue) { - if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP)) { + if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) { this.executeTestPlanOrGroup(queue); - } else if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN)) { + } else if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)) { this.executeTestPlan(queue); } else if (StringUtils.equalsIgnoreCase(queue.getQueueType(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)) { this.executeByTestPlanCollection(queue); @@ -350,9 +388,11 @@ public class TestPlanExecuteService { } private void queueExecuteFinish(TestPlanExecutionQueue queue) { - if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP)) { + if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) { + // todo Song-cc 测试计划组集合报告生成 this.testPlanGroupQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); - } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN)) { + } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)) { + // todo Song-cc 测试计划报告计算 this.testPlanExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)) { this.caseTypeExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); @@ -369,10 +409,9 @@ public class TestPlanExecuteService { String queueKey = this.genQueueKey(queueId, queueType); ListOperations listOps = redisTemplate.opsForList(); - String queueDetail = listOps.leftPop(queueKey); if (StringUtils.isBlank(queueDetail)) { - // 重试3次获取 + // 重试2次获取 for (int i = 0; i < 3; i++) { queueDetail = redisTemplate.opsForList().leftPop(queueKey); if (StringUtils.isNotBlank(queueDetail)) { @@ -387,18 +426,22 @@ public class TestPlanExecuteService { if (StringUtils.isNotBlank(queueDetail)) { TestPlanExecutionQueue returnQueue = JSON.parseObject(queueDetail, TestPlanExecutionQueue.class); - Long size = getQueueSize(queueId); + 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); - redisTemplate.opsForValue().setIfAbsent(genQueueKey(LAST_QUEUE_PREFIX, queueKey), JSON.toJSONString(returnQueue), 1, TimeUnit.DAYS); } return returnQueue; } else { - String lastQueueJson = redisTemplate.opsForValue().getAndDelete(genQueueKey(LAST_QUEUE_PREFIX, queueKey)); + String lastQueueJson = redisTemplate.opsForValue().getAndDelete(genQueueKey(queueKey, LAST_QUEUE_PREFIX)); if (StringUtils.isNotBlank(lastQueueJson)) { TestPlanExecutionQueue nextQueue = JSON.parseObject(lastQueueJson, TestPlanExecutionQueue.class); - nextQueue.setLastFinished(true); + nextQueue.setExecuteFinish(true); return nextQueue; } } @@ -413,11 +456,6 @@ public class TestPlanExecuteService { redisTemplate.delete(queueKey); } - private Long getQueueSize(String queueKey) { - ListOperations listOps = redisTemplate.opsForList(); - return listOps.size(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/TestPlanService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java index 18f727a197..276e5902ed 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java @@ -105,6 +105,8 @@ public class TestPlanService extends TestPlanBaseUtilsService { */ public TestPlan add(TestPlanCreateRequest testPlanCreateRequest, String operator, String requestUrl, String requestMethod) { TestPlan testPlan = savePlanDTO(testPlanCreateRequest, operator); + //自动生成测试规划 + this.initDefaultPlanCollection(testPlan.getId(), operator); testPlanLogService.saveAddLog(testPlan, operator, requestUrl, requestMethod); return testPlan; } @@ -139,7 +141,8 @@ public class TestPlanService extends TestPlanBaseUtilsService { testPlanConfig.setRepeatCase(createOrCopyRequest.isRepeatCase()); testPlanConfig.setPassThreshold(createOrCopyRequest.getPassThreshold()); - handleAssociateCase(createOrCopyRequest.getBaseAssociateCaseRequest(), operator, createTestPlan); + //正式版本不再支持这种操作 + // handleAssociateCase(createOrCopyRequest.getBaseAssociateCaseRequest(), operator, createTestPlan); testPlanMapper.insert(createTestPlan); testPlanConfigMapper.insertSelective(testPlanConfig); 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 new file mode 100644 index 0000000000..358b987f92 --- /dev/null +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanExecuteTests.java @@ -0,0 +1,293 @@ +package io.metersphere.plan.controller; + +import io.metersphere.plan.domain.TestPlan; +import io.metersphere.plan.domain.TestPlanCollection; +import io.metersphere.plan.domain.TestPlanCollectionExample; +import io.metersphere.plan.domain.TestPlanConfig; +import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest; +import io.metersphere.plan.dto.request.TestPlanCreateRequest; +import io.metersphere.plan.dto.request.TestPlanExecuteRequest; +import io.metersphere.plan.mapper.TestPlanCollectionMapper; +import io.metersphere.plan.mapper.TestPlanConfigMapper; +import io.metersphere.plan.mapper.TestPlanMapper; +import io.metersphere.plan.service.TestPlanExecuteService; +import io.metersphere.plan.service.TestPlanTestService; +import io.metersphere.project.domain.Project; +import io.metersphere.sdk.constants.ApiBatchRunMode; +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.constants.TestPlanConstants; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.base.BaseTest; +import io.metersphere.system.controller.handler.ResultHolder; +import io.metersphere.system.dto.AddProjectRequest; +import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.service.CommonProjectService; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.*; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureMockMvc +public class TestPlanExecuteTests extends BaseTest { + private static Project project; + private static List testPlanGroupList = new ArrayList<>(); + private static TestPlan allSerialGroup; + private static TestPlan allParallelGroup; + private static TestPlan noGroupPlan; + + private static final String URL_POST_TEST_PLAN_SINGLE_EXECUTE = "/test-plan-execute/single"; + private static final String URL_POST_TEST_PLAN_BATCH_EXECUTE = "/test-plan-execute/batch"; + + @Resource + private CommonProjectService commonProjectService; + @Resource + private TestPlanMapper testPlanMapper; + @Resource + private TestPlanTestService testPlanTestService; + @Resource + private TestPlanExecuteService testPlanExecuteService; + @Resource + private TestPlanConfigMapper testPlanConfigMapper; + @Resource + private TestPlanCollectionMapper testPlanCollectionMapper; + + public static final String[] EXECUTE_QUEUE_PREFIX = new String[]{ + "test-plan-batch-execute:", "test-plan-group-execute:", "test-plan-case-type-execute:", "test-plan-collection-execute:" + }; + public static final String QUEUE_PREFIX_TEST_PLAN_COLLECTION = "test-plan-collection-execute:"; + + @Test + @Order(1) + public void initData() throws Exception { + AddProjectRequest initProject = new AddProjectRequest(); + initProject.setOrganizationId("100001"); + initProject.setName("测试计划执行专用项目"); + initProject.setDescription("建国创建的测试计划执行专用项目"); + initProject.setEnable(true); + initProject.setUserIds(List.of("admin")); + project = commonProjectService.add(initProject, "admin", "/organization-project/add", OperationLogModule.SETTING_ORGANIZATION_PROJECT); + testPlanTestService.resetProjectModule(project, new String[]{"workstation", "testPlan", "bugManagement", "caseManagement", "apiTest", "uiTest", "loadTest"}); + + for (int i = 0; i < 4; i++) { + TestPlanCreateRequest request = new TestPlanCreateRequest(); + request.setProjectId(project.getId()); + request.setType(TestPlanConstants.TEST_PLAN_TYPE_GROUP); + request.setName("testPlanGroupForExecute:" + i); + request.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + MvcResult mvcResult = this.requestPostWithOkAndReturn("/test-plan/add", request); + String returnStr = mvcResult.getResponse().getContentAsString(); + ResultHolder holder = JSON.parseObject(returnStr, ResultHolder.class); + String returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); + testPlanGroupList.add(testPlanMapper.selectByPrimaryKey(returnId)); + + if (i == 0) { + allSerialGroup = testPlanMapper.selectByPrimaryKey(returnId); + //第一个全部都是串行 + request = new TestPlanCreateRequest(); + request.setProjectId(project.getId()); + request.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); + request.setName("testPlanForExecute:" + i); + request.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + request.setGroupId(returnId); + mvcResult = this.requestPostWithOkAndReturn("/test-plan/add", request); + returnStr = mvcResult.getResponse().getContentAsString(); + holder = JSON.parseObject(returnStr, ResultHolder.class); + returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); + + TestPlanConfig testPlanConfig = new TestPlanConfig(); + testPlanConfig.setTestPlanId(returnId); + testPlanConfig.setCaseRunMode(ApiBatchRunMode.SERIAL.name()); + testPlanConfigMapper.updateByPrimaryKeySelective(testPlanConfig); + + TestPlanCollectionExample testPlanCollectionExample = new TestPlanCollectionExample(); + testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(returnId); + List testPlanCollections = testPlanCollectionMapper.selectByExample(testPlanCollectionExample); + testPlanCollections.forEach(item -> { + item.setExecuteMethod(ApiBatchRunMode.SERIAL.name()); + testPlanCollectionMapper.updateByPrimaryKeySelective(item); + }); + + } else if (i == 1) { + allParallelGroup = testPlanMapper.selectByPrimaryKey(returnId); + + //第二个全部都是并行 + request = new TestPlanCreateRequest(); + request.setProjectId(project.getId()); + request.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); + request.setName("testPlanForExecute:" + i); + request.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + request.setGroupId(returnId); + mvcResult = this.requestPostWithOkAndReturn("/test-plan/add", request); + returnStr = mvcResult.getResponse().getContentAsString(); + holder = JSON.parseObject(returnStr, ResultHolder.class); + returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); + + + TestPlanConfig testPlanConfig = new TestPlanConfig(); + testPlanConfig.setTestPlanId(returnId); + testPlanConfig.setCaseRunMode(ApiBatchRunMode.PARALLEL.name()); + testPlanConfigMapper.updateByPrimaryKeySelective(testPlanConfig); + + TestPlanCollectionExample testPlanCollectionExample = new TestPlanCollectionExample(); + testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(returnId); + List testPlanCollections = testPlanCollectionMapper.selectByExample(testPlanCollectionExample); + testPlanCollections.forEach(item -> { + item.setExecuteMethod(ApiBatchRunMode.PARALLEL.name()); + testPlanCollectionMapper.updateByPrimaryKeySelective(item); + }); + } else if (i == 2) { + // 第三个走默认方法 + request = new TestPlanCreateRequest(); + request.setProjectId(project.getId()); + request.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); + request.setName("testPlanForExecute:" + i); + request.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + request.setGroupId(returnId); + mvcResult = this.requestPostWithOkAndReturn("/test-plan/add", request); + returnStr = mvcResult.getResponse().getContentAsString(); + holder = JSON.parseObject(returnStr, ResultHolder.class); + returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); + } + } + + TestPlanCreateRequest request = new TestPlanCreateRequest(); + request.setProjectId(project.getId()); + request.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); + request.setName("testPlanForSingleExecute"); + request.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + MvcResult mvcResult = this.requestPostWithOkAndReturn("/test-plan/add", request); + String returnStr = mvcResult.getResponse().getContentAsString(); + ResultHolder holder = JSON.parseObject(returnStr, ResultHolder.class); + String returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); + noGroupPlan = testPlanMapper.selectByPrimaryKey(returnId); + } + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Test + @Order(2) + public void executeTest() throws Exception { + if (CollectionUtils.isEmpty(testPlanGroupList)) { + this.initData(); + } + + List batchExecuteIds = new ArrayList<>(); + batchExecuteIds.add(noGroupPlan.getId()); + for (TestPlan group : testPlanGroupList) { + batchExecuteIds.add(group.getId()); + } + + // 串行4个计划组和一个计划 + this.executeBatch(batchExecuteIds, ApiBatchRunMode.SERIAL.name()); + //并行4个计划组和一个计划 + this.executeBatch(batchExecuteIds, ApiBatchRunMode.PARALLEL.name()); + + + //单独串行一个计划组 + this.executeOne(allSerialGroup.getId(), ApiBatchRunMode.SERIAL.name()); + //单独串行一个计划 + this.executeOne(noGroupPlan.getId(), ApiBatchRunMode.SERIAL.name()); + + //单独并行一个计划组 + this.executeOne(allParallelGroup.getId(), ApiBatchRunMode.PARALLEL.name()); + //单独并行一个计划 + this.executeOne(noGroupPlan.getId(), ApiBatchRunMode.PARALLEL.name()); + } + + private void executeBatch(List execIds, String runMode) throws Exception { + + TestPlanBatchExecuteRequest batchExecuteRequest = new TestPlanBatchExecuteRequest(); + batchExecuteRequest.setExecuteIds(execIds); + batchExecuteRequest.setProjectId(project.getId()); + batchExecuteRequest.setRunMode(runMode); + + this.requestPostWithOk(URL_POST_TEST_PLAN_BATCH_EXECUTE, batchExecuteRequest); + + //检查队列 + List allQueueIds = new ArrayList<>(); + List collectionQueueIdList = new ArrayList<>(); + for (String executeQueue : EXECUTE_QUEUE_PREFIX) { + Set keys = stringRedisTemplate.keys("*" + executeQueue + "*"); + allQueueIds.addAll(keys); + for (String key : keys) { + if (StringUtils.equalsIgnoreCase(executeQueue, QUEUE_PREFIX_TEST_PLAN_COLLECTION)) { + String[] keyArr = key.split(QUEUE_PREFIX_TEST_PLAN_COLLECTION); + collectionQueueIdList.add(keyArr[keyArr.length - 1]); + } + } + } + Assertions.assertTrue(!collectionQueueIdList.isEmpty()); + Assertions.assertTrue(!allQueueIds.isEmpty()); + + this.checkRedisKeyEmpty(allQueueIds, collectionQueueIdList); + + } + + private void executeOne(String id, String runMode) throws Exception { + TestPlanExecuteRequest executeRequest = new TestPlanExecuteRequest(); + executeRequest.setExecuteId(id); + executeRequest.setRunMode(runMode); + + this.requestPostWithOk(URL_POST_TEST_PLAN_SINGLE_EXECUTE, executeRequest); + + //检查队列 + List allQueueIds = new ArrayList<>(); + List collectionQueueIdList = new ArrayList<>(); + for (String executeQueue : EXECUTE_QUEUE_PREFIX) { + Set keys = stringRedisTemplate.keys("*" + executeQueue + "*"); + allQueueIds.addAll(keys); + for (String key : keys) { + if (StringUtils.equalsIgnoreCase(executeQueue, QUEUE_PREFIX_TEST_PLAN_COLLECTION)) { + String[] keyArr = key.split(QUEUE_PREFIX_TEST_PLAN_COLLECTION); + collectionQueueIdList.add(keyArr[keyArr.length - 1]); + } + } + } + Assertions.assertTrue(!collectionQueueIdList.isEmpty()); + Assertions.assertTrue(!allQueueIds.isEmpty()); + + this.checkRedisKeyEmpty(allQueueIds, collectionQueueIdList); + + } + + private void checkRedisKeyEmpty(List allQueueIds, List collectionQueueIdList) throws Exception { + //本条测试用例中,最多传入的是5个批量执行。(4个测试计划组和1个测试计划, 有一个测试计划组下面没有测试计划), + // 串行的话模拟其中8个测试集的回调。 既while中最多循环8次进行回调,每次回调只传入1个测试集,防止并行/串行的干扰 + int foreachIndex = 8; + long timeStem = System.currentTimeMillis(); + while (foreachIndex > 0 && !allQueueIds.isEmpty()) { + + String collectionFinishQueueIds = collectionQueueIdList.getFirst(); + //模拟执行完成之后的回调 + testPlanExecuteService.collectionExecuteQueueFinish(collectionFinishQueueIds, QUEUE_PREFIX_TEST_PLAN_COLLECTION); + + allQueueIds = new ArrayList<>(); + collectionQueueIdList = new ArrayList<>(); + + for (String executeQueue : EXECUTE_QUEUE_PREFIX) { + Set keys = stringRedisTemplate.keys("*" + executeQueue + "*"); + allQueueIds.addAll(keys); + for (String key : keys) { + if (StringUtils.equalsIgnoreCase(executeQueue, QUEUE_PREFIX_TEST_PLAN_COLLECTION)) { + String[] keyArr = key.split(QUEUE_PREFIX_TEST_PLAN_COLLECTION); + collectionQueueIdList.add(keyArr[keyArr.length - 1]); + } + } + } + foreachIndex--; + } + + Assertions.assertTrue(allQueueIds.isEmpty()); + } +} diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java index 1a6685d994..3965fef15d 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java @@ -119,8 +119,6 @@ public class TestPlanTests extends BaseTest { private static final String URL_POST_TEST_PLAN_BATCH_DELETE = "/test-plan/batch-delete"; private static final String URL_POST_TEST_PLAN_SCHEDULE = "/test-plan/schedule-config"; private static final String URL_POST_TEST_PLAN_SCHEDULE_DELETE = "/test-plan/schedule-config-delete/%s"; - private static final String URL_POST_TEST_PLAN_SINGLE_EXECUTE = "/test-plan-execute/single"; - private static final String URL_POST_TEST_PLAN_BATCH_EXECUTE = "/test-plan-execute/batch"; //测试计划资源-功能用例 private static final String URL_POST_RESOURCE_CASE_ASSOCIATION = "/test-plan/association"; @@ -531,7 +529,6 @@ public class TestPlanTests extends BaseTest { assert a1Node != null & a2Node != null & a3Node != null & a1a1Node != null & a1b1Node != null; TestPlanCreateRequest request = new TestPlanCreateRequest(); request.setProjectId(project.getId()); - request.setTestPlanning(false); BaseAssociateCaseRequest associateCaseRequest = new BaseAssociateCaseRequest(); request.setBaseAssociateCaseRequest(associateCaseRequest); @@ -1482,26 +1479,6 @@ public class TestPlanTests extends BaseTest { Assertions.assertTrue(statisticsResponses.getFirst().getScheduleConfig() == null); } - @Test - @Order(71) - public void executeTest() throws Exception { - TestPlanExecuteRequest executeRequest = new TestPlanExecuteRequest(); - executeRequest.setExecuteId(groupTestPlanId7); - //串行 - this.requestPostWithOk(URL_POST_TEST_PLAN_SINGLE_EXECUTE, executeRequest); - //并行 - executeRequest.setRunMode(ApiBatchRunMode.PARALLEL.name()); - this.requestPostWithOk(URL_POST_TEST_PLAN_SINGLE_EXECUTE, executeRequest); - - TestPlanBatchExecuteRequest batchExecuteRequest = new TestPlanBatchExecuteRequest(); - batchExecuteRequest.setExecuteIds(Collections.singletonList(groupTestPlanId7)); - batchExecuteRequest.setProjectId(project.getId()); - //串行 - this.requestPostWithOk(URL_POST_TEST_PLAN_BATCH_EXECUTE, batchExecuteRequest); - //并行 - batchExecuteRequest.setRunMode(ApiBatchRunMode.PARALLEL.name()); - this.requestPostWithOk(URL_POST_TEST_PLAN_BATCH_EXECUTE, batchExecuteRequest); - } @Test @Order(81) public void copyTestPlan() throws Exception { @@ -2139,7 +2116,6 @@ public class TestPlanTests extends BaseTest { public void testAdd() throws Exception { TestPlanCreateRequest request = new TestPlanCreateRequest(); request.setProjectId(project.getId()); - request.setTestPlanning(false); BaseAssociateCaseRequest associateCaseRequest = new BaseAssociateCaseRequest(); associateCaseRequest.setFunctionalSelectIds(Arrays.asList("wx_fc_1", "wx_fc_2")); request.setBaseAssociateCaseRequest(associateCaseRequest);