feat(接口测试): JMeter格式导入支持插件
--story=1015890 --user=陈建星 接口定义-导入(Postman、JMX、HAR、MeterSphere)& 导出(MeterSphere) https://www.tapd.cn/55049933/s/1578307
This commit is contained in:
parent
3f8c1da5e1
commit
a8833729e5
|
@ -8,12 +8,35 @@ import org.pf4j.ExtensionPoint;
|
||||||
/**
|
/**
|
||||||
* @author jianxing
|
* @author jianxing
|
||||||
* @createTime 2021-10-30 10:07
|
* @createTime 2021-10-30 10:07
|
||||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
* 将 HashTree 转换为 MsTestElement
|
||||||
|
* 接口导入 jmx 格式时,解析扩展的 HashTree
|
||||||
|
* 要支持导入,插件需要做以下修改:
|
||||||
|
* 1. 编写 com.thoughtworks.xstream.converters.Converter 的实现类,以实现 xstream 的 xml 反序列化解析
|
||||||
|
* 实现类示例:
|
||||||
|
* public class TCPXStreamConverter extends TestElementConverter {
|
||||||
|
* public TCPXStreamConverter(Mapper mapper) {
|
||||||
|
* super(mapper);
|
||||||
|
* }
|
||||||
|
* @Override
|
||||||
|
* public boolean canConvert(Class clazz) {
|
||||||
|
* return TCPSampler.class.isAssignableFrom(clazz);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* 2. 在插件的 resource 下新建 META-INF/services/com.thoughtworks.xstream.converters.Converter 文件
|
||||||
|
* 并添加实现类的全限定名,已便于SPI的服务发现
|
||||||
|
*
|
||||||
|
* 3. 在插件的 resource 下新建 jmeter_element_alias.properties 配置文件,用于配置元素的别名
|
||||||
|
* 文件内容示例(如果是jmeter官方支持的组件,可以省略这步):
|
||||||
|
* TCPSampler=org.apache.jmeter.protocol.tcp.sampler.TCPSampler
|
||||||
|
* TCPSamplerGui=org.apache.jmeter.protocol.tcp.control.gui.TCPSamplerGui
|
||||||
|
*
|
||||||
|
* 4. 编写 MsElementConverter 的实现类,将 HashTree 转换为 MsTestElement
|
||||||
*/
|
*/
|
||||||
public interface MsElementConverter<T extends TestElement> extends ExtensionPoint {
|
public interface MsElementConverter<T extends TestElement> extends ExtensionPoint {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
* 将 HashTree 转换为 MsTestElement
|
||||||
*/
|
*/
|
||||||
void toMsElement(AbstractMsTestElement parent, T element, HashTree hashTree);
|
void toMsElement(AbstractMsTestElement parent, T element, HashTree hashTree);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,10 @@ public class MsPluginManager extends DefaultPluginManager {
|
||||||
@Override
|
@Override
|
||||||
protected ExtensionFinder createExtensionFinder() {
|
protected ExtensionFinder createExtensionFinder() {
|
||||||
DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder();
|
DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder();
|
||||||
|
// 添加 jdbc 驱动支持
|
||||||
extensionFinder.add(new JdbcDriverServiceProviderExtensionFinder(this));
|
extensionFinder.add(new JdbcDriverServiceProviderExtensionFinder(this));
|
||||||
|
// 添加 SPI 支持
|
||||||
|
extensionFinder.addServiceProviderExtensionFinder();
|
||||||
return extensionFinder;
|
return extensionFinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.metersphere.api.dto.definition.ApiTestCaseDTO;
|
||||||
import io.metersphere.api.dto.request.ImportRequest;
|
import io.metersphere.api.dto.request.ImportRequest;
|
||||||
import io.metersphere.api.dto.request.http.MsHTTPElement;
|
import io.metersphere.api.dto.request.http.MsHTTPElement;
|
||||||
import io.metersphere.api.parser.ApiDefinitionImportParser;
|
import io.metersphere.api.parser.ApiDefinitionImportParser;
|
||||||
|
import io.metersphere.api.parser.jmeter.xstream.MsSaveService;
|
||||||
import io.metersphere.api.parser.ms.MsTestElementParser;
|
import io.metersphere.api.parser.ms.MsTestElementParser;
|
||||||
import io.metersphere.api.utils.ApiDefinitionImportUtils;
|
import io.metersphere.api.utils.ApiDefinitionImportUtils;
|
||||||
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;
|
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;
|
||||||
|
@ -21,7 +22,6 @@ import io.metersphere.system.service.ApiPluginService;
|
||||||
import io.metersphere.system.uid.IDGenerator;
|
import io.metersphere.system.uid.IDGenerator;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.jmeter.save.SaveService;
|
|
||||||
import org.apache.jorphan.collections.HashTree;
|
import org.apache.jorphan.collections.HashTree;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -35,7 +35,7 @@ public class JmeterParserApiDefinition implements ApiDefinitionImportParser<ApiI
|
||||||
@Override
|
@Override
|
||||||
public ApiImportFileParseResult parse(InputStream inputSource, ImportRequest request) throws Exception {
|
public ApiImportFileParseResult parse(InputStream inputSource, ImportRequest request) throws Exception {
|
||||||
try {
|
try {
|
||||||
Object scriptWrapper = SaveService.loadElement(inputSource);
|
Object scriptWrapper = MsSaveService.loadElement(inputSource);
|
||||||
HashTree hashTree = this.getHashTree(scriptWrapper);
|
HashTree hashTree = this.getHashTree(scriptWrapper);
|
||||||
MsTestElementParser parser = new MsTestElementParser();
|
MsTestElementParser parser = new MsTestElementParser();
|
||||||
AbstractMsTestElement msTestElement = parser.parse(hashTree);
|
AbstractMsTestElement msTestElement = parser.parse(hashTree);
|
||||||
|
|
|
@ -63,6 +63,15 @@ public class JmeterElementConverterRegister {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销 MsTestElement 对应的转换器
|
||||||
|
*
|
||||||
|
* @param elementConverterClass 转换器的类
|
||||||
|
*/
|
||||||
|
public static void unRegister(Class<? extends AbstractJmeterElementConverter<? extends MsTestElement>> elementConverterClass) {
|
||||||
|
parserMap.remove(elementConverterClass);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取对应组件的转换器
|
* 获取对应组件的转换器
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
package io.metersphere.api.parser.jmeter.xstream;
|
||||||
|
|
||||||
|
import com.thoughtworks.xstream.converters.ConversionException;
|
||||||
|
import com.thoughtworks.xstream.converters.Converter;
|
||||||
|
import com.thoughtworks.xstream.converters.ConverterLookup;
|
||||||
|
import com.thoughtworks.xstream.converters.ConverterRegistry;
|
||||||
|
import com.thoughtworks.xstream.core.Caching;
|
||||||
|
import com.thoughtworks.xstream.core.util.Cloneables;
|
||||||
|
import com.thoughtworks.xstream.mapper.Mapper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jianxing
|
||||||
|
* 代码参考 {@link com.thoughtworks.xstream.core.DefaultConverterLookup}
|
||||||
|
* 增加了 removeConverter 实现动态移除转换器
|
||||||
|
*/
|
||||||
|
public class MsJmeterConverterLookup implements ConverterLookup, ConverterRegistry, Caching {
|
||||||
|
|
||||||
|
private MsPrioritizedList converters = new MsPrioritizedList();
|
||||||
|
private transient Map typeToConverterMap;
|
||||||
|
private Map serializationMap = null;
|
||||||
|
|
||||||
|
public MsJmeterConverterLookup() {
|
||||||
|
this(new HashMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a DefaultConverterLookup with a provided map.
|
||||||
|
*
|
||||||
|
* @param map the map to use
|
||||||
|
* @throws NullPointerException if map is null
|
||||||
|
* @since 1.4.11
|
||||||
|
*/
|
||||||
|
public MsJmeterConverterLookup(Map map) {
|
||||||
|
typeToConverterMap = map;
|
||||||
|
typeToConverterMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of 1.3, use {@link #MsJmeterConverterLookup()}
|
||||||
|
*/
|
||||||
|
public MsJmeterConverterLookup(Mapper mapper) {
|
||||||
|
this();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Converter lookupConverterForType(Class type) {
|
||||||
|
Converter cachedConverter = type != null ? (Converter)typeToConverterMap.get(type.getName()) : null;
|
||||||
|
if (cachedConverter != null) {
|
||||||
|
return cachedConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map errors = new LinkedHashMap();
|
||||||
|
Iterator iterator = converters.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Converter converter = (Converter)iterator.next();
|
||||||
|
try {
|
||||||
|
if (converter.canConvert(type)) {
|
||||||
|
if (type != null) {
|
||||||
|
typeToConverterMap.put(type.getName(), converter);
|
||||||
|
}
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
errors.put(converter.getClass().getName(), e.getMessage());
|
||||||
|
} catch (final LinkageError e) {
|
||||||
|
errors.put(converter.getClass().getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ConversionException exception = new ConversionException(errors.isEmpty()
|
||||||
|
? "No converter specified"
|
||||||
|
: "No converter available");
|
||||||
|
exception.add("type", type != null ? type.getName() : "null");
|
||||||
|
iterator = errors.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
final Map.Entry entry = (Map.Entry)iterator.next();
|
||||||
|
exception.add("converter", entry.getKey().toString());
|
||||||
|
exception.add("message", entry.getValue().toString());
|
||||||
|
}
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerConverter(Converter converter, int priority) {
|
||||||
|
typeToConverterMap.clear();
|
||||||
|
converters.add(converter, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeConverter(Class<? extends Converter> converterClass) {
|
||||||
|
flushCache();
|
||||||
|
Iterator iterator = this.converters.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Object next = iterator.next();
|
||||||
|
if (converterClass.equals(next.getClass())) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushCache() {
|
||||||
|
typeToConverterMap.clear();
|
||||||
|
Iterator iterator = converters.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Converter converter = (Converter)iterator.next();
|
||||||
|
if (converter instanceof Caching) {
|
||||||
|
((Caching)converter).flushCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object writeReplace() {
|
||||||
|
serializationMap = (Map)Cloneables.cloneIfPossible(typeToConverterMap);
|
||||||
|
serializationMap.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readResolve() {
|
||||||
|
typeToConverterMap = serializationMap == null ? new HashMap() : serializationMap;
|
||||||
|
serializationMap = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package io.metersphere.api.parser.jmeter.xstream;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: jianxing
|
||||||
|
* @CreateTime: 2024-09-13 14:18
|
||||||
|
* 参考 {@link com.thoughtworks.xstream.core.util.PrioritizedList}
|
||||||
|
* 主要修改 PrioritizedItemIterator remove 方式,支持移除转换器
|
||||||
|
*/
|
||||||
|
public class MsPrioritizedList {
|
||||||
|
private final Set set = new TreeSet();
|
||||||
|
private int lowestPriority = Integer.MAX_VALUE;
|
||||||
|
private int lastId = 0;
|
||||||
|
|
||||||
|
public void add(Object item, int priority) {
|
||||||
|
if (this.lowestPriority > priority) {
|
||||||
|
this.lowestPriority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set.add(new MsPrioritizedList.PrioritizedItem(item, priority, ++this.lastId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator iterator() {
|
||||||
|
return new MsPrioritizedList.PrioritizedItemIterator(this.set.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrioritizedItemIterator implements Iterator {
|
||||||
|
private Iterator iterator;
|
||||||
|
|
||||||
|
public PrioritizedItemIterator(Iterator iterator) {
|
||||||
|
this.iterator = iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
this.iterator.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return this.iterator.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object next() {
|
||||||
|
return ((MsPrioritizedList.PrioritizedItem)this.iterator.next()).value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrioritizedItem implements Comparable {
|
||||||
|
final Object value;
|
||||||
|
final int priority;
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
public PrioritizedItem(Object value, int priority, int id) {
|
||||||
|
this.value = value;
|
||||||
|
this.priority = priority;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(Object o) {
|
||||||
|
MsPrioritizedList.PrioritizedItem other = (MsPrioritizedList.PrioritizedItem)o;
|
||||||
|
return this.priority != other.priority ? other.priority - this.priority : other.id - this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return this.id == ((MsPrioritizedList.PrioritizedItem)obj).id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,600 @@
|
||||||
|
package io.metersphere.api.parser.jmeter.xstream;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.thoughtworks.xstream.converters.*;
|
||||||
|
import com.thoughtworks.xstream.core.ClassLoaderReference;
|
||||||
|
import com.thoughtworks.xstream.core.util.CompositeClassLoader;
|
||||||
|
import com.thoughtworks.xstream.mapper.CachingMapper;
|
||||||
|
import io.metersphere.sdk.exception.MSException;
|
||||||
|
import io.metersphere.sdk.util.CommonBeanFactory;
|
||||||
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
|
import io.metersphere.system.service.PluginLoadService;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.apache.jmeter.reporters.ResultCollectorHelper;
|
||||||
|
import org.apache.jmeter.samplers.SampleEvent;
|
||||||
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
|
import org.apache.jmeter.util.NameUpdater;
|
||||||
|
import org.apache.jorphan.collections.HashTree;
|
||||||
|
import org.apache.jorphan.util.JMeterError;
|
||||||
|
import org.apache.jorphan.util.JOrphanUtils;
|
||||||
|
import org.pf4j.PluginWrapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.thoughtworks.xstream.XStream;
|
||||||
|
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
|
||||||
|
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
|
||||||
|
import com.thoughtworks.xstream.io.xml.XppDriver;
|
||||||
|
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
|
||||||
|
import com.thoughtworks.xstream.mapper.Mapper;
|
||||||
|
import com.thoughtworks.xstream.mapper.MapperWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: jianxing
|
||||||
|
* 代码参考 {@link org.apache.jmeter.save.SaveService}
|
||||||
|
* 主要为了接口导入支持动态插件,将 jmx 文件转换成 HashTree
|
||||||
|
* 修改点:
|
||||||
|
* 1. 因为 ScriptWrapper 不是 public ,所以替换成 MsScriptWrapper
|
||||||
|
* 2. StreamWrapper 替换为 MsXStreamWrapper,使用 MsJmeterConverterLookup,实现动态维护 converter
|
||||||
|
* 3. 增加 registerConverter 和 removeConverter 方法,动态维护 converter
|
||||||
|
* 4. 增加 pluginAliasMap 记录插件中 jmeter 元素的别名
|
||||||
|
*/
|
||||||
|
public class MsSaveService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MsSaveService.class);
|
||||||
|
|
||||||
|
// Names of DataHolder entries for JTL processing
|
||||||
|
public static final String SAMPLE_EVENT_OBJECT = "SampleEvent"; // $NON-NLS-1$
|
||||||
|
public static final String RESULTCOLLECTOR_HELPER_OBJECT = "ResultCollectorHelper"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
// Names of DataHolder entries for JMX processing
|
||||||
|
public static final String TEST_CLASS_NAME = "TestClassName"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
public static final MsJmeterConverterLookup JMETER_CONVERTER_LOOKUP = new MsJmeterConverterLookup();
|
||||||
|
|
||||||
|
private static final class MsXStreamWrapper extends XStream {
|
||||||
|
private MsXStreamWrapper(ReflectionProvider reflectionProvider) {
|
||||||
|
super(reflectionProvider, new XppDriver(), new ClassLoaderReference(new CompositeClassLoader()), null,
|
||||||
|
type -> JMETER_CONVERTER_LOOKUP.lookupConverterForType(type),
|
||||||
|
(converter, priority) -> JMETER_CONVERTER_LOOKUP.registerConverter(converter, priority));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override wrapMapper in order to insert the Wrapper in the chain
|
||||||
|
@Override
|
||||||
|
protected MapperWrapper wrapMapper(MapperWrapper next) {
|
||||||
|
// Provide our own aliasing using strings rather than classes
|
||||||
|
return new MapperWrapper(next) {
|
||||||
|
// Translate alias to classname and then delegate to wrapped class
|
||||||
|
@Override
|
||||||
|
public Class<?> realClass(String alias) {
|
||||||
|
String fullName = aliasToClass(alias);
|
||||||
|
if (fullName != null) {
|
||||||
|
fullName = NameUpdater.getCurrentName(fullName);
|
||||||
|
}
|
||||||
|
if (fullName == null) {
|
||||||
|
fullName = pluginAliasMap.get(alias);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return super.realClass(fullName == null ? alias : fullName);
|
||||||
|
} catch (CannotResolveClassException e) {
|
||||||
|
PluginLoadService pluginLoadService = CommonBeanFactory.getBean(PluginLoadService.class);
|
||||||
|
LogUtils.info(e.getMessage());
|
||||||
|
for (PluginWrapper plugin : pluginLoadService.getMsPluginManager().getPlugins()) {
|
||||||
|
try {
|
||||||
|
Class<?> aClass = plugin.getPluginClassLoader().loadClass(fullName);
|
||||||
|
if (aClass != null) {
|
||||||
|
return aClass;
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
LogUtils.info(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new MSException("无法解析:" + alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate to alias and then delegate to wrapped class
|
||||||
|
@Override
|
||||||
|
public String serializedClass(@SuppressWarnings("rawtypes") // superclass does not use types
|
||||||
|
Class type) {
|
||||||
|
if (type == null) {
|
||||||
|
return super.serializedClass(null); // was type, but that caused FindBugs warning
|
||||||
|
}
|
||||||
|
String alias = classToAlias(type.getName());
|
||||||
|
return alias == null ? super.serializedClass(type) : alias;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final XStream JMXSAVER = new MsXStreamWrapper(new PureJavaReflectionProvider());
|
||||||
|
private static final XStream JTLSAVER = new MsXStreamWrapper(new PureJavaReflectionProvider());
|
||||||
|
|
||||||
|
static {
|
||||||
|
JTLSAVER.setMode(XStream.NO_REFERENCES); // This is needed to stop XStream keeping copies of each class
|
||||||
|
JMeterUtils.setupXStreamSecurityPolicy(JMXSAVER);
|
||||||
|
JMeterUtils.setupXStreamSecurityPolicy(JTLSAVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The XML header, with placeholder for encoding, since that is controlled by property
|
||||||
|
private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"<ph>\"?>"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
// Default file name
|
||||||
|
private static final String SAVESERVICE_PROPERTIES_FILE = "saveservice.properties"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
// Property name used to define file name
|
||||||
|
private static final String SAVESERVICE_PROPERTIES = "saveservice_properties"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String JMETER_ELEMENT_ALIAS_PROPERTIES = "jmeter_element_alias.properties"; // $NON-NLS-1$
|
||||||
|
private static final Map<String, String> pluginAliasMap = new HashMap<>();
|
||||||
|
|
||||||
|
// Define file format versions
|
||||||
|
private static final String VERSION_2_2 = "2.2"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
// Holds the mappings from the saveservice properties file
|
||||||
|
// Key: alias Entry: full class name
|
||||||
|
// There may be multiple aliases which map to the same class
|
||||||
|
private static final Properties aliasToClass = new Properties();
|
||||||
|
|
||||||
|
// Holds the reverse mappings
|
||||||
|
// Key: full class name Entry: primary alias
|
||||||
|
private static final Properties classToAlias = new Properties();
|
||||||
|
|
||||||
|
// Version information for test plan header
|
||||||
|
// This is written to JMX files by ScriptWrapperConverter
|
||||||
|
// Also to JTL files by ResultCollector
|
||||||
|
private static final String VERSION = "1.2"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
// This is written to JMX files by ScriptWrapperConverter
|
||||||
|
private static String propertiesVersion = "";// read from properties file; written to JMX files
|
||||||
|
|
||||||
|
// Must match _version property value in saveservice.properties
|
||||||
|
// used to ensure saveservice.properties and SaveService are updated simultaneously
|
||||||
|
static final String PROPVERSION = "5.0";// Expected version $NON-NLS-1$
|
||||||
|
|
||||||
|
// Internal information only
|
||||||
|
private static String fileVersion = ""; // computed from saveservice.properties file// $NON-NLS-1$
|
||||||
|
|
||||||
|
private static String fileEncoding = ""; // read from properties file// $NON-NLS-1$
|
||||||
|
|
||||||
|
static {
|
||||||
|
log.info("Testplan (JMX) version: {}. Testlog (JTL) version: {}", VERSION_2_2, VERSION_2_2);
|
||||||
|
initProps();
|
||||||
|
checkVersions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Converter registerConverter(Class<? extends Converter> converterClass) {
|
||||||
|
try {
|
||||||
|
Converter converter = converterClass.getConstructor(Mapper.class).newInstance(getJMXMapper());
|
||||||
|
JMETER_CONVERTER_LOOKUP.registerConverter(converter, 0);
|
||||||
|
return converter;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.error(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unRegisterConverter(Class<? extends Converter> converterClass) {
|
||||||
|
JMETER_CONVERTER_LOOKUP.removeConverter(converterClass);
|
||||||
|
if (getJMXMapper() instanceof CachingMapper cachingMapper) {
|
||||||
|
// 有插件卸载,则清空缓存,否则,插件重新上传无效
|
||||||
|
cachingMapper.flushCache();
|
||||||
|
}
|
||||||
|
if (getJTLMapper() instanceof CachingMapper cachingMapper) {
|
||||||
|
// 有插件卸载,则清空缓存,否则,插件重新上传无效
|
||||||
|
cachingMapper.flushCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mapper getJMXMapper() {
|
||||||
|
return JMXSAVER.getMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mapper getJTLMapper() {
|
||||||
|
return JTLSAVER.getMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to simplify alias creation from properties
|
||||||
|
private static void makeAlias(String aliasList, String clazz) {
|
||||||
|
String[] aliases = aliasList.split(","); // Can have multiple aliases for same target classname
|
||||||
|
String alias = aliases[0];
|
||||||
|
for (String a : aliases) {
|
||||||
|
Object old = aliasToClass.setProperty(a, clazz);
|
||||||
|
if (old != null) {
|
||||||
|
log.error("Duplicate class detected for {}: {} & {}", alias, clazz, old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object oldval = classToAlias.setProperty(clazz, alias);
|
||||||
|
if (oldval != null) {
|
||||||
|
log.error("Duplicate alias detected for {}: {} & {}", clazz, alias, oldval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getSaveServiceFile() {
|
||||||
|
String saveServiceProps = JMeterUtils.getPropDefault(SAVESERVICE_PROPERTIES, SAVESERVICE_PROPERTIES_FILE); //$NON-NLS-1$
|
||||||
|
if (saveServiceProps.length() > 0) { //$NON-NLS-1$
|
||||||
|
return JMeterUtils.findFile(saveServiceProps);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Could not find file configured in saveservice_properties property set to:" + saveServiceProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Properties loadProperties() throws IOException {
|
||||||
|
Properties nameMap = new Properties();
|
||||||
|
File saveServiceFile = getSaveServiceFile();
|
||||||
|
if (saveServiceFile.canRead()) {
|
||||||
|
try (FileInputStream fis = new FileInputStream(saveServiceFile)) {
|
||||||
|
nameMap.load(fis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nameMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Properties loadPluginAliasProperties(ClassLoader classLoader) throws IOException {
|
||||||
|
Properties nameMap = new Properties();
|
||||||
|
InputStream resourceAsStream = classLoader.getResourceAsStream(JMETER_ELEMENT_ALIAS_PROPERTIES);
|
||||||
|
nameMap.load(resourceAsStream);
|
||||||
|
nameMap.forEach((k, v) -> pluginAliasMap.put((String) k, (String) v));
|
||||||
|
return nameMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String checksum(Properties nameMap) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||||
|
// This checksums the actual entries, and it ignores comments and blank lines
|
||||||
|
nameMap.entrySet().stream().sorted(
|
||||||
|
Comparator.comparing((Map.Entry<Object, Object> e) -> e.getKey().toString())
|
||||||
|
.thenComparing(e -> e.getValue().toString())
|
||||||
|
).forEachOrdered(e -> {
|
||||||
|
md.update(e.getKey().toString().getBytes(StandardCharsets.UTF_8));
|
||||||
|
md.update(e.getValue().toString().getBytes(StandardCharsets.UTF_8));
|
||||||
|
});
|
||||||
|
return JOrphanUtils.baToHexString(md.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initProps() {
|
||||||
|
// Load the alias properties
|
||||||
|
try {
|
||||||
|
Properties nameMap = loadProperties();
|
||||||
|
try {
|
||||||
|
fileVersion = checksum(nameMap);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.error("Can't compute checksum for saveservice properties file", e);
|
||||||
|
throw new JMeterError("JMeter requires the checksum of saveservice properties file to continue", e);
|
||||||
|
}
|
||||||
|
// now create the aliases
|
||||||
|
for (Map.Entry<Object, Object> me : nameMap.entrySet()) {
|
||||||
|
String key = (String) me.getKey();
|
||||||
|
String val = (String) me.getValue();
|
||||||
|
if (!key.startsWith("_")) { // $NON-NLS-1$
|
||||||
|
makeAlias(key, val);
|
||||||
|
} else {
|
||||||
|
// process special keys
|
||||||
|
if (key.equalsIgnoreCase("_version")) { // $NON-NLS-1$
|
||||||
|
propertiesVersion = val;
|
||||||
|
log.info("Using SaveService properties version {}", propertiesVersion);
|
||||||
|
} else if (key.equalsIgnoreCase("_file_version")) { // $NON-NLS-1$
|
||||||
|
log.info("SaveService properties file version is now computed by a checksum,"
|
||||||
|
+ "the property _file_version is not used anymore and can be removed.");
|
||||||
|
} else if (key.equalsIgnoreCase("_file_encoding")) { // $NON-NLS-1$
|
||||||
|
fileEncoding = val;
|
||||||
|
log.info("Using SaveService properties file encoding {}", fileEncoding);
|
||||||
|
} else {
|
||||||
|
key = key.substring(1);// Remove the leading "_"
|
||||||
|
registerConverter(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Bad saveservice properties file", e);
|
||||||
|
throw new JMeterError("JMeter requires the saveservice properties file to continue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerConverter(String key, String val) {
|
||||||
|
try {
|
||||||
|
final String trimmedValue = val.trim();
|
||||||
|
boolean useMapper = "collection".equals(trimmedValue) || "mapping".equals(trimmedValue); // $NON-NLS-1$ $NON-NLS-2$
|
||||||
|
registerConverter(key, JMXSAVER, useMapper);
|
||||||
|
registerConverter(key, JTLSAVER, useMapper);
|
||||||
|
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException | IllegalArgumentException |
|
||||||
|
SecurityException | InvocationTargetException | NoSuchMethodException e1) {
|
||||||
|
log.warn("Can't register a converter: {}", key, e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register converter.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param jmxsaver
|
||||||
|
* @param useMapper
|
||||||
|
* @throws InstantiationException
|
||||||
|
* @throws IllegalAccessException
|
||||||
|
* @throws InvocationTargetException
|
||||||
|
* @throws NoSuchMethodException
|
||||||
|
* @throws ClassNotFoundException
|
||||||
|
*/
|
||||||
|
private static void registerConverter(String key, XStream jmxsaver, boolean useMapper)
|
||||||
|
throws InstantiationException, IllegalAccessException,
|
||||||
|
InvocationTargetException, NoSuchMethodException,
|
||||||
|
ClassNotFoundException {
|
||||||
|
final Class<? extends Converter> clazz = Class.forName(key).asSubclass(Converter.class);
|
||||||
|
if (useMapper) {
|
||||||
|
jmxsaver.registerConverter(clazz.getConstructor(Mapper.class).newInstance(jmxsaver.getMapper()));
|
||||||
|
} else {
|
||||||
|
jmxsaver.registerConverter(clazz.getDeclaredConstructor().newInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For converters to use
|
||||||
|
public static String aliasToClass(String s) {
|
||||||
|
String r = aliasToClass.getProperty(s);
|
||||||
|
return r == null ? s : r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For converters to use
|
||||||
|
public static String classToAlias(String s) {
|
||||||
|
String r = classToAlias.getProperty(s);
|
||||||
|
return r == null ? s : r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by Save function
|
||||||
|
public static void saveTree(HashTree tree, OutputStream out) throws IOException {
|
||||||
|
// Get the OutputWriter to use
|
||||||
|
OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out);
|
||||||
|
writeXmlHeader(outputStreamWriter);
|
||||||
|
// Use deprecated method, to avoid duplicating code
|
||||||
|
MsScriptWrapper wrapper = new MsScriptWrapper();
|
||||||
|
wrapper.testPlan = tree;
|
||||||
|
JMXSAVER.toXML(wrapper, outputStreamWriter);
|
||||||
|
outputStreamWriter.write('\n');// Ensure terminated properly
|
||||||
|
outputStreamWriter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by Test code
|
||||||
|
public static void saveElement(Object el, OutputStream out) throws IOException {
|
||||||
|
// Get the OutputWriter to use
|
||||||
|
OutputStreamWriter outputStreamWriter = getOutputStreamWriter(out);
|
||||||
|
writeXmlHeader(outputStreamWriter);
|
||||||
|
// Use deprecated method, to avoid duplicating code
|
||||||
|
JMXSAVER.toXML(el, outputStreamWriter);
|
||||||
|
outputStreamWriter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by Test code
|
||||||
|
public static Object loadElement(InputStream in) throws IOException {
|
||||||
|
// Get the InputReader to use
|
||||||
|
InputStreamReader inputStreamReader = getInputStreamReader(in);
|
||||||
|
// Use deprecated method, to avoid duplicating code
|
||||||
|
Object element = JMXSAVER.fromXML(inputStreamReader);
|
||||||
|
inputStreamReader.close();
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a sampleResult to an XML output file using XStream.
|
||||||
|
*
|
||||||
|
* @param evt sampleResult wrapped in a sampleEvent
|
||||||
|
* @param writer output stream which must be created using {@link #getFileEncoding(String)}
|
||||||
|
* @throws IOException when writing data to output fails
|
||||||
|
*/
|
||||||
|
// Used by ResultCollector.sampleOccurred(SampleEvent event)
|
||||||
|
public static synchronized void saveSampleResult(SampleEvent evt, Writer writer) throws IOException {
|
||||||
|
DataHolder dh = JTLSAVER.newDataHolder();
|
||||||
|
dh.put(SAMPLE_EVENT_OBJECT, evt);
|
||||||
|
// This is effectively the same as saver.toXML(Object, Writer) except we get to provide the DataHolder
|
||||||
|
// Don't know why there is no method for this in the XStream class
|
||||||
|
try {
|
||||||
|
JTLSAVER.marshal(evt.getResult(), new XppDriver().createWriter(writer), dh);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new IllegalArgumentException("Failed marshalling:" + (evt.getResult() != null ? showDebuggingInfo(evt.getResult()) : "null"), e);
|
||||||
|
}
|
||||||
|
writer.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param result SampleResult
|
||||||
|
* @return String debugging information
|
||||||
|
*/
|
||||||
|
private static String showDebuggingInfo(SampleResult result) {
|
||||||
|
try {
|
||||||
|
return "class:" + result.getClass() + ",content:" + ToStringBuilder.reflectionToString(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "Exception occurred creating debug from event, message:" + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routines for TestSaveService
|
||||||
|
static String getPropertyVersion() {
|
||||||
|
return MsSaveService.propertiesVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getFileVersion() {
|
||||||
|
return MsSaveService.fileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow test code to check for spurious class references
|
||||||
|
static List<String> checkClasses() {
|
||||||
|
final ClassLoader classLoader = MsSaveService.class.getClassLoader();
|
||||||
|
List<String> missingClasses = new ArrayList<>();
|
||||||
|
for (Object clazz : classToAlias.keySet()) {
|
||||||
|
String name = (String) clazz;
|
||||||
|
if (!NameUpdater.isMapped(name)) {// don't bother checking class is present if it is to be updated
|
||||||
|
try {
|
||||||
|
Class.forName(name, false, classLoader);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
log.error("Unexpected entry in saveservice.properties; class does not exist and is not upgraded: {}", name);
|
||||||
|
missingClasses.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missingClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkVersions() {
|
||||||
|
if (!PROPVERSION.equalsIgnoreCase(propertiesVersion)) {
|
||||||
|
log.warn("Bad _version - expected {}, found {}.", PROPVERSION, propertiesVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read results from JTL file.
|
||||||
|
*
|
||||||
|
* @param reader of the file
|
||||||
|
* @param resultCollectorHelper helper class to enable TestResultWrapperConverter to deliver the samples
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public static void loadTestResults(InputStream reader, ResultCollectorHelper resultCollectorHelper) throws IOException {
|
||||||
|
// Get the InputReader to use
|
||||||
|
InputStreamReader inputStreamReader = getInputStreamReader(reader);
|
||||||
|
DataHolder dh = JTLSAVER.newDataHolder();
|
||||||
|
dh.put(RESULTCOLLECTOR_HELPER_OBJECT, resultCollectorHelper); // Allow TestResultWrapper to feed back the samples
|
||||||
|
// This is effectively the same as saver.fromXML(InputStream) except we get to provide the DataHolder
|
||||||
|
// Don't know why there is no method for this in the XStream class
|
||||||
|
JTLSAVER.unmarshal(new XppDriver().createReader(reader), null, dh);
|
||||||
|
inputStreamReader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a Test tree (JMX file)
|
||||||
|
*
|
||||||
|
* @param file the JMX file
|
||||||
|
* @return the loaded tree
|
||||||
|
* @throws IOException if there is a problem reading the file or processing it
|
||||||
|
*/
|
||||||
|
public static HashTree loadTree(File file) throws IOException {
|
||||||
|
log.info("Loading file: {}", file);
|
||||||
|
try (InputStream inputStream = new FileInputStream(file);
|
||||||
|
BufferedInputStream bufferedInputStream =
|
||||||
|
new BufferedInputStream(inputStream)) {
|
||||||
|
return readTree(bufferedInputStream, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param inputStream {@link InputStream}
|
||||||
|
* @param file the JMX file used only for debug, can be null
|
||||||
|
* @return the loaded tree
|
||||||
|
* @throws IOException if there is a problem reading the file or processing it
|
||||||
|
*/
|
||||||
|
private static HashTree readTree(InputStream inputStream, File file)
|
||||||
|
throws IOException {
|
||||||
|
MsScriptWrapper wrapper = null;
|
||||||
|
try {
|
||||||
|
// Get the InputReader to use
|
||||||
|
InputStreamReader inputStreamReader = getInputStreamReader(inputStream);
|
||||||
|
wrapper = (MsScriptWrapper) JMXSAVER.fromXML(inputStreamReader);
|
||||||
|
inputStreamReader.close();
|
||||||
|
if (wrapper == null) {
|
||||||
|
log.error("Problem loading XML: see above.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return wrapper.testPlan;
|
||||||
|
} catch (CannotResolveClassException | ConversionException | NoClassDefFoundError e) {
|
||||||
|
if (file != null) {
|
||||||
|
throw new IllegalArgumentException("Problem loading XML from:'" + file.getAbsolutePath() + "'. \nCause:\n" +
|
||||||
|
ExceptionUtils.getRootCauseMessage(e) + "\n\n Detail:" + e, e);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Problem loading XML. \nCause:\n" +
|
||||||
|
ExceptionUtils.getRootCauseMessage(e) + "\n\n Detail:" + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InputStreamReader getInputStreamReader(InputStream inStream) {
|
||||||
|
// Check if we have a encoding to use from properties
|
||||||
|
Charset charset = getFileEncodingCharset();
|
||||||
|
return new InputStreamReader(inStream, charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OutputStreamWriter getOutputStreamWriter(OutputStream outStream) {
|
||||||
|
// Check if we have a encoding to use from properties
|
||||||
|
Charset charset = getFileEncodingCharset();
|
||||||
|
return new OutputStreamWriter(outStream, charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file Encoding specified in saveservice.properties or the default
|
||||||
|
*
|
||||||
|
* @param dflt value to return if file encoding was not provided
|
||||||
|
* @return file encoding or default
|
||||||
|
*/
|
||||||
|
// Used by ResultCollector when creating output files
|
||||||
|
public static String getFileEncoding(String dflt) {
|
||||||
|
if (fileEncoding != null && fileEncoding.length() > 0) {
|
||||||
|
return fileEncoding;
|
||||||
|
} else {
|
||||||
|
return dflt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @NotNull
|
||||||
|
private static Charset getFileEncodingCharset() {
|
||||||
|
// Check if we have a encoding to use from properties
|
||||||
|
if (fileEncoding != null && fileEncoding.length() > 0) {
|
||||||
|
return Charset.forName(fileEncoding);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// We use the default character set encoding of the JRE
|
||||||
|
log.info("fileEncoding not defined - using JRE default");
|
||||||
|
return Charset.defaultCharset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeXmlHeader(OutputStreamWriter writer) throws IOException {
|
||||||
|
// Write XML header if we have the charset to use for encoding
|
||||||
|
Charset charset = getFileEncodingCharset();
|
||||||
|
// We do not use getEncoding method of Writer, since that returns
|
||||||
|
// the historical name
|
||||||
|
String header = XML_HEADER.replaceAll("<ph>", charset.name());
|
||||||
|
writer.write(header);
|
||||||
|
writer.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal output
|
||||||
|
// ---- Debugging information ----
|
||||||
|
// required-type : org.apache.jorphan.collections.ListedHashTree
|
||||||
|
// cause-message : WebServiceSampler : WebServiceSampler
|
||||||
|
// class : org.apache.jmeter.save.ScriptWrapper
|
||||||
|
// message : WebServiceSampler : WebServiceSampler
|
||||||
|
// line number : 929
|
||||||
|
// path : /jmeterTestPlan/hashTree/hashTree/hashTree[4]/hashTree[5]/WebServiceSampler
|
||||||
|
// cause-exception : com.thoughtworks.xstream.alias.CannotResolveClassException
|
||||||
|
// -------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplify getMessage() output from XStream ConversionException
|
||||||
|
*
|
||||||
|
* @param ce - ConversionException to analyse
|
||||||
|
* @return string with details of error
|
||||||
|
*/
|
||||||
|
public static String CEtoString(ConversionException ce) {
|
||||||
|
return "XStream ConversionException at line: " + ce.get("line number") + "\n" + ce.get("message")
|
||||||
|
+ "\nPerhaps a missing jar? See log file.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPropertiesVersion() {
|
||||||
|
return propertiesVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVERSION() {
|
||||||
|
return VERSION;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.metersphere.api.parser.jmeter.xstream;
|
||||||
|
|
||||||
|
import org.apache.jorphan.collections.HashTree;
|
||||||
|
class MsScriptWrapper {
|
||||||
|
// Used by ScriptWrapperConverter
|
||||||
|
String version = "";
|
||||||
|
|
||||||
|
HashTree testPlan;
|
||||||
|
}
|
|
@ -51,8 +51,6 @@ public class MsElementConverterRegister {
|
||||||
register(BeanShellPreProcessConverter.class);
|
register(BeanShellPreProcessConverter.class);
|
||||||
register(JDBCPreProcessConverter.class);
|
register(JDBCPreProcessConverter.class);
|
||||||
register(JSR223PreProcessConverter.class);
|
register(JSR223PreProcessConverter.class);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +69,15 @@ public class MsElementConverterRegister {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销 TestElement 对应的转换器
|
||||||
|
*
|
||||||
|
* @param elementConverterClass 转换器的类
|
||||||
|
*/
|
||||||
|
public static void unRegister(Class<? extends AbstractMsElementConverter<? extends TestElement>> elementConverterClass) {
|
||||||
|
parserMap.remove(elementConverterClass);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取对应组件的转换器
|
* 获取对应组件的转换器
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package io.metersphere.api.service;
|
package io.metersphere.api.service;
|
||||||
|
|
||||||
|
import com.thoughtworks.xstream.converters.Converter;
|
||||||
|
import io.metersphere.api.parser.jmeter.xstream.MsSaveService;
|
||||||
|
import io.metersphere.api.parser.ms.MsElementConverterRegister;
|
||||||
import io.metersphere.api.utils.ApiDataUtils;
|
import io.metersphere.api.utils.ApiDataUtils;
|
||||||
import io.metersphere.api.parser.jmeter.JmeterElementConverterRegister;
|
import io.metersphere.api.parser.jmeter.JmeterElementConverterRegister;
|
||||||
import io.metersphere.plugin.api.spi.AbstractApiPlugin;
|
import io.metersphere.plugin.api.spi.*;
|
||||||
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
|
|
||||||
import io.metersphere.plugin.api.spi.JmeterElementConverter;
|
|
||||||
import io.metersphere.plugin.api.spi.MsTestElement;
|
|
||||||
import io.metersphere.sdk.plugin.MsPluginManager;
|
import io.metersphere.sdk.plugin.MsPluginManager;
|
||||||
import io.metersphere.sdk.util.CommonBeanFactory;
|
import io.metersphere.sdk.util.CommonBeanFactory;
|
||||||
import io.metersphere.sdk.util.LogUtils;
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
import io.metersphere.system.service.PluginChangeService;
|
import io.metersphere.system.service.PluginChangeService;
|
||||||
import io.metersphere.system.service.PluginLoadService;
|
import io.metersphere.system.service.PluginLoadService;
|
||||||
|
import org.apache.jmeter.testelement.TestElement;
|
||||||
import org.pf4j.Plugin;
|
import org.pf4j.Plugin;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -35,12 +36,46 @@ public class ApiPluginChangeService implements PluginChangeService {
|
||||||
extensionClasses.forEach(clazzList::add);
|
extensionClasses.forEach(clazzList::add);
|
||||||
ApiDataUtils.setResolvers(clazzList);
|
ApiDataUtils.setResolvers(clazzList);
|
||||||
|
|
||||||
// 注册插件元素解析器
|
// 注册插件 JmeterElementConverter 解析器
|
||||||
msPluginManager.getExtensionClasses(JmeterElementConverter.class, pluginId)
|
msPluginManager.getExtensionClasses(JmeterElementConverter.class, pluginId)
|
||||||
.forEach(item -> JmeterElementConverterRegister.register((Class<? extends AbstractJmeterElementConverter<? extends MsTestElement>>) item));
|
.forEach(item -> JmeterElementConverterRegister.register((Class<? extends AbstractJmeterElementConverter<? extends MsTestElement>>) item));
|
||||||
|
|
||||||
|
// 注册插件 MsElementConverter 解析器
|
||||||
|
msPluginManager.getExtensionClasses(MsElementConverter.class, pluginId)
|
||||||
|
.forEach(item -> MsElementConverterRegister.register((Class<? extends AbstractMsElementConverter<? extends TestElement>>) item));
|
||||||
|
|
||||||
|
// 注册插件 xstream xml 序列化解析器
|
||||||
|
msPluginManager.getExtensionClasses(Converter.class, pluginId)
|
||||||
|
.forEach(MsSaveService::registerConverter);
|
||||||
|
|
||||||
|
// 加载插件 jmeter 元素别名
|
||||||
|
MsSaveService.loadPluginAliasProperties(msPluginManager.getPluginClassLoader(pluginId));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.error("注册接口插件实现类失败:{}", e);
|
LogUtils.error("注册接口插件实现类失败:{}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlePluginUnLoad(String pluginId) {
|
||||||
|
MsPluginManager msPluginManager = CommonBeanFactory.getBean(PluginLoadService.class).getMsPluginManager();
|
||||||
|
Plugin plugin = msPluginManager.getPlugin(pluginId).getPlugin();
|
||||||
|
try {
|
||||||
|
if (plugin instanceof AbstractApiPlugin) {
|
||||||
|
// 注销插件 JmeterElementConverter 解析器
|
||||||
|
msPluginManager.getExtensionClasses(JmeterElementConverter.class, pluginId)
|
||||||
|
.forEach(item -> JmeterElementConverterRegister.unRegister((Class<? extends AbstractJmeterElementConverter<? extends MsTestElement>>) item));
|
||||||
|
|
||||||
|
// 注销插件 MsElementConverter 解析器
|
||||||
|
msPluginManager.getExtensionClasses(MsElementConverter.class, pluginId)
|
||||||
|
.forEach(item -> MsElementConverterRegister.unRegister((Class<? extends AbstractMsElementConverter<? extends TestElement>>) item));
|
||||||
|
|
||||||
|
// 注销插件 xstream xml 序列化解析器
|
||||||
|
msPluginManager.getExtensionClasses(Converter.class, pluginId)
|
||||||
|
.forEach(MsSaveService::unRegisterConverter);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.error("注销接口插件实现类失败:{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package io.metersphere.api.service;
|
||||||
|
|
||||||
|
import io.metersphere.api.parser.jmeter.xstream.MsSaveService;
|
||||||
|
import io.metersphere.system.base.BaseApiPluginTestService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.apache.jorphan.collections.HashTree;
|
||||||
|
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 java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: jianxing
|
||||||
|
* @CreateTime: 2024-08-01 14:04
|
||||||
|
*/
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
public class MsSaveServiceTests {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BaseApiPluginTestService baseApiPluginTestService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getBlankJon() throws Exception {
|
||||||
|
baseApiPluginTestService.addPlugin("file/tcp-sampler-xstream.jar");
|
||||||
|
File tcpJmx = new File(
|
||||||
|
this.getClass().getClassLoader().getResource("file/tcp.jmx")
|
||||||
|
.getPath()
|
||||||
|
);
|
||||||
|
Object scriptWrapper = MsSaveService.loadElement(new FileInputStream(tcpJmx));
|
||||||
|
getHashTree(scriptWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashTree getHashTree(Object scriptWrapper) throws Exception {
|
||||||
|
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
|
||||||
|
field.setAccessible(true);
|
||||||
|
return (HashTree) field.get(scriptWrapper);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
package io.metersphere.api.utils;
|
package io.metersphere.api.utils;
|
||||||
|
|
||||||
|
import io.metersphere.api.parser.jmeter.xstream.MsSaveService;
|
||||||
import io.metersphere.api.parser.ms.MsTestElementParser;
|
import io.metersphere.api.parser.ms.MsTestElementParser;
|
||||||
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;
|
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;
|
||||||
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
||||||
import org.apache.jmeter.save.SaveService;
|
|
||||||
import org.apache.jorphan.collections.HashTree;
|
import org.apache.jorphan.collections.HashTree;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
@ -39,7 +39,7 @@ public class MsTestElementParserTest {
|
||||||
.getPath()
|
.getPath()
|
||||||
);
|
);
|
||||||
|
|
||||||
Object scriptWrapper = SaveService.loadElement(new FileInputStream(httpJmx));
|
Object scriptWrapper = MsSaveService.loadElement(new FileInputStream(httpJmx));
|
||||||
HashTree hashTree = getHashTree(scriptWrapper);
|
HashTree hashTree = getHashTree(scriptWrapper);
|
||||||
MsTestElementParser parser = new MsTestElementParser();
|
MsTestElementParser parser = new MsTestElementParser();
|
||||||
AbstractMsTestElement msTestElement = parser.parse(hashTree);
|
AbstractMsTestElement msTestElement = parser.parse(hashTree);
|
||||||
|
@ -51,7 +51,7 @@ public class MsTestElementParserTest {
|
||||||
this.getClass().getClassLoader().getResource("file/import/jmeter/single.jmx")
|
this.getClass().getClassLoader().getResource("file/import/jmeter/single.jmx")
|
||||||
.getPath()
|
.getPath()
|
||||||
);
|
);
|
||||||
scriptWrapper = SaveService.loadElement(new FileInputStream(httpJmx));
|
scriptWrapper = MsSaveService.loadElement(new FileInputStream(httpJmx));
|
||||||
hashTree = getHashTree(scriptWrapper);
|
hashTree = getHashTree(scriptWrapper);
|
||||||
parser = new MsTestElementParser();
|
parser = new MsTestElementParser();
|
||||||
msTestElement = parser.parse(hashTree);
|
msTestElement = parser.parse(hashTree);
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,59 @@
|
||||||
|
<?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="172474118045900001" 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>
|
||||||
|
<TCPSampler guiclass="TCPSamplerGui" testclass="TCPSampler" testname="12312312" enabled="true">
|
||||||
|
<stringProp name="TCPSampler.classname">TCPClientImpl</stringProp>
|
||||||
|
<stringProp name="TCPSampler.ctimeout">5000</stringProp>
|
||||||
|
<boolProp name="TCPSampler.reUseConnection">false</boolProp>
|
||||||
|
<boolProp name="TCPSampler.nodelay">false</boolProp>
|
||||||
|
<stringProp name="TCPSampler.closeConnection">false</stringProp>
|
||||||
|
<stringProp name="TCPSampler.timeout">5000</stringProp>
|
||||||
|
<stringProp name="TCPSampler.request">123123</stringProp>
|
||||||
|
<stringProp name="MS_RESOURCE_ID">913573930622976</stringProp>
|
||||||
|
<stringProp name="MS_STEP_ID">913573930622976</stringProp>
|
||||||
|
<stringProp name="MS_REPORT_ID">172474118045900001</stringProp>
|
||||||
|
<stringProp name="PROJECT_ID">718255970852864</stringProp>
|
||||||
|
<stringProp name="MS_CUSTOM_URL">TCP://null:null</stringProp>
|
||||||
|
<stringProp name="xxx">{"reportId":"172474118045900001","parseDisabledElement":false,"enableGlobalCookie":true,"envConfig":{"id":"2160780881534976","projectId":"718255970852864","name":"百度云","config":{"commonParams":{"requestTimeout":60000,"responseTimeout":60000},"commonVariables":[{"key":"","value":"","id":"aa0d9ff3-e0da-4f24-bf0c-51728e37bcd4","paramType":"CONSTANT","enable":true,"description":"","tags":[],"notBlankValue":false,"valid":false}],"httpConfig":[{"id":"4062c143-b73c-4109-bceb-6a60a8067f34","protocol":"https","hostname":"www.baidu.com","url":"https://www.baidu.com","type":"NONE","pathMatchRule":{"condition":"CONTAINS","path":""},"moduleMatchRule":{"modules":[]},"headers":[],"description":"","order":1,"authConfig":{"authType":"NONE","basicAuth":{"userName":"","password":"","valid":false},"digestAuth":{"userName":"","password":"","valid":false},"httpauthValid":false},"moduleMatchRuleOrder":2}],"dataSources":[{"id":"1ab74c5f-eea2-4d35-9bbc-e8e17e96185e","dataSource":"DM","driver":"dm.jdbc.driver.DmDriver","driverId":"dm.jdbc.driver.DmDriver&dm.jdbc.driver.DmDriver","dbUrl":"jdbc:dm://123.56.8.132:5236/SYS","username":"SYSDBA","password":"SYSDBA001","poolMax":1,"timeout":1000}],"hostConfig":{"enable":false,"hosts":[{"ip":null,"domain":null,"description":null}]},"preProcessorConfig":{"apiProcessorConfig":{"planProcessorConfig":{"processors":[]},"scenarioProcessorConfig":{"processors":[]},"requestProcessorConfig":{"processors":[]}}},"postProcessorConfig":{"apiProcessorConfig":{"planProcessorConfig":{"processors":[]},"scenarioProcessorConfig":{"processors":[]},"requestProcessorConfig":{"processors":[]}}},"assertionConfig":{"assertions":[]},"pluginConfigMap":{}},"mock":false,"description":null},"globalParams":null,"retryOnFail":false,"retryConfig":null,"testElementClassPluginIdMap":{"io.metersphere.plugin.sofa.rpc.SofaRpcSamplerModule":"sofa-rpc-sampler","io.metersphere.plugin.sampler.MsSpxTradeSampler":"SpxSampler","io.metersphere.plugin.redis.mode.RedisSamplerModule":"redis-sampler","io.metersphere.plugin.mongo.mode.MongoSamplerModule":"metersphere-plugin-mongo","io.metersphere.plugin.tcp.TCPSamplerModule":"tcp-sampler"},"testElementClassProtocolMap":{"io.metersphere.plugin.sofa.rpc.SofaRpcSamplerModule":"MongoDB","io.metersphere.plugin.sampler.MsSpxTradeSampler":"MongoDB","io.metersphere.plugin.redis.mode.RedisSamplerModule":"MongoDB","io.metersphere.plugin.mongo.mode.MongoSamplerModule":"MongoDB","io.metersphere.plugin.tcp.TCPSamplerModule":"MongoDB"}}</stringProp>
|
||||||
|
</TCPSampler>
|
||||||
|
<hashTree>
|
||||||
|
<JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="脚本名称" enabled="true">
|
||||||
|
<boolProp name="cacheKey">false</boolProp>
|
||||||
|
<stringProp name="PROJECT_ID">718255970852864</stringProp>
|
||||||
|
<stringProp name="script">log.info("=======");</stringProp>
|
||||||
|
<stringProp name="scriptLanguage">beanshell</stringProp>
|
||||||
|
</JSR223PostProcessor>
|
||||||
|
<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>
|
|
@ -24,4 +24,9 @@ public class PluginChangeServiceInvoker implements PluginChangeService {
|
||||||
public void handlePluginLoad(String pluginId) {
|
public void handlePluginLoad(String pluginId) {
|
||||||
this.pluginChangeServices.forEach(service -> service.handlePluginLoad(pluginId));
|
this.pluginChangeServices.forEach(service -> service.handlePluginLoad(pluginId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlePluginUnLoad(String pluginId) {
|
||||||
|
this.pluginChangeServices.forEach(service -> service.handlePluginUnLoad(pluginId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,13 @@ package io.metersphere.system.service;
|
||||||
*/
|
*/
|
||||||
public interface PluginChangeService {
|
public interface PluginChangeService {
|
||||||
/**
|
/**
|
||||||
* 插件时调用
|
* 插件加载时调用
|
||||||
* @param pluginId 插件ID
|
* @param pluginId 插件ID
|
||||||
*/
|
*/
|
||||||
void handlePluginLoad(String pluginId);
|
void handlePluginLoad(String pluginId);
|
||||||
|
/**
|
||||||
|
* 插件卸载时调用
|
||||||
|
* @param pluginId 插件ID
|
||||||
|
*/
|
||||||
|
void handlePluginUnLoad(String pluginId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,7 @@ public class PluginLoadService {
|
||||||
*/
|
*/
|
||||||
public synchronized void unloadPlugin(String pluginId) {
|
public synchronized void unloadPlugin(String pluginId) {
|
||||||
if (hasPlugin(pluginId)) {
|
if (hasPlugin(pluginId)) {
|
||||||
|
pluginChangeServiceInvoker.handlePluginUnLoad(pluginId);
|
||||||
msPluginManager.deletePlugin(pluginId);
|
msPluginManager.deletePlugin(pluginId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
|
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
|
||||||
|
|
||||||
|
@ -37,19 +39,23 @@ public class BaseApiPluginTestService {
|
||||||
if (hasJdbcPlugin()) {
|
if (hasJdbcPlugin()) {
|
||||||
return jdbcPlugin;
|
return jdbcPlugin;
|
||||||
}
|
}
|
||||||
|
jdbcPlugin = addPlugin("file/jdbc-sampler-v3.x.jar");
|
||||||
|
return jdbcPlugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugin addPlugin(String filePath) throws IOException {
|
||||||
PluginUpdateRequest request = new PluginUpdateRequest();
|
PluginUpdateRequest request = new PluginUpdateRequest();
|
||||||
File jarFile = new File(
|
File jarFile = new File(
|
||||||
this.getClass().getClassLoader().getResource("file/jdbc-sampler-v3.x.jar")
|
this.getClass().getClassLoader().getResource(filePath)
|
||||||
.getPath()
|
.getPath()
|
||||||
);
|
);
|
||||||
FileInputStream inputStream = new FileInputStream(jarFile);
|
FileInputStream inputStream = new FileInputStream(jarFile);
|
||||||
MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream);
|
MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream);
|
||||||
request.setName("测试jdbc插件");
|
request.setName(UUID.randomUUID().toString());
|
||||||
request.setGlobal(true);
|
request.setGlobal(true);
|
||||||
request.setEnable(true);
|
request.setEnable(true);
|
||||||
request.setCreateUser(ADMIN.name());
|
request.setCreateUser(ADMIN.name());
|
||||||
jdbcPlugin = pluginService.add(request, mockMultipartFile);
|
return pluginService.add(request, mockMultipartFile);
|
||||||
return jdbcPlugin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ public class PluginControllerTests extends BaseTest {
|
||||||
);
|
);
|
||||||
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
|
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
|
||||||
getDefaultMultiPartParam(request, myDriver));
|
getDefaultMultiPartParam(request, myDriver));
|
||||||
Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(DEFAULT_ORGANIZATION_ID), Arrays.asList("io.jianxing.MyDriver", "com.mysql.cj.jdbc.Driver"));
|
Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(DEFAULT_ORGANIZATION_ID), Arrays.asList("io.jianxing.MyDriver", "io.jianxing.MyDriver", "com.mysql.cj.jdbc.Driver"));
|
||||||
|
|
||||||
// 校验QUOTA动上传成功
|
// 校验QUOTA动上传成功
|
||||||
request.setName("cloud-quota-plugin");
|
request.setName("cloud-quota-plugin");
|
||||||
|
|
Loading…
Reference in New Issue