fix(测试计划): 修复测试计划中单用例调试结果偶发收不到问题

--bug=1026377 --user=赵勇 【测试计划】接口测试用例执行,禁用项目管理-接口执行资源池后,执行接口用例,local无结果,k8s资源池正常输出 https://www.tapd.cn/55049933/s/1373857

Signed-off-by: fit2-zhao <yong.zhao@fit2cloud.com>
This commit is contained in:
fit2-zhao 2023-05-22 15:12:29 +08:00 committed by fit2-zhao
parent c0c8ba5aab
commit 77490c32a1
8 changed files with 76 additions and 41 deletions

View File

@ -71,6 +71,7 @@ public class ApiCaseExecuteService {
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
private RedisTemplateService redisTemplateService;
/**
* 测试计划case执行
*
@ -134,7 +135,7 @@ public class ApiCaseExecuteService {
executeQueue.put(testPlanApiCase.getId(), report);
responseDTOS.add(new MsExecResponseDTO(testPlanApiCase.getId(), report.getId(), request.getTriggerMode()));
// 执行中资源锁住防止重复更新造成LOCK WAIT
redisTemplateService.lock(testPlanApiCase.getId());
redisTemplateService.lock(testPlanApiCase.getId(), report.getId());
LoggerUtil.info("预生成测试用例结果报告:" + report.getName(), report.getId());
}

View File

@ -161,7 +161,7 @@ public class ApiScenarioExecuteService {
DBTestQueue executionQueue = apiExecutionQueueService.add(
executeQueue, request.getConfig().getResourcePoolId(),
ApiRunMode.SCENARIO.name(), planReportId, reportType,
request.getRunMode(), request.getConfig());
request.getRunMode(), request.getConfig());
// 预生成报告
if (!request.isRerun() && !GenerateHashTreeUtil.isSetReport(request.getConfig())) {
@ -347,7 +347,7 @@ public class ApiScenarioExecuteService {
apiScenarioReportStructureService.save(scenario, report.getId(), request.getConfig() != null ? request.getConfig().getReportType() : null);
}
// 执行中资源锁住防止重复更新造成LOCK WAIT
redisTemplateService.lock(planApiScenario.getId());
redisTemplateService.lock(planApiScenario.getId(), report.getId());
// 重置报告ID
reportId = UUID.randomUUID().toString();
}

View File

@ -1,15 +1,18 @@
package io.metersphere.service;
import io.metersphere.api.jmeter.utils.JmxFileUtil;
import io.metersphere.commons.enums.LockEnum;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Service
@ -58,29 +61,42 @@ public class RedisTemplateService {
/**
* 加锁
*/
public boolean lock(String key) {
return redisTemplate.opsForValue().setIfAbsent(StringUtils.join(PRX, key), LockEnum.LOCK.name());
public boolean lock(String key, String value) {
boolean hasReport = redisTemplate.opsForValue().setIfAbsent(
StringUtils.join(PRX, key),
value,
TIME_OUT,
TimeUnit.MINUTES);
if (!hasReport) {
redisTemplate.opsForValue().setIfPresent(
StringUtils.join(PRX, key),
value,
TIME_OUT,
TimeUnit.MINUTES);
}
return hasReport;
}
public boolean has(String key) {
public boolean has(String key, String reportId) {
try {
Object value = redisTemplate.opsForValue().get(StringUtils.join(PRX, key));
if (ObjectUtils.isNotEmpty(value)) {
if (StringUtils.equals(LockEnum.LOCK.name(), String.valueOf(value))) {
// 设置一分钟超时
redisTemplate.opsForValue().setIfPresent(StringUtils.join(PRX, key),
LockEnum.WAITING.name(), TIME_OUT, TimeUnit.SECONDS);
return false;
}
} else {
redisTemplate.opsForValue().setIfAbsent(StringUtils.join(PRX, key),
LockEnum.WAITING.name(), TIME_OUT, TimeUnit.SECONDS);
return false;
}
return true;
return ObjectUtils.isNotEmpty(value) && StringUtils.equals(reportId, String.valueOf(value));
} catch (Exception e) {
LogUtil.error(e);
}
return false;
}
/**
* 解锁
*/
public boolean unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(StringUtils.join(PRX, key)), value);
if (Objects.equals(1L, result)) {
return true;
}
return false;
}
}

View File

@ -21,4 +21,11 @@ public class ApiCaseResultService {
resultMapper.sqlInsert(new LinkedList<>(executeQueue.values()));
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void batchSave(ApiDefinitionExecResultWithBLOBs result) {
if (result != null) {
resultMapper.sqlInsert(new LinkedList<>(){{this.add(result);}});
}
}
}

View File

