feat(接口测试): jmx导入,解析机制

--task=1016131 --user=陈建星 协议插件的导入机制 https://www.tapd.cn/55049933/s/1570442
This commit is contained in:
AgAngle 2024-08-28 14:52:23 +08:00 committed by 建国
parent 6fc6ffa1d1
commit bf1860faa2
16 changed files with 387 additions and 15 deletions

View File

@ -18,7 +18,7 @@ import java.util.function.Function;
* @createTime 2021-10-30 10:07
* MsTestElement 具体实现类转换为 HashTree
*/
public abstract class AbstractJmeterElementConverter<T extends MsTestElement> implements JmeterElementConverter {
public abstract class AbstractJmeterElementConverter<T extends MsTestElement> implements JmeterElementConverter<T> {
public Class<? extends MsTestElement> testElementClass;
@ -60,11 +60,6 @@ public abstract class AbstractJmeterElementConverter<T extends MsTestElement> im
}
}
/**
* MsTestElement 具体实现类转换为 HashTree
*/
public abstract void toHashTree(HashTree tree, T element, ParameterConfig config);
/**
* 解析 MsTestElement 的子节点
*/

View File

@ -0,0 +1,49 @@
package io.metersphere.plugin.api.spi;
import lombok.Setter;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.Function;
/**
* @author jianxing
* @createTime 2021-10-30 10:07
* Jmeter TestElement 转换成 MsTestElement
*/
public abstract class AbstractMsElementConverter<T extends TestElement> implements MsElementConverter<T> {
public Class<? extends TestElement> testElementClass;
/**
* 获取转换器的函数
*/
@Setter
private static Function<Class<? extends TestElement>, AbstractMsElementConverter> getConverterFunc;
public AbstractMsElementConverter() {
Type genericSuperclass = getClass().getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType parameterizedType) {
// 获取泛型的具体类型 MsTestElement 的具体实现类
testElementClass = ((Class) parameterizedType.getActualTypeArguments()[0]);
}
}
/**
* 解析 MsTestElement 的子节点
*/
public void parseChild(AbstractMsTestElement parentMsElement, TestElement parentElement, HashTree hashTree) {
HashTree currentHashtree = hashTree.get(parentElement);
if (currentHashtree == null) {
return;
}
for (Object key : currentHashtree.keySet()) {
if (key instanceof TestElement testElement) {
getConverterFunc.apply(testElement.getClass()).toMsElement(parentMsElement, testElement, currentHashtree);
}
}
}
}

View File

