feat(接口测试): 添加执行队列

This commit is contained in:
fit2-zhao 2024-01-08 19:34:33 +08:00 committed by Craftsman
parent 8c2522ab26
commit 0df503b683
5 changed files with 313 additions and 2 deletions

View File

@ -0,0 +1,58 @@
package io.metersphere.sdk.dto.queue;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
@Data
public class ExecutionQueue implements Serializable {
/**
* 队列ID, 用于区分不同的队列,如果测试计划执行队列ID为测试计划报告ID
*/
private String queueId;
/**
* 报告类型/测试计划类型/测试用例类型/测试集类型/场景集合类型
*/
private String reportType;
/**
* 运行模式
*/
private String runMode;
/**
* 资源池ID
*/
private String poolId;
/**
* 创建时间
*/
private Long createTime;
/**
* 是否失败继续
*/
private Boolean failure;
/**
* 开启重试
*/
private Boolean retryEnable;
/**
* 重试次数
*/
private Long retryNumber;
/**
* 环境key= projectIDvalue=envID
*/
private Map<String, String> envMap;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,38 @@
package io.metersphere.sdk.dto.queue;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
@Data
public class ExecutionQueueDetail implements Serializable {
/**
* 资源id每个资源在同一个运行队列中唯一
*/
private String resourceId;
/**
* 排序
*/
private Integer sort;
/**
* 当前资源产生的执行报告id
*/
private String reportId;
/**
* 资源类型 / APICASEPLAN_CASE,PLAN_SCENARIOAPI_SCENARIO
*/
private String resourceType;
/**
* 环境key= projectIDvalue=envID优先使用Queue上的环境如果没有则使用资源上的环境
*/
private Map<String, String> envMap;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,101 @@
package io.metersphere.api.service.queue;
import io.metersphere.sdk.dto.queue.ExecutionQueue;
import io.metersphere.sdk.dto.queue.ExecutionQueueDetail;
import io.metersphere.sdk.util.JSON;
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.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.LinkedList;
import java.util.List;
@Service
public class ApiExecutionQueueService {
public static final String QUEUE_PREFIX = "queue:";
public static final String QUEUE_DETAIL_PREFIX = "queue:detail:";
@Resource
private RedisTemplate<String, String> redisTemplate;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertQueue(ExecutionQueue queue, List<ExecutionQueueDetail> queues) {
// 保存队列信息
redisTemplate.opsForValue().setIfAbsent(QUEUE_PREFIX + queue.getQueueId(), JSON.toJSONString(queue));
// 保存队列详情信息
queues.forEach(n -> redisTemplate.opsForList().rightPush(QUEUE_DETAIL_PREFIX + queue.getQueueId(), JSON.toJSONString(n)));
}
/**
* 获取下一个节点
*/
public ExecutionQueueDetail getNextDetail(String queueId) throws Exception {
String queueKey = QUEUE_DETAIL_PREFIX + queueId;
ListOperations<String, String> listOps = redisTemplate.opsForList();
String queueDetail = listOps.leftPop(queueKey);
if (StringUtils.isBlank(queueDetail)) {
// 重试3次获取
for (int i = 0; i < 3; i++) {
queueDetail = redisTemplate.opsForList().leftPop(queueKey);
if (StringUtils.isNotBlank(queueDetail)) {
break;
}
Thread.sleep(1000);
}
}
if (StringUtils.isNotBlank(queueDetail)) {
// 将节点重新放回列表尾部实现轮询
return JSON.parseObject(queueDetail, ExecutionQueueDetail.class);
}
// 整体获取完清理队列
redisTemplate.delete(queueKey);
redisTemplate.delete(QUEUE_PREFIX + queueId);
return null;
}
/**
* 获取所有节点
*/
public List<ExecutionQueueDetail> getDetails(String queueId) {
String queueKey = QUEUE_DETAIL_PREFIX + queueId;
List<ExecutionQueueDetail> details = new LinkedList<>();
ListOperations<String, String> listOps = redisTemplate.opsForList();
Long listSize = listOps.size(queueKey);
if (listSize == null) {
return details;
}
for (int i = 0; i < listSize; i++) {
String element = listOps.index(queueKey, i);
details.add(JSON.parseObject(element, ExecutionQueueDetail.class));
}
return details;
}
/**
* 获取队列信息
*/
public ExecutionQueue getQueue(String queueId) {
String queue = redisTemplate.opsForValue().get(QUEUE_PREFIX + queueId);
if (StringUtils.isNotBlank(queue)) {
return JSON.parseObject(queue, ExecutionQueue.class);
}
return null;
}
public Long size(String queueId) {
ListOperations<String, String> listOps = redisTemplate.opsForList();
String queueKey = QUEUE_DETAIL_PREFIX + queueId;
return listOps.size(queueKey);
}
}

View File

@ -0,0 +1,115 @@
package io.metersphere.api.service;
import io.metersphere.api.service.queue.ApiExecutionQueueService;
import io.metersphere.sdk.dto.queue.ExecutionQueue;
import io.metersphere.sdk.dto.queue.ExecutionQueueDetail;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.mockito.Mock;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class ApiExecutionQueueServiceTest {
@Mock
private RedisTemplate<String, String> redisTemplate;
@Mock
private ListOperations<String, String> listOps;
@Resource
private ApiExecutionQueueService apiExecutionQueueService;
@Test
@Order(1)
void testInsertQueue() {
ExecutionQueue queue = new ExecutionQueue();
queue.setQueueId("queueId1");
queue.setReportType("REPORT");
queue.setRunMode("SEQUENTIAL");
queue.setPoolId("poolId1");
queue.setCreateTime(System.currentTimeMillis());
queue.setFailure(true);
queue.setRetryEnable(true);
queue.setRetryNumber(3L);
queue.setEnvMap(Map.of("projectID1", "envID1", "projectID2", "envID2"));
ExecutionQueueDetail queueDetail1 = new ExecutionQueueDetail();
queueDetail1.setResourceId("resourceId1");
queueDetail1.setSort(1);
queueDetail1.setReportId("reportId1");
queueDetail1.setResourceType("API");
queueDetail1.setEnvMap(Map.of("projectID1", "envID1", "projectID2", "envID2"));
ExecutionQueueDetail queueDetail2 = new ExecutionQueueDetail();
queueDetail2.setResourceId("resourceId2");
queueDetail2.setSort(2);
queueDetail2.setReportId("reportId2");
queueDetail2.setResourceType("CASE");
queueDetail2.setEnvMap(Map.of("projectID1", "envID1", "projectID2", "envID2"));
List<ExecutionQueueDetail> queueDetails = List.of(queueDetail1, queueDetail2);
when(redisTemplate.opsForList()).thenReturn(listOps);
apiExecutionQueueService.insertQueue(queue, queueDetails);
}
@Test
@Order(2)
void testGetNextDetail() throws Exception {
String queueId = "queueId1";
System.out.println("start : " + apiExecutionQueueService.size(queueId));
when(redisTemplate.opsForList()).thenReturn(listOps);
when(listOps.leftPop(queueId)).thenReturn("{\"resourceId\":\"resourceId1\"}");
ExecutionQueueDetail result = apiExecutionQueueService.getNextDetail(queueId);
assertNotNull(result);
assertEquals("resourceId1", result.getResourceId());
System.out.println("end : " + apiExecutionQueueService.size(queueId));
}
@Test
@Order(3)
void testGetDetails() throws Exception {
String queueId = "queueId1";
when(redisTemplate.opsForList()).thenReturn(listOps);
when(listOps.size(queueId)).thenReturn(2L);
when(listOps.index(eq(queueId), anyLong())).thenReturn("{\"resourceId\":\"resourceId1\"}", "{\"resourceId\":\"resourceId2\"}");
List<ExecutionQueueDetail> result = apiExecutionQueueService.getDetails(queueId);
assertNotNull(result);
assertEquals(1, result.size());
}
@Test
@Order(4)
void testGetQueue() throws Exception {
String queueId = "queueId1";
ExecutionQueue result = apiExecutionQueueService.getQueue(queueId);
assertNotNull(result);
assertEquals("queueId1", result.getQueueId());
}
}

View File

@ -1,7 +1,6 @@
package io.metersphere.api.config;
package io.metersphere.api.service;
import io.metersphere.api.dto.NodeDTO;
import io.metersphere.api.service.RoundRobinService;
import io.metersphere.sdk.util.LogUtils;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;