feat(接口测试): 场景支持jmeter插件的执行
This commit is contained in:
parent
07647b1535
commit
0b60e549cd
|
@ -7,5 +7,5 @@ import lombok.EqualsAndHashCode;
|
|||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MsJMeterComponent extends AbstractMsTestElement {
|
||||
|
||||
private String testElementContent;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -42,6 +42,7 @@ public class JmeterElementConverterRegister {
|
|||
register(MsLoopControllerConverter.class);
|
||||
register(MsOnceOnlyControllerConverter.class);
|
||||
register(MsConstantTimerControllerConverter.class);
|
||||
register(MsJMeterComponentConverter.class);
|
||||
|
||||
// 注册转换器拦截器
|
||||
AbstractJmeterElementConverter.registerConvertInterceptor(new RetryInterceptor());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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不做处理
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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不做处理
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue