feat(接口测试): 场景支持jmeter插件的执行

This commit is contained in:
Jianguo-Genius 2024-10-16 14:28:49 +08:00 committed by Craftsman
parent 07647b1535
commit 0b60e549cd
14 changed files with 217 additions and 34 deletions

View File

@ -7,5 +7,5 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class MsJMeterComponent extends AbstractMsTestElement {
private String testElementContent;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.request;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class MsThreadGroup extends AbstractMsTestElement {
}

View File

@ -4,6 +4,7 @@ import io.metersphere.api.constants.ApiScenarioStatus;
import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.api.dto.request.MsThreadGroup;
import io.metersphere.api.dto.request.controller.*;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.scenario.ApiScenarioImportDetail;
@ -26,7 +27,10 @@ import org.apache.jorphan.collections.HashTree;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JmeterParserApiScenario implements ApiScenarioImportParser {
@ -39,31 +43,71 @@ public class JmeterParserApiScenario implements ApiScenarioImportParser {
MsTestElementParser parser = new MsTestElementParser();
AbstractMsTestElement msTestElement = parser.parse(hashTree);
Map<String, String> polymorphicNameMap = parser.getPolymorphicNameMap(request.getProjectId());
String scenarioName = StringUtils.trim(parser.parseTestPlanName(hashTree));
return Collections.singletonList(this.parseImportFile(request.getProjectId(), msTestElement, polymorphicNameMap, scenarioName));
return this.parseImportFile(request.getProjectId(), msTestElement, polymorphicNameMap);
} catch (Exception e) {
LogUtils.error(e);
throw new MSException("当前JMX版本不兼容");
}
}
private ApiScenarioImportDetail parseImportFile(String projectId, AbstractMsTestElement msElementList, Map<String, String> polymorphicNameMap, String scenarioName) {
ApiScenarioImportDetail apiScenarioDetail = new ApiScenarioImportDetail();
apiScenarioDetail.setName(scenarioName);
apiScenarioDetail.setPriority("P0");
apiScenarioDetail.setStatus(ApiScenarioStatus.UNDERWAY.name());
apiScenarioDetail.setGrouped(false);
apiScenarioDetail.setDeleted(false);
apiScenarioDetail.setLatest(true);
apiScenarioDetail.setProjectId(projectId);
private List<ApiScenarioImportDetail> parseImportFile(String projectId, AbstractMsTestElement msElementList, Map<String, String> polymorphicNameMap) {
List<AbstractMsTestElement> scenarioTestElementList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(msElementList.getChildren())) {
for (AbstractMsTestElement msTestElement : msElementList.getChildren()) {
if (msTestElement instanceof MsThreadGroup) {
scenarioTestElementList.add(msTestElement);
}
}
}
ApiScenarioStepParseResult stepParseResult = this.parseScenarioStep(msElementList.getChildren(), projectId, polymorphicNameMap);
if (CollectionUtils.isEmpty(scenarioTestElementList)) {
// 无法分辨ThreadGroup,当做一个场景来处理
scenarioTestElementList.add(msElementList);
}
apiScenarioDetail.setSteps(stepParseResult.getStepList());
apiScenarioDetail.setStepDetails(stepParseResult.getStepDetails());
List<ApiScenarioImportDetail> importList = new ArrayList<>();
for (AbstractMsTestElement msTestElement : scenarioTestElementList) {
ApiScenarioImportDetail apiScenarioDetail = new ApiScenarioImportDetail();
apiScenarioDetail.setName(StringUtils.trim(msTestElement.getName()));
apiScenarioDetail.setPriority("P0");
apiScenarioDetail.setStatus(ApiScenarioStatus.UNDERWAY.name());
apiScenarioDetail.setGrouped(false);
apiScenarioDetail.setDeleted(false);
apiScenarioDetail.setLatest(true);
apiScenarioDetail.setProjectId(projectId);
ApiScenarioStepParseResult stepParseResult = this.parseScenarioStep(msTestElement.getChildren(), projectId, polymorphicNameMap);
apiScenarioDetail.setSteps(stepParseResult.getStepList());
apiScenarioDetail.setStepDetails(stepParseResult.getStepDetails());
apiScenarioDetail.setStepTotal(CollectionUtils.size(apiScenarioDetail.getSteps()));
apiScenarioDetail.setStepTotal(CollectionUtils.size(apiScenarioDetail.getSteps()));
return apiScenarioDetail;
importList.add(apiScenarioDetail);
}
importList = this.apiScenarioRename(importList);
return importList;
}
public List<ApiScenarioImportDetail> apiScenarioRename(List<ApiScenarioImportDetail> scenarioList) {
List<ApiScenarioImportDetail> returnList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(scenarioList)) {
List<String> nameList = new ArrayList<>();
for (ApiScenarioImportDetail scenario : scenarioList) {
String uniqueName = getUniqueName(scenario.getName(), nameList);
scenario.setName(uniqueName);
nameList.add(uniqueName);
returnList.add(scenario);
}
}
return returnList;
}
private String getUniqueName(String originalName, List<String> existenceNameList) {
String returnName = originalName;
int index = 1;
while (existenceNameList.contains(returnName)) {
returnName = originalName + " - " + index;
index++;
}
return returnName;
}
private ApiScenarioStepParseResult parseScenarioStep(List<AbstractMsTestElement> msElementList, String projectId, Map<String, String> polymorphicNameMap) {
@ -94,6 +138,7 @@ public class JmeterParserApiScenario implements ApiScenarioImportParser {
apiScenarioStep.setStepType(this.getStepType(msTestElement));
apiScenarioStep.setConfig(new HashMap<>());
apiScenarioStep.setRefType(ApiScenarioStepRefType.DIRECT.name());
stepBlobContent = JSON.toJSONString(msTestElement).getBytes();
} else {
apiScenarioStep.setStepType(this.getStepType(msTestElement));
apiScenarioStep.setConfig(JSON.toJSONString(msTestElement));

View File

@ -42,6 +42,7 @@ public class JmeterElementConverterRegister {
register(MsLoopControllerConverter.class);
register(MsOnceOnlyControllerConverter.class);
register(MsConstantTimerControllerConverter.class);
register(MsJMeterComponentConverter.class);
// 注册转换器拦截器
AbstractJmeterElementConverter.registerConvertInterceptor(new RetryInterceptor());

View File

@ -0,0 +1,52 @@
package io.metersphere.api.parser.jmeter;
import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* 脚本解析器
*/
public class MsJMeterComponentConverter extends AbstractJmeterElementConverter<MsJMeterComponent> {
@Override
public void toHashTree(HashTree tree, MsJMeterComponent jMeterComponent, ParameterConfig msParameter) {
HashTree elementTree = null;
try (InputStream inputSource = getStrToStream(jMeterComponent.getTestElementContent())) {
if (inputSource != null) {
Object scriptWrapper = SaveService.loadElement(inputSource);
if (scriptWrapper instanceof TestElement) {
((TestElement) scriptWrapper).setName(jMeterComponent.getName());
((TestElement) scriptWrapper).setEnabled(jMeterComponent.getEnable());
}
elementTree = tree.add(scriptWrapper);
}
} catch (Exception ignore) {
}
if (elementTree != null) {
parseChild(elementTree, jMeterComponent, msParameter);
} else {
parseChild(tree, jMeterComponent, msParameter);
}
}
public static InputStream getStrToStream(String sInputString) {
if (StringUtils.isNotEmpty(sInputString)) {
return new ByteArrayInputStream(sInputString.getBytes());
}
return null;
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.api.parser.ms;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.sampler.DebugSampler;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* 脚本解析器
*/
public class DebugSampleConverter extends AbstractMsElementConverter<DebugSampler> {
@Override
public void toMsElement(AbstractMsTestElement parent, DebugSampler element, HashTree hashTree) {
// debug不做处理
}
}

View File

@ -1,19 +1,30 @@
package io.metersphere.api.parser.ms;
import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2024-08-28 13:57
* 作为通用的解析器如果遇到不支持的组件会使用这个解析器
*/
import java.io.ByteArrayOutputStream;
public class JmeterGeneralElementConverter extends AbstractMsElementConverter<TestElement> {
@Override
public void toMsElement(AbstractMsTestElement parent, TestElement element, HashTree hashTree) {
// 不做任何处理直接解析子元素
parseChild(parent, element, hashTree);
if (parent instanceof AbstractMsProtocolTestElement) {
// 在请求类型的组件下如果存在未知子组件暂时不解析
return;
}
MsJMeterComponent msJMeterComponent = new MsJMeterComponent();
msJMeterComponent.setName(element.getName());
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
SaveService.saveElement(element, baos);
msJMeterComponent.setTestElementContent(baos.toString());
} catch (Exception ignore) {
}
parent.getChildren().add(msJMeterComponent);
parseChild(msJMeterComponent, element, hashTree);
}
}

View File

@ -12,6 +12,7 @@ import org.apache.jmeter.testelement.TestElement;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @Author: jianxing
@ -33,7 +34,6 @@ public class MsElementConverterRegister {
// 注册默认的转换器
register(TestPlanConverter.class);
register(ThreadGroupConverter.class);
register(HTTPSamplerConverter.class);
register(HeaderManagerConverter.class);
@ -47,10 +47,14 @@ public class MsElementConverterRegister {
register(XPathExtractorConverter.class);
register(JSONPathAssertionConverter.class);
register(XPathAssertionConverter.class);
register(ThreadGroupConverter.class);
register(BeanShellPreProcessConverter.class);
register(JDBCPreProcessConverter.class);
register(JSR223PreProcessConverter.class);
register(ResultCollectorConverter.class);
register(DebugSampleConverter.class);
}
/**
@ -86,11 +90,8 @@ public class MsElementConverterRegister {
*/
public static AbstractMsElementConverter getConverter(Class<? extends TestElement> TestElementClass) {
AbstractMsElementConverter<? extends TestElement> converter = parserMap.get(TestElementClass);
if (converter == null) {
// 如果没有对应的转换器则使用通用的转换器
return generalElementConverter;
}
return converter;
// 如果没有对应的转换器则使用通用的转换器
return Objects.requireNonNullElse(converter, generalElementConverter);
}
/**

View File

@ -0,0 +1,20 @@
package io.metersphere.api.parser.ms;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* 脚本解析器
*/
public class ResultCollectorConverter extends AbstractMsElementConverter<ResultCollector> {
@Override
public void toMsElement(AbstractMsTestElement parent, ResultCollector element, HashTree hashTree) {
// resultController不做处理
}
}

View File

@ -1,7 +1,7 @@
package io.metersphere.api.parser.ms;
import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.api.dto.request.MsThreadGroup;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.threads.ThreadGroup;
@ -16,7 +16,7 @@ import org.apache.jorphan.collections.HashTree;
public class ThreadGroupConverter extends AbstractMsElementConverter<ThreadGroup> {
@Override
public void toMsElement(AbstractMsTestElement parent, ThreadGroup element, HashTree hashTree) {
MsJMeterComponent msJMeterComponent = new MsJMeterComponent();
MsThreadGroup msJMeterComponent = new MsThreadGroup();
msJMeterComponent.setName(element.getName());
parent.getChildren().add(msJMeterComponent);
parseChild(msJMeterComponent, element, hashTree);

View File

@ -0,0 +1,19 @@
package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
public class JMeterComponentStepParser extends StepParser {
@Override
public AbstractMsTestElement parseTestElement(ApiScenarioStepCommonDTO step, String resourceBlob, String stepDetail) {
return ApiDataUtils.parseObject(stepDetail, MsJMeterComponent.class);
}
@Override
public Object parseDetail(ApiScenarioStep step) {
return null;
}
}

View File

@ -22,6 +22,8 @@ public abstract class StepParserFactory {
stepParserMap.put(ApiScenarioStepType.LOOP_CONTROLLER.name(), new LoopControllerStepParser());
stepParserMap.put(ApiScenarioStepType.ONCE_ONLY_CONTROLLER.name(), new OnceOnlyControllerStepParser());
stepParserMap.put(ApiScenarioStepType.IF_CONTROLLER.name(), new IfControllerStepParser());
stepParserMap.put(ApiScenarioStepType.JMETER_COMPONENT.name(), new JMeterComponentStepParser());
}
public static StepParser getStepParser(String stepType) {

View File

@ -518,7 +518,7 @@ public class ApiScenarioDataTransferService {
}
if (modulePathMap.containsKey(modulePath)) {
List<ApiScenario> existenceScenarios = extApiScenarioMapper.selectBaseInfoByModuleId(modulePathMap.get(modulePath).getId());
Map<String, String> existenceNameIdMap = existenceScenarios.stream().collect(Collectors.toMap(ApiScenario::getName, ApiScenario::getId));
Map<String, String> existenceNameIdMap = existenceScenarios.stream().collect(Collectors.toMap(ApiScenario::getName, ApiScenario::getId, (k1, k2) -> k1));
String finalModulePath = modulePath;
scenarios.forEach(scenario -> {
if (existenceNameIdMap.containsKey(scenario.getName())) {

View File

@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import io.metersphere.api.dto.request.MsCommonElement;
import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.api.dto.request.controller.*;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.sdk.exception.MSException;
@ -42,6 +43,7 @@ public class ApiDataUtils {
namedTypes.add(new NamedType(MsOnceOnlyController.class, MsOnceOnlyController.class.getSimpleName()));
namedTypes.add(new NamedType(MsConstantTimerController.class, MsConstantTimerController.class.getSimpleName()));
namedTypes.add(new NamedType(MsScriptElement.class, MsScriptElement.class.getSimpleName()));
namedTypes.add(new NamedType(MsJMeterComponent.class, MsJMeterComponent.class.getSimpleName()));
setObjectMapper(objectMapper);
namedTypes.forEach(objectMapper::registerSubtypes);
}