@ -46,7 +46,7 @@ public abstract class AbstractMsTestElement implements MsTestElement {
/**
* 子组件
*/
private LinkedList<AbstractMsTestElement> children;
private LinkedList<AbstractMsTestElement> children = new LinkedList<>();
/**
* 父组件
*/

View File

@ -1,6 +1,8 @@
package io.metersphere.plugin.api.spi;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.jorphan.collections.HashTree;
import org.pf4j.ExtensionPoint;
/**
@ -8,5 +10,10 @@ import org.pf4j.ExtensionPoint;
* @createTime 2021-10-30 10:07
* MsTestElement 具体实现类转换为 HashTree
*/
public interface JmeterElementConverter extends ExtensionPoint {
public interface JmeterElementConverter<T extends MsTestElement> extends ExtensionPoint {
/**
* MsTestElement 具体实现类转换为 HashTree
*/
void toHashTree(HashTree tree, T element, ParameterConfig config);
}

View File

@ -0,0 +1,19 @@
package io.metersphere.plugin.api.spi;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import org.pf4j.ExtensionPoint;
/**
* @author jianxing
* @createTime 2021-10-30 10:07
* MsTestElement 具体实现类转换为 HashTree
*/
public interface MsElementConverter<T extends TestElement> extends ExtensionPoint {
/**
* MsTestElement 具体实现类转换为 HashTree
*/
void toMsElement(AbstractMsTestElement parent, T element, HashTree hashTree);
}

View File

@ -1,9 +1,5 @@
package io.metersphere.api.utils;
package io.metersphere.api.parser.jmeter;
import io.metersphere.api.parser.jmeter.MsCommonElementConverter;
import io.metersphere.api.parser.jmeter.MsHTTPElementConverter;
import io.metersphere.api.parser.jmeter.MsScenarioConverter;
import io.metersphere.api.parser.jmeter.MsScriptElementConverter;
import io.metersphere.api.parser.jmeter.child.MsCsvChildPreConverter;
import io.metersphere.api.parser.jmeter.controller.MsConstantTimerControllerConverter;
import io.metersphere.api.parser.jmeter.controller.MsIfControllerConverter;

View File

@ -4,7 +4,6 @@ import io.metersphere.api.dto.ApiParamConfig;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.scenario.ScenarioOtherConfig;
import io.metersphere.api.parser.TestElementParser;
import io.metersphere.api.utils.JmeterElementConverterRegister;
import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;

View File

@ -0,0 +1,21 @@
package io.metersphere.api.parser.ms;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2024-08-28 11:36
*/
public class HTTPSamplerConverter extends AbstractMsElementConverter<HTTPSamplerProxy> {
@Override
public void toMsElement(AbstractMsTestElement parent, HTTPSamplerProxy httpSampler, HashTree hashTree) {
MsHTTPElement msHTTPElement = new MsHTTPElement();
// todo 解析HTTP请求
parent.getChildren().add(msHTTPElement);
parseChild(msHTTPElement, httpSampler, hashTree);
}
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.parser.ms;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2024-08-28 13:57
* 作为通用的解析器如果遇到不支持的组件会使用这个解析器
*/
public class JmeterGeneralElementConverter extends AbstractMsElementConverter<TestElement> {
@Override
public void toMsElement(AbstractMsTestElement parent, TestElement element, HashTree hashTree) {
// 不做任何处理直接解析子元素
parseChild(parent, element, hashTree);
}
}

View File

@ -0,0 +1,76 @@
package io.metersphere.api.parser.ms;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.sdk.util.PluginLogUtils;
import org.apache.jmeter.testelement.TestElement;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: jianxing
* @CreateTime: 2023-11-03 17:14
*/
public class MsElementConverterRegister {
/**
* 解析器集合
* key TestElement 实现类的 Class
* value 为对应的转换器
*/
private static final Map<Class<? extends TestElement>, AbstractMsElementConverter<? extends TestElement>> parserMap = new HashMap<>();
private static final JmeterGeneralElementConverter generalElementConverter = new JmeterGeneralElementConverter();
static {
// 设置获取转换器的方法
AbstractMsElementConverter.setGetConverterFunc(MsElementConverterRegister::getConverter);
// 注册默认的转换器
register(TestPlanConverter.class);
register(ThreadGroupConverter.class);
register(HTTPSamplerConverter.class);
}
/**
* 注册 TestElement 对应的转换器
*
* @param elementConverterClass 转换器的类
*/
public static void register(Class<? extends AbstractMsElementConverter<? extends TestElement>> elementConverterClass) {
try {
AbstractMsElementConverter<? extends TestElement> elementConverter = elementConverterClass.getDeclaredConstructor().newInstance();
// 注册到解析器集合中
parserMap.put(elementConverter.testElementClass, elementConverter);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
handleRegistrationException(elementConverterClass, e);
}
}
/**
* 获取对应组件的转换器
*
* @param TestElementClass 组件的类
* @return 转换器
*/
public static AbstractMsElementConverter getConverter(Class<? extends TestElement> TestElementClass) {
AbstractMsElementConverter<? extends TestElement> converter = parserMap.get(TestElementClass);
if (converter == null) {
// 如果没有对应的转换器则使用通用的转换器
return generalElementConverter;
}
return converter;
}
/**
* 处理注册转换器时的异常
*
* @param elementConverterClass 转换器的类
* @param e 异常
*/
private static void handleRegistrationException(Class<?> elementConverterClass, Exception e) {
PluginLogUtils.error("注册转换器失败: " + elementConverterClass, e);
}
}

View File

@ -0,0 +1,25 @@
package io.metersphere.api.parser.ms;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* hashTree 转换为 MsTestElement
*/
public class MsTestElementParser {
public AbstractMsTestElement parse(HashTree hashTree) {
MsScenario msScenario = new MsScenario();
for (Object key : hashTree.keySet()) {
if (key instanceof TestElement testElement) {
MsElementConverterRegister.getConverter(testElement.getClass()).toMsElement(msScenario, testElement, hashTree);
}
}
return msScenario;
}
}

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.testelement.TestPlan;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* 脚本解析器
*/
public class TestPlanConverter extends AbstractMsElementConverter<TestPlan> {
@Override
public void toMsElement(AbstractMsTestElement parent, TestPlan element, HashTree hashTree) {
// 测试计划不做任何处理直接解析子元素
parseChild(parent, element, hashTree);
}
}

View File

@ -0,0 +1,24 @@
package io.metersphere.api.parser.ms;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jorphan.collections.HashTree;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* 脚本解析器
*/
public class ThreadGroupConverter extends AbstractMsElementConverter<ThreadGroup> {
@Override
public void toMsElement(AbstractMsTestElement parent, ThreadGroup element, HashTree hashTree) {
MsScenario msScenario = new MsScenario();
// todo 解析线程组
parent.getChildren().add(msScenario);
parseChild(msScenario, element, hashTree);
}
}

View File

@ -1,7 +1,7 @@
package io.metersphere.api.service;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.JmeterElementConverterRegister;
import io.metersphere.api.parser.jmeter.JmeterElementConverterRegister;
import io.metersphere.plugin.api.spi.AbstractApiPlugin;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
import io.metersphere.plugin.api.spi.JmeterElementConverter;

View File

@ -0,0 +1,46 @@
package io.metersphere.api.utils;
import io.metersphere.api.parser.ms.MsTestElementParser;
import io.metersphere.plugin.api.spi.MsTestElement;
import org.apache.jmeter.save.SaveService;
import org.apache.jorphan.collections.HashTree;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.ReflectionUtils;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
/**
* @Author: jianxing
* @CreateTime: 2024-08-28 11:46
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class MsTestElementParserTest {
@Test
public void testParse() throws Exception {
File httpJmx = new File(
this.getClass().getClassLoader().getResource("file/http.jmx")
.getPath()
);
Object scriptWrapper = SaveService.loadElement(new FileInputStream(httpJmx));
HashTree hashTree = getHashTree(scriptWrapper);
MsTestElementParser parser = new MsTestElementParser();
MsTestElement msTestElement = parser.parse(hashTree);
Assertions.assertNotNull(msTestElement);
}
private HashTree getHashTree(Object scriptWrapper) throws Exception {
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
ReflectionUtils.makeAccessible(field);
return (HashTree) field.get(scriptWrapper);
}
}

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" enabled="true">
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="172481082966700000" enabled="true">
<intProp name="ThreadGroup.num_threads">1</intProp>
<intProp name="ThreadGroup.ramp_time">1</intProp>
<longProp name="ThreadGroup.delay">0</longProp>
<longProp name="ThreadGroup.duration">0</longProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="LoopController" enabled="true">
<intProp name="LoopController.loops">1</intProp>
<boolProp name="LoopController.continue_forever">false</boolProp>
</elementProp>
</ThreadGroup>
<hashTree>
<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="BBB_User Parameters" enabled="true">
<boolProp name="UserParameters.per_iteration">true</boolProp>
<collectionProp name="UserParameters.names">
<stringProp name="-1422464715">testVar</stringProp>
</collectionProp>
<collectionProp name="UserParameters.thread_values">
<collectionProp name="159016969">
<stringProp name="159016938">2024-08-22 16:15:34</stringProp>
</collectionProp>
</collectionProp>
</UserParameters>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="BBB">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="MS_RESOURCE_ID">2138842179805184</stringProp>
<stringProp name="MS_STEP_ID">2138842179805184</stringProp>
<stringProp name="MS_REPORT_ID">172481082966700000</stringProp>
<stringProp name="PROJECT_ID">100001100001</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<stringProp name="HTTPSampler.path">https://demo.halo.run/dddfsfs</stringProp>
<stringProp name="HTTPSampler.connect_timeout">60000</stringProp>
<stringProp name="HTTPSampler.response_timeout">60000</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
</HTTPSamplerProxy>
<hashTree>
<AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="AuthManager" enabled="true">
<collectionProp name="AuthManager.auth_list">
<elementProp name="" elementType="Authorization">
<stringProp name="Authorization.url"></stringProp>
<stringProp name="Authorization.username">demo</stringProp>
<stringProp name="Authorization.password">P@ssw0rd123..</stringProp>
<stringProp name="Authorization.domain"></stringProp>
<stringProp name="Authorization.realm"></stringProp>
</elementProp>
</collectionProp>
</AuthManager>
<hashTree/>
</hashTree>
<DebugSampler guiclass="TestBeanGUI" testclass="DebugSampler" testname="RunningDebugSampler" enabled="true">
<boolProp name="displayJMeterProperties">false</boolProp>
<boolProp name="displayJMeterVariables">true</boolProp>
<boolProp name="displaySystemProperties">false</boolProp>
</DebugSampler>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>