@ -103,12 +103,13 @@ public class ApiDefinitionExecResultService {
User user = getUser(dto, result);
//如果是测试计划用例更新接口用例的上次执行结果
TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(dto.getTestId());
if (testPlanApiCase != null && !redisTemplateService.has(dto.getTestId())) {
if (testPlanApiCase != null && redisTemplateService.has(dto.getTestId(), dto.getReportId())) {
ApiTestCaseWithBLOBs apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId());
if (apiTestCase != null) {
apiTestCase.setLastResultId(dto.getReportId());
apiTestCaseMapper.updateByPrimaryKeySelective(apiTestCase);
}
redisTemplateService.unlock(dto.getTestId(), dto.getReportId());
}
// 发送通知
LoggerUtil.info("执行结果【 " + result.getName() + " 】入库存储完成");
@ -223,13 +224,11 @@ public class ApiDefinitionExecResultService {
}
public void setExecResult(String id, String status, Long time) {
if (!redisTemplateService.has(id)) {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(id);
apiCase.setStatus(status);
apiCase.setUpdateTime(time);
testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
}
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(id);
apiCase.setStatus(status);
apiCase.setUpdateTime(time);
testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
}
public void editStatus(ApiDefinitionExecResult saveResult, String type, String status, Long time, String reportId, String testId) {
@ -243,7 +242,7 @@ public class ApiDefinitionExecResultService {
ApiRunMode.MANUAL_PLAN.name())) {
TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(testId);
ApiTestCaseWithBLOBs caseWithBLOBs = null;
if (testPlanApiCase != null && !redisTemplateService.has(testId)) {
if (testPlanApiCase != null && redisTemplateService.has(testId, saveResult.getId())) {
this.setExecResult(testId, status, time);
caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId());
testPlanApiCase.setStatus(status);
@ -252,6 +251,7 @@ public class ApiDefinitionExecResultService {
if (LoggerUtil.getLogger().isDebugEnabled()) {
LoggerUtil.debug("更新测试计划用例【 " + testPlanApiCase.getId() + "");
}
redisTemplateService.unlock(testId, saveResult.getId());
}
if (caseWithBLOBs != null) {
name = caseWithBLOBs.getName();
@ -265,7 +265,7 @@ public class ApiDefinitionExecResultService {
projectId = apiDefinition.getProjectId();
} else {
ApiTestCaseWithBLOBs caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testId);
if (caseWithBLOBs != null && !redisTemplateService.has(testId)) {
if (caseWithBLOBs != null && redisTemplateService.has(testId, saveResult.getId())) {
// 更新用例最后执行结果
caseWithBLOBs.setLastResultId(reportId);
caseWithBLOBs.setStatus(status);
@ -278,6 +278,7 @@ public class ApiDefinitionExecResultService {
name = caseWithBLOBs.getName();
version = caseWithBLOBs.getVersionId();
projectId = caseWithBLOBs.getProjectId();
redisTemplateService.unlock(testId, saveResult.getId());
}
}
}
@ -296,7 +297,7 @@ public class ApiDefinitionExecResultService {
ApiRunMode.SCHEDULE_API_PLAN.name(),
ApiRunMode.JENKINS_API_PLAN.name(),
ApiRunMode.MANUAL_PLAN.name())) {
if (!redisTemplateService.has(testId)) {
if (redisTemplateService.has(testId, reportId)) {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(testId);
apiCase.setStatus(status);
@ -307,6 +308,7 @@ public class ApiDefinitionExecResultService {
reviewApiCase.setId(testId);
reviewApiCase.setStatus(status);
reviewApiCase.setUpdateTime(System.currentTimeMillis());
redisTemplateService.unlock(testId, reportId);
}
} else {
// 更新用例最后执行结果
@ -339,7 +341,7 @@ public class ApiDefinitionExecResultService {
}
if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) {
TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(dto.getTestId());
if (apiCase != null && !redisTemplateService.has(dto.getTestId())) {
if (apiCase != null && redisTemplateService.has(dto.getTestId(), dto.getReportId())) {
String projectId = extTestPlanApiCaseMapper.selectProjectId(apiCase.getId());
ApiDefinition apiDefinition = extApiTestCaseMapper.selectApiBasicInfoByCaseId(apiCase.getId());
String version = apiDefinition == null ? "" : apiDefinition.getVersionId();
@ -353,6 +355,7 @@ public class ApiDefinitionExecResultService {
apiTestCase.setLastResultId(dto.getReportId());
apiTestCaseMapper.updateByPrimaryKeySelective(apiTestCase);
}
redisTemplateService.unlock(dto.getTestId(), dto.getReportId());
}
} else {
this.setExecResult(dto.getTestId(), reportResult.getStatus(), item.getStartTime());

View File

@ -167,6 +167,8 @@ public class ApiDefinitionService {
private BaseQuotaService baseQuotaService;
@Resource
private BaseEnvGroupProjectService environmentGroupProjectService;
@Resource
private ApiCaseResultService apiCaseResultService;
private static final String COPY = "Copy";
@ -2018,7 +2020,8 @@ public class ApiDefinitionService {
result.setEnvConfig(JSON.toJSONString(runModeConfigDTO));
}
result.setActuator(request.getConfig().getResourcePoolId());
apiDefinitionExecResultMapper.insert(result);
apiCaseResultService.batchSave(result);
}
if (request.isEditCaseRequest() && CollectionUtils.isNotEmpty(request.getTestElement().getHashTree()) && CollectionUtils.isNotEmpty(request.getTestElement().getHashTree().get(0).getHashTree())) {
ApiTestCaseWithBLOBs record = new ApiTestCaseWithBLOBs();

View File

@ -34,11 +34,9 @@ import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.request.OrderRequest;
import io.metersphere.request.ResetOrderRequest;
import io.metersphere.service.BaseProjectService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.service.ServiceUtils;
import io.metersphere.service.definition.ApiDefinitionExecResultService;
import io.metersphere.service.definition.ApiDefinitionService;
import io.metersphere.service.definition.ApiModuleService;
import io.metersphere.service.definition.ApiTestCaseService;
import io.metersphere.service.definition.*;
import io.metersphere.service.plan.remote.TestPlanService;
import jakarta.annotation.Resource;
import org.apache.commons.collections.MapUtils;
@ -96,6 +94,10 @@ public class TestPlanApiCaseService {
private JMeterService jMeterService;
@Resource
private ApiScenarioReportMapper apiScenarioReportMapper;
@Resource
private ApiCaseResultService apiCaseResultService;
@Resource
private RedisTemplateService redisTemplateService;
public List<TestPlanApiCaseDTO> list(ApiTestCaseRequest request) {
request.setProjectId(null);
@ -783,7 +785,6 @@ public class TestPlanApiCaseService {
if (apiCase == null) {
MSException.throwException("用例已经被删除");
}
String reportName = apiCase.getName();
ApiDefinitionExecResultWithBLOBs result = ApiDefinitionExecResultUtil.add(testId, ApiReportStatus.RUNNING.name(), reportId, Objects.requireNonNull(SessionUtils.getUser()).getId());
result.setName(reportName);
@ -799,7 +800,7 @@ public class TestPlanApiCaseService {
result.setEnvConfig(JSON.toJSONString(runModeConfigDTO));
}
result.setActuator(runModeConfigDTO.getResourcePoolId());
apiDefinitionExecResultMapper.insert(result);
apiCaseResultService.batchSave(result);
apiCase.setId(testId);
RunCaseRequest request = new RunCaseRequest();
@ -812,6 +813,8 @@ public class TestPlanApiCaseService {
request.setTestPlanId(testPlanApiCase.getTestPlanId());
Map<String, Object> extendedParameters = new HashMap<>();
extendedParameters.put(ExtendedParameter.SYNC_STATUS, true);
redisTemplateService.lock(testId, reportId);
apiExecuteService.exec(request, extendedParameters);
}

View File

@ -305,7 +305,7 @@ public class ApiScenarioReportService {
ResultVO resultVO = ReportStatusUtil.computedProcess(dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), resultVO.getStatus(), dto.getRunMode());
// 当前资源正在执行中
if (redisTemplateService.has(dto.getTestId())) {
if (!redisTemplateService.has(dto.getTestId(), dto.getReportId())) {
return report;
}
TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId());
@ -335,6 +335,7 @@ public class ApiScenarioReportService {
scenario.setExecuteTimes(executeTimes + 1);
apiScenarioMapper.updateByPrimaryKey(scenario);
}
redisTemplateService.unlock(dto.getTestId(), dto.getReportId());
}
return report;
}
@ -345,7 +346,7 @@ public class ApiScenarioReportService {
ResultVO resultVO = ReportStatusUtil.computedProcess(dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), resultVO.getStatus(), dto.getRunMode());
// 当前资源正在执行中
if (redisTemplateService.has(dto.getTestId())) {
if (!redisTemplateService.has(dto.getTestId(), dto.getReportId())) {
return report;
}
if (report != null) {
@ -375,6 +376,7 @@ public class ApiScenarioReportService {
scenario.setExecuteTimes(executeTimes + 1);
apiScenarioMapper.updateByPrimaryKey(scenario);
}
redisTemplateService.unlock(dto.getTestId(), dto.getReportId());
}
}
return report;