feat(接口测试): 添加执行队列
This commit is contained in:
parent
8c2522ab26
commit
0df503b683
|
@ -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= projectID,value=envID
|
||||||
|
*/
|
||||||
|
private Map<String, String> envMap;
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源类型 / API,CASE,PLAN_CASE,PLAN_SCENARIO,API_SCENARIO
|
||||||
|
*/
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境,key= projectID,value=envID,优先使用Queue上的环境,如果没有则使用资源上的环境
|
||||||
|
*/
|
||||||
|
private Map<String, String> envMap;
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package io.metersphere.api.config;
|
package io.metersphere.api.service;
|
||||||
|
|
||||||
import io.metersphere.api.dto.NodeDTO;
|
import io.metersphere.api.dto.NodeDTO;
|
||||||
import io.metersphere.api.service.RoundRobinService;
|
|
||||||
import io.metersphere.sdk.util.LogUtils;
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
Loading…
Reference in New Issue