refactor(通用功能): ms jmeter core代码迁移到sdk中维护

This commit is contained in:
fit2-zhao 2022-11-08 15:56:12 +08:00 committed by fit2-zhao
parent f0324e3bdb
commit 48ea53c72c
52 changed files with 11016 additions and 0 deletions

View File

@ -0,0 +1,313 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.metersphere</groupId>
<artifactId>sdk-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>ms-jmeter-core</artifactId>
<version>${revision}</version>
<name>ms-jmeter-core</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<description>MeterSphere jmeter core</description>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- webdriver -->
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>jmeter-plugins-webdriver</artifactId>
<version>${jmeter-plugins-webdriver.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
</exclusion>
<exclusion>
<artifactId>slf4j-nop</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
<exclusion>
<artifactId>mail</artifactId>
<groupId>javax.mail</groupId>
</exclusion>
<exclusion>
<artifactId>geronimo-spec-javamail</artifactId>
<groupId>geronimo-spec</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- jmeter执行机 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<!-- jmeter -->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_http</artifactId>
<version>${jmeter.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</exclusion>
<exclusion>
<artifactId>xstream</artifactId>
<groupId>com.thoughtworks.xstream</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>xmlgraphics-commons</artifactId>
<version>${xmlgraphics-commons.version}</version>
</dependency>
<!-- 2.7.2 版本在大批量执行中会造成阻塞 -->
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>${jython.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_functions</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_jdbc</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_tcp</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>metersphere-jmeter-functions</artifactId>
<version>${metersphere-jmeter-functions.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>${mssql-jdbc.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>${oracle-database.version}</version>
</dependency>
<!-- Zookeeper -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<!-- nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-api</artifactId>
<version>${nacos.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>jmeter-plugins-dubbo</artifactId>
<version>${jmeter-plugins-dubbo.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>hessian-lite</artifactId>
<version>${hessian-lite.version}</version>
</dependency>
<!-- ApacheJMeter_core scope 是 runtime这里去掉scope -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-jsr223</artifactId>
<version>${groovy.version}</version>
</dependency>
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_bolt</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_jms</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_ftp</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_junit</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_ldap</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_mail</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_components</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_native</artifactId>
<version>${jmeter.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.brotli/dec -->
<dependency>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<version>${dec.version}</version>
</dependency>
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${org-json.version}</version>
</dependency>
<!-- jmeter执行机 -->
<!--钉钉sdk-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>${dingtalk-sdk.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>jorphan</artifactId>
<version>${jmeter.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,333 @@
/*
* 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.github.ningyu.jmeter.plugin.dubbo.sample;
import io.github.ningyu.jmeter.plugin.util.*;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ConfigCenterConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.utils.ReferenceConfigCache;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.service.GenericService;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.Interruptible;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZATION_DEFAULT;
/**
* DubboSample
*/
public class DubboSample extends AbstractSampler implements Interruptible {
private static final Logger log = LoggingManager.getLoggerForClass();
private static final long serialVersionUID = -6794913295411458705L;
public static ApplicationConfig application = new ApplicationConfig("DubboSample");
@Override
public SampleResult sample(Entry entry) {
SampleResult res = new SampleResult();
res.setSampleLabel(getName());
//构造请求数据
res.setSamplerData(getSampleData());
//调用dubbo
res.setResponseData(JsonUtils.toJson(callDubbo(res)), StandardCharsets.UTF_8.name());
//构造响应数据
res.setDataType(SampleResult.TEXT);
//dubbo协议执行没有时间现在默认执行时间为当前时间
res.setStartTime(System.currentTimeMillis());
res.setEndTime(System.currentTimeMillis());
return res;
}
/**
* Construct request data
*/
private String getSampleData() {
log.info("sample中的实例id" + this.toString() + ",element名称" + this.getName());
StringBuilder sb = new StringBuilder();
sb.append("Registry Protocol: ").append(Constants.getRegistryProtocol(this)).append("\n");
sb.append("Address: ").append(Constants.getAddress(this)).append("\n");
sb.append("RPC Protocol: ").append(Constants.getRpcProtocol(this)).append("\n");
sb.append("Timeout: ").append(Constants.getTimeout(this)).append("\n");
sb.append("Version: ").append(Constants.getVersion(this)).append("\n");
sb.append("Retries: ").append(Constants.getRetries(this)).append("\n");
sb.append("Cluster: ").append(Constants.getCluster(this)).append("\n");
sb.append("Group: ").append(Constants.getGroup(this)).append("\n");
sb.append("Connections: ").append(Constants.getConnections(this)).append("\n");
sb.append("LoadBalance: ").append(Constants.getLoadbalance(this)).append("\n");
sb.append("Async: ").append(Constants.getAsync(this)).append("\n");
sb.append("Interface: ").append(Constants.getInterface(this)).append("\n");
sb.append("Method: ").append(Constants.getMethod(this)).append("\n");
sb.append("Method Args: ").append(Constants.getMethodArgs(this).toString());
sb.append("Attachment Args: ").append(Constants.getAttachmentArgs(this).toString());
return sb.toString();
}
@SuppressWarnings({"unchecked", "rawtypes"})
private Object callDubbo(SampleResult res) {
// This instance is heavy, encapsulating the connection to the registry and the connection to the provider,
// so please cache yourself, otherwise memory and connection leaks may occur.
ReferenceConfig reference = new ReferenceConfig();
// set application
reference.setApplication(application);
/* registry center */
String address = Constants.getAddress(this);
if (StringUtils.isBlank(address)) {
setResponseError(res, ErrorCode.MISS_ADDRESS);
return ErrorCode.MISS_ADDRESS.getMessage();
}
RegistryConfig registry;
String rpcProtocol = Constants.getRpcProtocol(this).replaceAll("://", "");
String protocol = Constants.getRegistryProtocol(this);
String registryGroup = Constants.getRegistryGroup(this);
Integer registryTimeout = null;
try {
if (!StringUtils.isBlank(Constants.getRegistryTimeout(this))) {
registryTimeout = Integer.valueOf(Constants.getRegistryTimeout(this));
}
} catch (NumberFormatException e) {
setResponseError(res, ErrorCode.TIMEOUT_ERROR);
return ErrorCode.TIMEOUT_ERROR.getMessage();
}
if (StringUtils.isBlank(protocol) || Constants.REGISTRY_NONE.equals(protocol)) {
//direct connection
StringBuilder sb = new StringBuilder();
sb.append(Constants.getRpcProtocol(this))
.append(Constants.getAddress(this))
.append("/").append(Constants.getInterface(this));
log.debug("rpc invoker url : " + sb.toString());
reference.setUrl(sb.toString());
} else if (Constants.REGISTRY_SIMPLE.equals(protocol)) {
registry = new RegistryConfig();
registry.setAddress(address);
reference.setProtocol(rpcProtocol);
reference.setRegistry(registry);
} else {
registry = new RegistryConfig();
registry.setProtocol(protocol);
registry.setGroup(registryGroup);
registry.setAddress(address);
if (registryTimeout != null) {
registry.setTimeout(registryTimeout);
}
reference.setProtocol(rpcProtocol);
reference.setRegistry(registry);
}
/* config center */
try {
String configCenterProtocol = Constants.getConfigCenterProtocol(this);
if (!StringUtils.isBlank(configCenterProtocol)) {
String configCenterGroup = Constants.getConfigCenterGroup(this);
String configCenterNamespace = Constants.getConfigCenterNamespace(this);
String configCenterAddress = Constants.getConfigCenterAddress(this);
if (StringUtils.isBlank(configCenterAddress)) {
setResponseError(res, ErrorCode.MISS_ADDRESS);
return ErrorCode.MISS_ADDRESS.getMessage();
}
Long configCenterTimeout = null;
try {
if (!StringUtils.isBlank(Constants.getConfigCenterTimeout(this))) {
configCenterTimeout = Long.valueOf(Constants.getConfigCenterTimeout(this));
}
} catch (NumberFormatException e) {
setResponseError(res, ErrorCode.TIMEOUT_ERROR);
return ErrorCode.TIMEOUT_ERROR.getMessage();
}
ConfigCenterConfig configCenter = new ConfigCenterConfig();
configCenter.setProtocol(configCenterProtocol);
configCenter.setGroup(configCenterGroup);
configCenter.setNamespace(configCenterNamespace);
configCenter.setAddress(configCenterAddress);
if (configCenterTimeout != null) {
configCenter.setTimeout(configCenterTimeout);
}
reference.setConfigCenter(configCenter);
}
} catch (IllegalStateException e) {
/* Duplicate Config */
setResponseError(res, ErrorCode.DUPLICATE_CONFIGCENTERCONFIG);
return ErrorCode.DUPLICATE_CONFIGCENTERCONFIG.getMessage();
}
try {
// set interface
String interfaceName = Constants.getInterface(this);
if (StringUtils.isBlank(interfaceName)) {
setResponseError(res, ErrorCode.MISS_INTERFACE);
return ErrorCode.MISS_INTERFACE.getMessage();
}
reference.setInterface(interfaceName);
// set retries
Integer retries = null;
try {
if (!StringUtils.isBlank(Constants.getRetries(this))) {
retries = Integer.valueOf(Constants.getRetries(this));
}
} catch (NumberFormatException e) {
setResponseError(res, ErrorCode.RETRIES_ERROR);
return ErrorCode.RETRIES_ERROR.getMessage();
}
if (retries != null) {
reference.setRetries(retries);
}
// set cluster
String cluster = Constants.getCluster(this);
if (!StringUtils.isBlank(cluster)) {
reference.setCluster(Constants.getCluster(this));
}
// set version
String version = Constants.getVersion(this);
if (!StringUtils.isBlank(version)) {
reference.setVersion(version);
}
// set timeout
Integer timeout = null;
try {
if (!StringUtils.isBlank(Constants.getTimeout(this))) {
timeout = Integer.valueOf(Constants.getTimeout(this));
}
} catch (NumberFormatException e) {
setResponseError(res, ErrorCode.TIMEOUT_ERROR);
return ErrorCode.TIMEOUT_ERROR.getMessage();
}
if (timeout != null) {
reference.setTimeout(timeout);
}
// set group
String group = Constants.getGroup(this);
if (!StringUtils.isBlank(group)) {
reference.setGroup(group);
}
// set connections
Integer connections = null;
try {
if (!StringUtils.isBlank(Constants.getConnections(this))) {
connections = Integer.valueOf(Constants.getConnections(this));
}
} catch (NumberFormatException e) {
setResponseError(res, ErrorCode.CONNECTIONS_ERROR);
return ErrorCode.CONNECTIONS_ERROR.getMessage();
}
if (connections != null) {
reference.setConnections(connections);
}
// set loadBalance
String loadBalance = Constants.getLoadbalance(this);
if (!StringUtils.isBlank(loadBalance)) {
reference.setLoadbalance(loadBalance);
}
// set async
String async = Constants.getAsync(this);
if (!StringUtils.isBlank(async)) {
reference.setAsync(Constants.ASYNC.equals(async));
}
// set generic
reference.setGeneric(GENERIC_SERIALIZATION_DEFAULT);
CheckUtils.checkZookeeper(reference);
String methodName = Constants.getMethod(this);
if (StringUtils.isBlank(methodName)) {
setResponseError(res, ErrorCode.MISS_METHOD);
return ErrorCode.MISS_METHOD.getMessage();
}
// The registry's address is to generate the ReferenceConfigCache key
ReferenceConfigCache cache = ReferenceConfigCache.getCache(Constants.getAddress(this));
GenericService genericService = (GenericService) cache.get(reference);
if (genericService == null) {
setResponseError(res, ErrorCode.GENERIC_SERVICE_IS_NULL);
return MessageFormat.format(ErrorCode.GENERIC_SERVICE_IS_NULL.getMessage(), interfaceName);
}
String[] parameterTypes;
Object[] parameterValues;
List<MethodArgument> args = Constants.getMethodArgs(this);
List<String> parameterTypeList = new ArrayList<>();
List<Object> parameterValuesList = new ArrayList<>();
for (MethodArgument arg : args) {
ClassUtils.parseParameter(parameterTypeList, parameterValuesList, arg);
}
parameterTypes = parameterTypeList.toArray(new String[0]);
parameterValues = parameterValuesList.toArray(new Object[0]);
List<MethodArgument> attachmentArgs = Constants.getAttachmentArgs(this);
if (!attachmentArgs.isEmpty()) {
RpcContext.getContext().setAttachments(attachmentArgs.stream().collect(Collectors.toMap(MethodArgument::getParamType, MethodArgument::getParamValue)));
}
res.sampleStart();
Object result;
try {
result = genericService.$invoke(methodName, parameterTypes, parameterValues);
res.setResponseOK();
} catch (Exception e) {
log.error("Exception", e);
if (e instanceof RpcException) {
RpcException rpcException = (RpcException) e;
setResponseError(res, String.valueOf(rpcException.getCode()), rpcException.getMessage());
} else {
setResponseError(res, ErrorCode.UNKNOWN_EXCEPTION);
}
result = e;
}
res.sampleEnd();
cache.destroy(reference); // 清除缓存
return result;
} catch (Exception e) {
log.error("UnknownException", e);
setResponseError(res, ErrorCode.UNKNOWN_EXCEPTION);
return e;
}
}
public void setResponseError(SampleResult res, ErrorCode errorCode) {
setResponseError(res, errorCode.getCode(), errorCode.getMessage());
}
public void setResponseError(SampleResult res, String code, String message) {
res.setSuccessful(false);
res.setResponseCode(code);
res.setResponseMessage(message);
}
@Override
public boolean interrupt() {
Thread.currentThread().interrupt();
return true;
}
}

View File

@ -0,0 +1,13 @@
package io.metersphere.cache;
import org.apache.jmeter.engine.StandardJMeterEngine;
import java.util.concurrent.ConcurrentHashMap;
public class JMeterEngineCache {
/**
* 执行中的线程池
*/
public static ConcurrentHashMap<String, StandardJMeterEngine> runningEngine = new ConcurrentHashMap<>();
}

View File

@ -0,0 +1,5 @@
package io.metersphere.constants;
public enum BackendListenerConstants {
RETRY_ENABLE, TEST_ID, NAME, REPORT_ID, RUN_MODE, REPORT_TYPE, CLASS_NAME, MS_TEST_PLAN_REPORT_ID, RUN, KAFKA_CONFIG, QUEUE_ID, RUN_TYPE, EPT, MS_DEBUG
}

View File

@ -0,0 +1,39 @@
package io.metersphere.constants;
import java.util.HashMap;
import java.util.Map;
public enum HttpMethodConstants {
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE;
private static final Map<String, HttpMethodConstants> mappings = new HashMap(16);
private HttpMethodConstants() {
}
public static HttpMethodConstants resolve(String method) {
return method != null ? (HttpMethodConstants) mappings.get(method) : null;
}
public boolean matches(String method) {
return this.name().equals(method);
}
static {
HttpMethodConstants[] var0 = values();
int var1 = var0.length;
for (int var2 = 0; var2 < var1; ++var2) {
HttpMethodConstants HttpMethodConstants = var0[var2];
mappings.put(HttpMethodConstants.name(), HttpMethodConstants);
}
}
}

View File

@ -0,0 +1,17 @@
package io.metersphere.constants;
public enum RunModeConstants {
SERIAL("serial"), SET_REPORT("setReport"), INDEPENDENCE("Independence"), PARALLEL("parallel"), HIS_PRO_ID("historyProjectID");
private String value;
RunModeConstants(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}

View File

@ -0,0 +1,14 @@
package io.metersphere.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class BaseSystemConfigDTO {
private String url;
private String concurrency;
private String prometheusHost;
private String seleniumDockerUrl;
private String runMode;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.dto;
import lombok.Data;
@Data
public class ErrorReportAssertionResult extends ResponseAssertionResult{
private String errorReportMessage;
public ErrorReportAssertionResult(String errorReportMessage){
this.errorReportMessage = errorReportMessage;
this.setMessage(errorReportMessage);
}
}

View File

@ -0,0 +1,104 @@
package io.metersphere.dto;
import io.metersphere.constants.RunModeConstants;
import io.metersphere.vo.BooleanPool;
import lombok.Data;
import org.apache.jorphan.collections.HashTree;
import java.util.LinkedHashMap;
import java.util.Map;
@Data
public class JmeterRunRequestDTO {
/**
* 队列ID
*/
private String queueId;
/**
* 执行模式/API/SCENARIO/PLAN_API等
*/
private String runMode;
/**
* RunModeConstants.SET_REPORT : RunModeConstants.INDEPENDENCE
*/
private String reportType;
/**
* 报告ID
*/
private String reportId;
/**
* 测试计划报告ID
*/
private String testPlanReportId;
/**
* 资源id/场景id/用例id/接口id/测试计划场景id/测试计划用例id
*/
private String testId;
/**
* 是否发送node节点执行
*/
private BooleanPool pool;
/**
* 资源池Id
*/
private String poolId;
/**
* 并行/串行
*/
private String runType;
/**
* 调试
*/
private boolean isDebug;
/**
* 执行脚本
*/
private HashTree hashTree;
/**
* 并发数
*/
private int corePoolSize;
/**
* 是否开启定时同步
*/
private boolean enable;
/**
* KAFKA配置只用在node节点中会用到
*/
private Map<String, Object> kafkaConfig;
/**
* 增加一个全局扩展的通传参数
*/
private Map<String, Object> extendedParameters;
/**
* 平台服务地址只用在node节点中会用到
*/
private String platformUrl;
// 失败重试
private boolean retryEnable;
// 失败重试次数
private long retryNum;
public JmeterRunRequestDTO() {
}
public JmeterRunRequestDTO(String testId, String reportId, String runMode, HashTree hashTree) {
this.testId = testId;
this.reportId = reportId;
this.runMode = runMode;
this.reportType = RunModeConstants.INDEPENDENCE.name();
this.hashTree = hashTree;
this.pool = new BooleanPool();
this.extendedParameters = new LinkedHashMap<>();
}
}

View File

@ -0,0 +1,37 @@
package io.metersphere.dto;
import lombok.Data;
@Data
public class JvmInfoDTO {
/**
* JVM内存的空闲空间为
*/
private long vmFree;
/**
* JVM内存已用的空间为
*/
private long vmUse;
private long vmTotal;
/**
* JVM总内存空间为
*/
private long vmMax;
private int totalThread;
private TestResourceDTO testResource;
public JvmInfoDTO() {
}
public JvmInfoDTO(long vmTotal, long vmFree, long vmMax, long vmUse, int totalThread) {
this.vmFree = vmFree;
this.vmTotal = vmTotal;
this.vmMax = vmMax;
this.vmUse = vmUse;
this.totalThread = totalThread;
}
}

View File

@ -0,0 +1,23 @@
package io.metersphere.dto;
import lombok.Data;
@Data
public class MsExecResponseDTO {
private String testId;
private String reportId;
private String runMode;
public MsExecResponseDTO() {
}
public MsExecResponseDTO(String testId, String reportId, String runMode) {
this.testId = testId;
this.reportId = reportId;
this.runMode = runMode;
}
}

View File

@ -0,0 +1,56 @@
package io.metersphere.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class RequestResult {
// 请求ID
private String id;
// 步骤请求唯一ID
private String resourceId;
private String threadName;
private String name;
private String url;
private String method;
private String scenario;
private long requestSize;
private long startTime;
private long endTime;
private int error;
private boolean success;
private String headers;
private String cookies;
private String body;
private String status;
private int totalAssertions = 0;
private int passAssertions = 0;
private List<RequestResult> subRequestResults = new ArrayList<>();
private ResponseResult responseResult = new ResponseResult();
public void addPassAssertions() {
this.passAssertions++;
}
}

View File

@ -0,0 +1,17 @@
package io.metersphere.dto;
import lombok.Data;
@Data
public class ResponseAssertionResult {
private String name;
private String content;
private String script;
private String message;
private boolean pass;
}

View File

@ -0,0 +1,32 @@
package io.metersphere.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ResponseResult {
private String responseCode;
private String responseMessage;
private long responseTime;
private long latency;
private long responseSize;
private String headers;
private String body;
private String vars;
private String console;
private final List<ResponseAssertionResult> assertions = new ArrayList<>();
}

View File

@ -0,0 +1,30 @@
package io.metersphere.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class ResultDTO {
private List<RequestResult> requestResults;
private String runMode;
private String queueId;
private String reportId;
private String reportType;
private String testPlanReportId;
private String testId;
private String runType;
private String console;
private String runningDebugSampler;
// 失败重试
private boolean retryEnable;
/**
* 增加一个全局扩展的通传参数
*/
private Map<String, Object> extendedParameters;
// 预留一个参数可以放任何数据
private Map<String, Object> arbitraryData;
}

View File

@ -0,0 +1,34 @@
package io.metersphere.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class RunModeConfigDTO {
private String mode;
private String reportType;
private String reportName;
private String reportId;
private String testId;
private String amassReport;
private boolean onSampleError;
// 失败重试
private boolean retryEnable;
// 失败重试次数
private long retryNum;
// 资源池
private String resourcePoolId;
private BaseSystemConfigDTO baseInfo;
private List<JvmInfoDTO> testResources;
/**
* 运行环境
*/
private Map<String, String> envMap;
private String environmentType;
private String environmentGroupId;
//ui 测试
private String browser;
private boolean headlessEnabled;
}

View File

@ -0,0 +1,22 @@
package io.metersphere.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class TestResourceDTO implements Serializable {
private String id;
private String testResourcePoolId;
private String status;
private Long createTime;
private Long updateTime;
private String configuration;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,253 @@
package io.metersphere.jmeter;
import io.metersphere.constants.BackendListenerConstants;
import io.metersphere.constants.HttpMethodConstants;
import io.metersphere.dto.*;
import io.metersphere.utils.JMeterVars;
import io.metersphere.utils.JsonUtils;
import io.metersphere.utils.ListenerUtil;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.visualizers.backend.BackendListener;
import org.apache.jorphan.collections.HashTree;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class JMeterBase {
private final static String THREAD_SPLIT = " ";
public static HashTree getHashTree(Object scriptWrapper) throws Exception {
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
field.setAccessible(true);
return (HashTree) field.get(scriptWrapper);
}
public static void addBackendListener(JmeterRunRequestDTO request, HashTree hashTree, String listenerClazz) {
LoggerUtil.info("开始为报告添加BackendListener", request.getReportId());
BackendListener backendListener = new BackendListener();
backendListener.setName(request.getReportId() + "_" + request.getTestId());
Arguments arguments = new Arguments();
arguments.addArgument(BackendListenerConstants.NAME.name(), request.getReportId() + "_" + request.getTestId());
arguments.addArgument(BackendListenerConstants.REPORT_ID.name(), request.getReportId());
arguments.addArgument(BackendListenerConstants.TEST_ID.name(), request.getTestId());
arguments.addArgument(BackendListenerConstants.RUN_MODE.name(), request.getRunMode());
arguments.addArgument(BackendListenerConstants.REPORT_TYPE.name(), request.getReportType());
arguments.addArgument(BackendListenerConstants.MS_TEST_PLAN_REPORT_ID.name(), request.getTestPlanReportId());
arguments.addArgument(BackendListenerConstants.CLASS_NAME.name(), listenerClazz);
arguments.addArgument(BackendListenerConstants.QUEUE_ID.name(), request.getQueueId());
arguments.addArgument(BackendListenerConstants.RUN_TYPE.name(), request.getRunType());
arguments.addArgument(BackendListenerConstants.RETRY_ENABLE.name(), String.valueOf(request.isRetryEnable()));
if (MapUtils.isNotEmpty(request.getExtendedParameters())) {
arguments.addArgument(BackendListenerConstants.EPT.name(), JsonUtils.toJSONString(request.getExtendedParameters()));
}
if (request.getKafkaConfig() != null && request.getKafkaConfig().size() > 0) {
arguments.addArgument(BackendListenerConstants.KAFKA_CONFIG.name(), JsonUtils.toJSONString(request.getKafkaConfig()));
}
backendListener.setArguments(arguments);
backendListener.setClassname(listenerClazz);
if (hashTree != null) {
hashTree.add(hashTree.getArray()[0], backendListener);
}
LoggerUtil.info("报告添加BackendListener 结束", request.getTestId());
}
public static RequestResult getRequestResult(SampleResult result) {
LoggerUtil.debug("开始处理结果资源【" + result.getSampleLabel() + "");
String threadName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT);
RequestResult requestResult = new RequestResult();
requestResult.setThreadName(threadName);
requestResult.setId(result.getSamplerId());
requestResult.setResourceId(result.getResourceId());
requestResult.setName(result.getSampleLabel());
requestResult.setUrl(result.getUrlAsString());
requestResult.setMethod(getMethod(result));
requestResult.setBody(result.getSamplerData());
requestResult.setHeaders(result.getRequestHeaders());
requestResult.setRequestSize(result.getSentBytes());
requestResult.setStartTime(result.getStartTime());
requestResult.setEndTime(result.getEndTime());
requestResult.setTotalAssertions(result.getAssertionResults().length);
requestResult.setSuccess(result.isSuccessful());
requestResult.setError(result.getErrorCount());
requestResult.setScenario(result.getScenario());
if (result instanceof HTTPSampleResult) {
HTTPSampleResult res = (HTTPSampleResult) result;
requestResult.setCookies(res.getCookies());
}
for (SampleResult subResult : result.getSubResults()) {
requestResult.getSubRequestResults().add(getRequestResult(subResult));
}
ResponseResult responseResult = requestResult.getResponseResult();
// 超过20M的文件不入库
long size = 1024 * 1024 * 20;
if (StringUtils.equals(ContentType.APPLICATION_OCTET_STREAM.getMimeType(), result.getContentType())
&& StringUtils.isNotEmpty(result.getResponseDataAsString())
&& result.getResponseDataAsString().length() > size) {
requestResult.setBody("");
} else {
responseResult.setBody(result.getResponseDataAsString());
}
responseResult.setHeaders(result.getResponseHeaders());
responseResult.setLatency(result.getLatency());
responseResult.setResponseCode(result.getResponseCode());
responseResult.setResponseSize(result.getResponseData().length);
responseResult.setResponseTime(result.getTime());
responseResult.setResponseMessage(result.getResponseMessage());
JMeterVariables variables = JMeterVars.get(result.getResourceId());
if (StringUtils.isNotEmpty(result.getExtVars())) {
responseResult.setVars(result.getExtVars());
JMeterVars.remove(result.getResourceId());
} else if (variables != null && CollectionUtils.isNotEmpty(variables.entrySet())) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, Object> entry : variables.entrySet()) {
builder.append(entry.getKey()).append("").append(entry.getValue()).append("\n");
}
if (StringUtils.isNotEmpty(builder)) {
responseResult.setVars(builder.toString());
}
JMeterVars.remove(result.getResourceId());
}
for (AssertionResult assertionResult : result.getAssertionResults()) {
ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult);
if (responseAssertionResult.isPass()) {
requestResult.addPassAssertions();
}
//xpath 提取错误会添加断言错误
if (StringUtils.isBlank(responseAssertionResult.getMessage()) ||
(StringUtils.isNotBlank(responseAssertionResult.getName()) && !responseAssertionResult.getName().endsWith("XPath2Extractor"))
|| (StringUtils.isNotBlank(responseAssertionResult.getContent()) && !responseAssertionResult.getContent().endsWith("XPath2Extractor"))
) {
responseResult.getAssertions().add(responseAssertionResult);
}
}
LoggerUtil.debug("处理结果资源【" + result.getSampleLabel() + "】结束");
return requestResult;
}
private static ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) {
ResponseAssertionResult responseAssertionResult = null;
if (StringUtils.startsWith(assertionResult.getName(), "ErrorReportAssertion")) {
responseAssertionResult = new ErrorReportAssertionResult(assertionResult.getFailureMessage());
} else {
responseAssertionResult = new ResponseAssertionResult();
}
responseAssertionResult.setName(assertionResult.getName());
if (StringUtils.isNotEmpty(assertionResult.getName()) && assertionResult.getName().indexOf("split==") != -1) {
if (assertionResult.getName().indexOf("JSR223") != -1) {
String[] array = assertionResult.getName().split("split==", 3);
if (array.length > 2 && "JSR223".equals(array[0])) {
responseAssertionResult.setName(array[1]);
if (array[2].indexOf("split&&") != -1) {
String[] content = array[2].split("split&&");
responseAssertionResult.setContent(content[0]);
if (content.length > 1) {
responseAssertionResult.setScript(content[1]);
}
} else {
responseAssertionResult.setContent(array[2]);
}
}
} else {
String[] array = assertionResult.getName().split("split==");
responseAssertionResult.setName(array[0]);
StringBuffer content = new StringBuffer();
for (int i = 1; i < array.length; i++) {
content.append(array[i]);
}
responseAssertionResult.setContent(content.toString());
}
}
responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError());
if (!responseAssertionResult.isPass()) {
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
}
return responseAssertionResult;
}
private static String getMethod(SampleResult result) {
String body = result.getSamplerData();
String start = "RPC Protocol: ";
String end = "://";
if (StringUtils.contains(body, start)) {
String protocol = StringUtils.substringBetween(body, start, end);
if (StringUtils.isNotEmpty(protocol)) {
return protocol.toUpperCase();
}
return "DUBBO";
} else if (StringUtils.contains(result.getResponseHeaders(), "url:jdbc")) {
return "SQL";
} else {
String method = StringUtils.substringBefore(body, " ");
for (HttpMethodConstants value : HttpMethodConstants.values()) {
if (StringUtils.equals(method, value.name())) {
return method;
}
}
return "Request";
}
}
/**
* 执行结果数据转化
*
* @param sampleResults
* @param dto
*/
public static void resultFormatting(List<SampleResult> sampleResults, ResultDTO dto) {
try {
List<RequestResult> requestResults = new LinkedList<>();
List<String> environmentList = new ArrayList<>();
sampleResults.forEach(result -> {
ListenerUtil.setVars(result);
RequestResult requestResult = JMeterBase.getRequestResult(result);
if (StringUtils.equals(result.getSampleLabel(), ListenerUtil.RUNNING_DEBUG_SAMPLER_NAME)) {
String evnStr = result.getResponseDataAsString();
environmentList.add(evnStr);
} else {
boolean resultNotFilterOut = ListenerUtil.checkResultIsNotFilterOut(requestResult);
if (resultNotFilterOut) {
if (StringUtils.isNotEmpty(requestResult.getName()) && requestResult.getName().startsWith("Transaction=")) {
transactionFormat(requestResult.getSubRequestResults(), requestResults);
} else {
requestResults.add(requestResult);
}
}
}
});
dto.setRequestResults(requestResults);
ListenerUtil.setEev(dto, environmentList);
} catch (Exception e) {
LoggerUtil.error("JMETER-调用存储方法失败", dto.getReportId(), e);
}
}
private static void transactionFormat(List<RequestResult> requestResults, List<RequestResult> refRes) {
for (RequestResult requestResult : requestResults) {
if (CollectionUtils.isEmpty(requestResult.getSubRequestResults())) {
refRes.add(requestResult);
} else {
transactionFormat(requestResult.getSubRequestResults(), refRes);
}
}
}
}

View File

@ -0,0 +1,56 @@
package io.metersphere.jmeter;
import io.metersphere.cache.JMeterEngineCache;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.engine.JMeterEngineException;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
public class LocalRunner {
private HashTree jmxTree;
public LocalRunner(HashTree jmxTree) {
this.jmxTree = jmxTree;
}
public LocalRunner() {
}
public void run(String report) {
StandardJMeterEngine engine = new StandardJMeterEngine();
engine.configure(jmxTree);
try {
LoggerUtil.info("LocalRunner 开始执行报告",report);
engine.runTest();
JMeterEngineCache.runningEngine.put(report, engine);
} catch (JMeterEngineException e) {
engine.stopTest(true);
}
}
public void stop(List<String> reports) {
if (CollectionUtils.isNotEmpty(reports)) {
for (String report : reports) {
StandardJMeterEngine engine = JMeterEngineCache.runningEngine.get(report);
if (engine != null) {
engine.stopTest();
JMeterEngineCache.runningEngine.remove(report);
}
}
}
}
public void stop(String report) {
if (StringUtils.isNotEmpty(report)) {
StandardJMeterEngine engine = JMeterEngineCache.runningEngine.get(report);
if (engine != null) {
engine.stopTest();
JMeterEngineCache.runningEngine.remove(report);
}
}
}
}

View File

@ -0,0 +1,62 @@
package io.metersphere.jmeter;
import groovy.lang.GroovyClassLoader;
import io.metersphere.utils.JsonUtils;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.net.URL;
import java.util.List;
public class MsClassLoader {
public static MsDynamicClassLoader loadJar(List<String> jarPaths) {
if (CollectionUtils.isEmpty(jarPaths)) {
return null;
}
LoggerUtil.info("开始初始化JAR[ " + JsonUtils.toJSONString(jarPaths) + " ]");
MsDynamicClassLoader urlClassLoader = new MsDynamicClassLoader();
jarPaths.forEach(path -> {
try {
if (StringUtils.isNotEmpty(path)) {
File jarFile = new File(path);
if (jarFile.exists()) {
URL jarUrl = jarFile.toURI().toURL();
urlClassLoader.addURLFile(jarUrl);
}
}
LoggerUtil.info("完成初始化JAR[ " + path + " ]");
} catch (Exception ex) {
LoggerUtil.error("加载JAR包失败", ex);
}
});
return urlClassLoader;
}
public static GroovyClassLoader getDynamic(List<String> jarPaths) {
if (CollectionUtils.isNotEmpty(jarPaths)) {
GroovyClassLoader dynamicClassLoader = new GroovyClassLoader();
jarPaths.forEach(path -> {
try {
if (StringUtils.isNotEmpty(path)) {
File jarFile = new File(path);
if (jarFile.exists()) {
dynamicClassLoader.addURL(jarFile.toURI().toURL());
LoggerUtil.info("初始化JAR[ " + path + " ] 成功");
} else {
LoggerUtil.info("JAR文件" + path + "】已经丢失,加载失败");
}
}
} catch (Exception ex) {
LoggerUtil.error("加载JAR包失败", ex);
}
});
return dynamicClassLoader;
}
return null;
}
private MsClassLoader() {
}
}

View File

@ -0,0 +1,51 @@
package io.metersphere.jmeter;
import io.metersphere.utils.LoggerUtil;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
public class MsDynamicClassLoader extends URLClassLoader {
public MsDynamicClassLoader() {
super(new URL[]{}, findParentClassLoader());
}
/**
* 将指定的文件url添加到类加载器的classpath中去并缓存jar connection方便以后卸载jar
* 一个可想类加载器的classpath中添加的文件url
*
* @param
*/
public void addURLFile(URL file) {
try {
// 打开并缓存文件url连接
URLConnection uc = file.openConnection();
if (uc instanceof JarURLConnection) {
uc.setUseCaches(true);
((JarURLConnection) uc).getManifest();
}
} catch (Exception e) {
LoggerUtil.error("Failed to cache plugin JAR file: " + file.toExternalForm());
}
addURL(file);
}
/**
* 定位基于当前上下文的父类加载器
*
* @return 返回可用的父类加载器.
*/
private static ClassLoader findParentClassLoader() {
ClassLoader parent = ClassLoader.getSystemClassLoader();
if (parent == null) {
parent = MsDynamicClassLoader.class.getClassLoader();
}
if (parent == null) {
parent = ClassLoader.getSystemClassLoader();
}
return parent;
}
}

View File

@ -0,0 +1,85 @@
package io.metersphere.utils;
import groovy.lang.GroovyClassLoader;
import io.metersphere.jmeter.MsClassLoader;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.functions.Function;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.reflect.ClassFinder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CustomizeFunctionUtil {
public static final String CUSTOMIZE_FUNCTION = "CUSTOMIZE_FUNCTION";
private static final String CONTAIN = "classfinder.functions.contain";
private static final String NOT_CONTAIN = "classfinder.functions.notContain";
public static final String JAR_PATH = "JAR_PATH";
public static final String MS_CLASS_LOADER = "MS_CLASS_LOADER";
public static void initCustomizeClass(TestPlan testPlan) {
try {
Map<String, Class<? extends Function>> functions = new HashMap<>();
String pathStr = testPlan.getPropertyAsString(JAR_PATH);
JMeterContext context = JMeterContextService.getContext();
if (StringUtils.isNotEmpty(pathStr) && context != null) {
List<String> jarPaths = JsonUtils.parseObject(pathStr, List.class);
if (CollectionUtils.isNotEmpty(jarPaths)) {
// 初始化类加载器
GroovyClassLoader loader = MsClassLoader.getDynamic(jarPaths);
if (loader == null) {
return;
}
// 所有自定义jar的类加载器
context.getVariables().putObject(MS_CLASS_LOADER, loader);
Thread.currentThread().setContextClassLoader(loader);
// 获取所有自定义函数class
List<String> classes = ClassFinder.findClassesThatExtend(
jarPaths.toArray(String[]::new),
new Class[]{Function.class}, true,
JMeterUtils.getProperty(CONTAIN),
JMeterUtils.getProperty(NOT_CONTAIN)
);
for (String clazzName : classes) {
Function tempFunc = loader.loadClass(clazzName)
.asSubclass(Function.class)
.getDeclaredConstructor().newInstance();
String referenceKey = tempFunc.getReferenceKey();
if (StringUtils.isNotEmpty(referenceKey)) { // ignore self
functions.put(referenceKey, tempFunc.getClass());
}
}
}
}
if (MapUtils.isNotEmpty(functions)) {
context.getVariables().putObject(CUSTOMIZE_FUNCTION, functions);
}
} catch (Exception e) {
LoggerUtil.error(e);
}
}
public static Map<String, Class<? extends Function>> getFunctions() {
try {
JMeterContext context = JMeterContextService.getContext();
if (context != null && ObjectUtils.isNotEmpty(context.getVariables())) {
Object function = context.getVariables().getObject(CUSTOMIZE_FUNCTION);
if (ObjectUtils.isNotEmpty(function)) {
return (Map<String, Class<? extends Function>>) function;
}
}
return new HashMap<>();
} catch (Exception e) {
LoggerUtil.error(e);
return new HashMap<>();
}
}
}

View File

@ -0,0 +1,195 @@
package io.metersphere.utils;
import com.google.gson.Gson;
import io.metersphere.vo.Condition;
import io.metersphere.vo.ElementCondition;
import net.minidev.json.JSONArray;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.oro.text.regex.Pattern;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;
public class DocumentUtils {
public static boolean documentChecked(Object subj, String condition, ThreadLocal<DecimalFormat> decimalFormatter) {
if (StringUtils.isNotEmpty(condition)) {
ElementCondition elementCondition = JsonUtils.parseObject(condition, ElementCondition.class);
boolean isTrue = true;
if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
for (Condition item : elementCondition.getConditions()) {
String expectedValue = item.getValue() != null ? item.getValue().toString() : "";
String resValue = objectToString(subj, decimalFormatter);
switch (item.getKey()) {
case "value_eq":
isTrue = valueEquals(resValue, expectedValue);
break;
case "value_not_eq":
isTrue = valueNotEquals(resValue, expectedValue);
break;
case "value_in":
isTrue = StringUtils.contains(resValue, expectedValue);
break;
case "length_eq":
isTrue = getLength(subj, decimalFormatter) == numberOf(item.getValue());
break;
case "length_not_eq":
isTrue = getLength(subj, decimalFormatter) != numberOf(item.getValue());
break;
case "length_gt":
isTrue = getLength(subj, decimalFormatter) > numberOf(item.getValue());
break;
case "length_lt":
isTrue = getLength(subj, decimalFormatter) < numberOf(item.getValue());
break;
case "regular":
Pattern pattern = JMeterUtils.getPatternCache().getPattern(expectedValue);
isTrue = JMeterUtils.getMatcher().matches(resValue, pattern);
break;
}
if (!isTrue) {
break;
}
}
}
return isTrue && checkType(elementCondition, subj);
}
return true;
}
public static boolean checkType(ElementCondition elementCondition, Object subj) {
if (elementCondition.isTypeVerification()) {
return StringUtils.equalsIgnoreCase(elementCondition.getType(), "object") || (subj != null
&& StringUtils.equalsIgnoreCase(elementCondition.getType(), getType(subj)));
}
return true;
}
public static String getType(Object object) {
String type = object.getClass().getName().substring(object.getClass().getName().lastIndexOf(".") + 1);
if (StringUtils.equalsIgnoreCase("Integer", type)) {
return type.toLowerCase();
} else if (StringUtils.equalsAnyIgnoreCase(type, "integer", "float", "long", "double")) {
return "number";
} else if (StringUtils.indexOfAny(type, "Array", "List") != -1) {
return "array";
}
return type.toLowerCase();
}
private static boolean valueEquals(String v1, String v2) {
try {
Number number1 = NumberUtils.createNumber(v1);
Number number2 = NumberUtils.createNumber(v2);
return number1.equals(number2);
} catch (Exception e) {
return StringUtils.equals(v1, v2);
}
}
private static boolean valueNotEquals(String v1, String v2) {
try {
Number number1 = NumberUtils.createNumber(v1);
Number number2 = NumberUtils.createNumber(v2);
return !number1.equals(number2);
} catch (Exception e) {
return !StringUtils.equals(v1, v2);
}
}
public static String objectToString(Object subj, ThreadLocal<DecimalFormat> decimalFormatter) {
String str;
if (subj == null) {
str = "null";
} else if (subj instanceof Map) {
str = new Gson().toJson(subj);
} else if (!(subj instanceof Double) && !(subj instanceof Float)) {
str = subj.toString();
} else {
str = ((DecimalFormat) decimalFormatter.get()).format(subj);
}
return str;
}
private static int getLength(Object value) {
if (value != null) {
if (value instanceof List) {
return ((List) value).size();
}
return value.toString().length();
}
return 0;
}
private static int getLength(Object value, ThreadLocal<DecimalFormat> decimalFormatter) {
if (value != null) {
if (value instanceof Map) {
return ((Map) value).size();
} else if (value instanceof List) {
return ((List) value).size();
} else if (!(value instanceof Double) && !(value instanceof Float)) {
return value.toString().length();
} else {
return ((DecimalFormat) decimalFormatter.get()).format(value).length();
}
}
return 0;
}
private static long numberOf(Object value) {
if (value != null) {
try {
return Long.parseLong(value.toString());
} catch (Exception e) {
return 0;
}
}
return 0;
}
private static String arrayMatched(JSONArray value) {
if (value.isEmpty()) {
return "array";
} else {
Object[] var2 = value.toArray();
int var3 = var2.length;
for (int var4 = 0; var4 < var3; ++var4) {
Object subj = var2[var4];
return getType(subj);
}
}
return getType(value);
}
public static String documentMsg(String name, Object resValue, String condition) {
String msg = "";
if (StringUtils.isNotEmpty(condition)) {
ElementCondition elementCondition = JsonUtils.parseObject(condition, ElementCondition.class);
if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
for (Condition item : elementCondition.getConditions()) {
if (StringUtils.equalsAny(item.getKey(), "value_eq", "value_not_eq", "value_in")) {
msg = resValue != null ? resValue.toString() : "";
} else if (StringUtils.equalsAny(item.getKey(), "length_eq", "length_not_eq", "length_gt", "length_lt")) {
msg = "长度是:" + getLength(resValue) + "";
} else {
msg = resValue != null ? resValue.toString() : "";
}
}
}
if (!checkType(elementCondition, resValue)) {
if (resValue instanceof JSONArray) {
msg = " 类型:" + (arrayMatched((JSONArray) resValue)) + ", 值:" + msg;
} else {
msg = " 类型:" + getType(resValue) + ", 值:" + msg;
}
}
}
return (StringUtils.isNotEmpty(name) ? name.split("==")[1] : "") + "校验失败,实际返回:" + msg;
}
}

View File

@ -0,0 +1,119 @@
package io.metersphere.utils;
import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.extractor.JSR223PostProcessor;
import org.apache.jmeter.extractor.RegexExtractor;
import org.apache.jmeter.extractor.XPath2Extractor;
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jorphan.collections.HashTree;
import java.util.*;
public class JMeterVars {
private JMeterVars() {
}
/**
* 数据和线程变量保持一致
*/
private static Map<String, JMeterVariables> variables = new HashMap<>();
/**
* 线程执行过程调用提取变量值
*
* @param testId
* @param vars
* @param extract
*/
public static void addVars(String testId, JMeterVariables vars, String extract) {
JMeterVariables vs = variables.get(testId);
if (vs == null) {
vs = new JMeterVariables();
}
if (!StringUtils.isEmpty(extract) && vars != null) {
List<String> extracts = Arrays.asList(extract.split(";"));
if (CollectionUtils.isNotEmpty(extracts)) {
for (String item : extracts) {
String nrKey = item + "_matchNr";
Object nr = vars.get(nrKey);
JMeterVariables jMeterVariables = new JMeterVariables();
if (nr != null) {
int nrv = 0;
try {
nrv = Integer.valueOf(String.valueOf(nr));
} catch (Exception e) {
}
if (nrv > 0) {
List<Object> data = new ArrayList<>();
for (int i = 1; i < nrv + 1; i++) {
data.add(vars.get(item + "_" + i));
}
String array = JsonUtils.toJSONString(data);
jMeterVariables.put(item, array);
}
}
if (jMeterVariables.get(item) != null) {
vs.put(item, jMeterVariables.get(item));
} else {
vs.put(item, vars.get(item) == null ? "" : vars.get(item));
}
}
vs.remove("TESTSTART.MS"); // 标示变量移除
}
}
variables.put(testId, vs);
}
/**
* 处理所有请求有提取变量的请求增加后置脚本提取变量值
*
* @param tree
*/
public static void addJSR223PostProcessor(HashTree tree) {
for (Object key : tree.keySet()) {
HashTree node = tree.get(key);
if (key instanceof HTTPSamplerProxy || key instanceof DubboSample || key instanceof JDBCSampler) {
StringJoiner extract = new StringJoiner(";");
for (Object child : node.keySet()) {
if (child instanceof RegexExtractor) {
RegexExtractor regexExtractor = (RegexExtractor) child;
extract.add(regexExtractor.getRefName());
} else if (child instanceof XPath2Extractor) {
XPath2Extractor regexExtractor = (XPath2Extractor) child;
extract.add(regexExtractor.getRefName());
} else if (child instanceof JSONPostProcessor) {
JSONPostProcessor regexExtractor = (JSONPostProcessor) child;
extract.add(regexExtractor.getRefNames());
}
}
if (Optional.ofNullable(extract).orElse(extract).length() > 0) {
JSR223PostProcessor shell = new JSR223PostProcessor();
shell.setEnabled(true);
shell.setProperty("script", "io.metersphere.utils.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + extract.toString() + "\"" + ");");
node.add(shell);
}
}
if (node != null) {
addJSR223PostProcessor(node);
}
}
}
public static JMeterVariables get(String key) {
return variables.get(key);
}
public static void remove(String key) {
variables.remove(key);
}
}

View File

@ -0,0 +1,58 @@
package io.metersphere.utils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class JsonUtils {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static String toJSONString(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Object parseObject(String content) {
return parseObject(content, Object.class);
}
public static <T> T parseObject(String content, Class<T> valueType) {
try {
return objectMapper.readValue(content, valueType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> T parseObject(InputStream src, Class<T> valueType) {
try {
return objectMapper.readValue(src, valueType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static List parseArray(String content) {
return parseArray(content, Object.class);
}
public static <T> List<T> parseArray(String content, Class<T> valueType) {
CollectionType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, valueType);
try {
return objectMapper.readValue(content, javaType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,66 @@
package io.metersphere.utils;
import io.metersphere.dto.RequestResult;
import io.metersphere.dto.ResultDTO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.JMeterVariables;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ListenerUtil {
public static final String ERROR_LOGGING = "MsResultCollector.error_logging";
public static final String TEST_IS_LOCAL = "*local*";
public static final String SUCCESS_ONLY_LOGGING = "MsResultCollector.success_only_logging";
public static final String RUNNING_DEBUG_SAMPLER_NAME = "RunningDebugSampler";
private static final String PRE_PROCESS_SCRIPT = "PRE_PROCESSOR_ENV_";
private static final String POST_PROCESS_SCRIPT = "POST_PROCESSOR_ENV_";
/**
* 判断结果是否需要被过滤
*
* @param result
* @return
*/
public static boolean checkResultIsNotFilterOut(RequestResult result) {
boolean resultNotFilterOut = true;
if (StringUtils.startsWithAny(result.getName(), PRE_PROCESS_SCRIPT)) {
resultNotFilterOut = Boolean.parseBoolean(StringUtils.substring(result.getName(), PRE_PROCESS_SCRIPT.length()));
} else if (StringUtils.startsWithAny(result.getName(), POST_PROCESS_SCRIPT)) {
resultNotFilterOut = Boolean.parseBoolean(StringUtils.substring(result.getName(), POST_PROCESS_SCRIPT.length()));
}
return resultNotFilterOut;
}
public static void setVars(SampleResult result) {
if (StringUtils.isNotEmpty(result.getSampleLabel()) && result.getSampleLabel().startsWith("Transaction=")) {
for (int i = 0; i < result.getSubResults().length; i++) {
SampleResult subResult = result.getSubResults()[i];
setVars(subResult);
}
}
JMeterVariables variables = JMeterVars.get(result.getResourceId());
if (variables != null && CollectionUtils.isNotEmpty(variables.entrySet())) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, Object> entry : variables.entrySet()) {
builder.append(entry.getKey()).append("").append(entry.getValue()).append("\n");
}
if (StringUtils.isNotEmpty(builder)) {
result.setExtVars(builder.toString());
}
}
}
public static void setEev(ResultDTO dto, List<String> environmentList) {
dto.setArbitraryData(new HashMap<String, Object>() {{
this.put("ENV", environmentList);
}});
}
}

View File

@ -0,0 +1,287 @@
package io.metersphere.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class LoggerUtil {
//日志工具类
public static final String DEBUG = "DEBUG";
public static final String INFO = "INFO";
public static final String WARN = "WARN";
public static final String ERROR = "ERROR";
/**
* 初始化日志
*
* @return
*/
public static Logger getLogger() {
return LoggerFactory.getLogger("ms-jmeter-run-log");
}
public static void writeLog(Object msg, String level) {
Logger logger = LoggerUtil.getLogger();
if (DEBUG.equals(level)) {
if (logger != null && logger.isDebugEnabled()) {
logger.debug(LoggerUtil.getMsg(msg));
}
} else if (INFO.equals(level)) {
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getMsg(msg));
}
} else if (WARN.equals(level)) {
if (logger != null && logger.isWarnEnabled()) {
logger.warn(LoggerUtil.getMsg(msg));
}
} else if (ERROR.equals(level)) {
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(msg));
}
} else {
if (logger != null && logger.isErrorEnabled()) {
logger.error("");
}
}
}
public static void info(Object msg) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getMsg(msg));
}
}
public static void info(Object msg, Object o1) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getMsg(msg), o1);
}
}
public static void info(String msg, String reportId) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getLogMethod() + "[REPORT-ID: " + reportId + "] " + msg);
}
}
public static void info(String msg, String reportId, Object o1) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getLogMethod() + "[REPORT-ID: " + reportId + "] " + msg, o1);
}
}
public static void info(Object msg, Object o1, Object o2) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getMsg(msg), o1, o2);
}
}
public static void info(Object msg, Object[] obj) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.info(LoggerUtil.getMsg(msg), obj);
}
}
public static void debug(Object msg) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isDebugEnabled()) {
logger.debug(LoggerUtil.getMsg(msg));
}
}
public static void debug(Object msg, Object o) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isDebugEnabled()) {
logger.debug(LoggerUtil.getMsg(msg), o);
}
}
public static void debug(Object msg, Object o1, Object o2) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isDebugEnabled()) {
logger.debug(LoggerUtil.getMsg(msg), o1, o2);
}
}
public static void debug(Object msg, Object[] obj) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isDebugEnabled()) {
logger.debug(LoggerUtil.getMsg(msg), obj);
}
}
public static void warn(Object msg) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isWarnEnabled()) {
logger.warn(LoggerUtil.getMsg(msg));
}
}
public static void warn(Object msg, Object o) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isWarnEnabled()) {
logger.warn(LoggerUtil.getMsg(msg), o);
}
}
public static void warn(Object msg, Object o1, Object o2) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isWarnEnabled()) {
logger.warn(LoggerUtil.getMsg(msg), o1, o2);
}
}
public static void warn(Object msg, Object[] obj) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isWarnEnabled()) {
logger.warn(LoggerUtil.getMsg(msg), obj);
}
}
public static void error(Object msg) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(msg));// 并追加方法名称
}
}
public static void error(Throwable e) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(e), e);// 同时打印错误堆栈信息
}
}
public static void error(Object msg, Object o) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(msg), o);
}
}
public static void error(String msg, String reportId, Object o1) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isInfoEnabled()) {
logger.error(LoggerUtil.getLogMethod() + "[REPORT-ID: " + reportId + "] " + msg, o1);
}
}
public static void error(Object msg, Object o1, Object o2) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(msg), o1, o2);
}
}
public static void error(Object msg, Object[] obj) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(msg), obj);
}
}
public static void error(Object msg, Throwable ex) {
Logger logger = LoggerUtil.getLogger();
if (logger != null && logger.isErrorEnabled()) {
logger.error(LoggerUtil.getMsg(msg), ex);
}
}
public static String getMsg(Object msg, Throwable ex) {
String str = "";
if (msg != null) {
str = LoggerUtil.getLogMethod() + "[" + msg.toString() + "]";
} else {
str = LoggerUtil.getLogMethod() + "[null]";
}
if (ex != null) {
str += "[" + ex.getMessage() + "]";
}
return str;
}
public static String getMsg(Object msg) {
return LoggerUtil.getMsg(msg, null);
}
/**
* 得到调用类名称
*
* @return
*/
private static String getLogClass() {
String str = "";
StackTraceElement[] stack = (new Throwable()).getStackTrace();
if (stack.length > 3) {
StackTraceElement ste = stack[3];
str = ste.getClassName();// 类名称
}
return str;
}
/**
* 得到调用方法名称
*
* @return
*/
private static String getLogMethod() {
String str = "";
StackTraceElement[] stack = (new Throwable()).getStackTrace();
if (stack.length > 4) {
StackTraceElement ste = stack[4];
str = "Method[" + ste.getMethodName() + "]";// 方法名称
}
return str;
}
public static String toString(Throwable e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
//将出错的栈信息输出到printWriter中
e.printStackTrace(pw);
pw.flush();
sw.flush();
} finally {
if (sw != null) {
try {
sw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (pw != null) {
pw.close();
}
}
return sw.toString();
}
public static String getExceptionDetailsToStr(Exception e) {
StringBuilder sb = new StringBuilder(e.toString());
StackTraceElement[] stackElements = e.getStackTrace();
for (StackTraceElement stackTraceElement : stackElements) {
sb.append(stackTraceElement.toString());
sb.append("\n");
}
sb.append("\n");
return sb.toString();
}
}

View File

@ -0,0 +1,84 @@
package io.metersphere.utils;
import io.metersphere.dto.RequestResult;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.samplers.SampleResult;
import java.util.*;
import java.util.stream.Collectors;
/**
* 重试报告处理util
*/
public class RetryResultUtil {
public final static String RETRY = "MsRetry_";
public final static String RETRY_CN = "重试";
public final static String RETRY_FIRST_CN = "首次";
public final static String MS_CLEAR_LOOPS_VAR = "MS_CLEAR_LOOPS_VAR_";
public final static int RETRY_RES_NUM = 11;
/**
* 合并掉重试结果保留最后十次重试结果
*
* @param results
*/
public static void mergeRetryResults(List<RequestResult> results) {
if (CollectionUtils.isNotEmpty(results)) {
Map<String, List<RequestResult>> resultMap = results.stream().collect(Collectors.groupingBy(RequestResult::getResourceId));
List<RequestResult> list = new LinkedList<>();
resultMap.forEach((k, v) -> {
if (CollectionUtils.isNotEmpty(v)) {
// 校验是否含重试结果
List<RequestResult> isRetryResults = v
.stream()
.filter(c -> StringUtils.isNotEmpty(c.getName()) && c.getName().startsWith(RETRY))
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(isRetryResults)) {
// 取最后执行的10
if (v.size() > 10) {
Collections.sort(v, Comparator.comparing(RequestResult::getResourceId));
RequestResult sampleResult = v.get(0);
List<RequestResult> topTens = v.subList(v.size() - RETRY_RES_NUM, v.size());
topTens.set(0, sampleResult);
assembleName(topTens);
list.addAll(topTens);
} else {
assembleName(v);
list.addAll(v);
}
} else {
// 成功的结果
list.addAll(v);
}
}
});
results.clear();
results.addAll(list);
}
}
private static void assembleName(List<RequestResult> list) {
// 名称排序处理
for (int i = 0; i < list.size(); i++) {
list.get(i).setName(list.get(i).getName().replaceAll(RETRY, RETRY_CN));
if (list.get(i).getName().endsWith("_")) {
list.get(i).setName(list.get(i).getName().substring(0, list.get(i).getName().length() - 1));
}
if (i == 0) {
list.get(i).setName(StringUtils.isNotEmpty(list.get(i).getName())
? RETRY_FIRST_CN + "_" + list.get(i).getName() : RETRY_FIRST_CN);
}
}
}
public static List<SampleResult> clearLoops(List<SampleResult> results) {
if (CollectionUtils.isNotEmpty(results)) {
return results.stream().filter(sampleResult ->
StringUtils.isNotEmpty(sampleResult.getSampleLabel())
&& !sampleResult.getSampleLabel().startsWith(MS_CLEAR_LOOPS_VAR))
.collect(Collectors.toList());
}
return results;
}
}

View File

@ -0,0 +1,9 @@
package io.metersphere.vo;
import lombok.Data;
@Data
public class BooleanPool {
private boolean isPool;
private boolean isK8s;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.vo;
import lombok.Data;
@Data
public class Condition {
private String key;
private Object value;
public Condition() {
}
public Condition(String key, Object value) {
this.key = key;
this.value = value;
}
}

View File

@ -0,0 +1,26 @@
package io.metersphere.vo;
import lombok.Data;
import java.util.List;
@Data
public class ElementCondition {
private boolean include;
private boolean typeVerification;
private boolean arrayVerification;
private String type;
List<Condition> conditions;
public ElementCondition() {
}
public ElementCondition(boolean include, boolean typeVerification, boolean arrayVerification, List<Condition> conditions) {
this.include = include;
this.typeVerification = typeVerification;
this.arrayVerification = arrayVerification;
this.conditions = conditions;
}
}

View File

@ -0,0 +1,388 @@
/*
* 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 org.apache.jmeter;
// N.B. this must only use standard Java packages
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Main class for JMeter - sets up initial classpath and the loader.
*/
public final class NewDriver {
private static final String CLASSPATH_SEPARATOR = File.pathSeparator;
private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$
private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH);
private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$
private static final String JMETER_LOGFILE_SYSTEM_PROPERTY = "jmeter.logfile";// $NON-NLS-1$
private static final String HEADLESS_MODE_PROPERTY = "java.awt.headless";// $NON-NLS-1$
/**
* The class loader to use for loading JMeter classes.
*/
private static final DynamicClassLoader loader;
/**
* The directory JMeter is installed in.
*/
private static final String JMETER_INSTALLATION_DIRECTORY;
private static final List<Exception> EXCEPTIONS_IN_INIT = new ArrayList<>();
// 将当前类加载器设置为 loader 解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题
public static void setContextClassLoader() {
Thread.currentThread().setContextClassLoader(loader);
}
public static void loaderClass(String name) {
try {
loader.loadClass(name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
static {
final List<URL> jars = new ArrayList<>();
final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);
// Find JMeter home dir from the initial classpath
String tmpDir;
//只从 jmeter.home 加载
tmpDir = System.getProperty("jmeter.home", "");// Allow override $NON-NLS-1$ $NON-NLS-2$
if (tmpDir.length() == 0) {
File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$
tmpDir = userDir.getAbsoluteFile().getParent();
}
if (tmpDir == null) {
tmpDir = System.getenv("JMETER_HOME");
}
JMETER_INSTALLATION_DIRECTORY = tmpDir;
/*
* Does the system support UNC paths? If so, may need to fix them up
* later
*/
boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$
// Add standard jar locations to initial classpath
StringBuilder classpath = new StringBuilder();
File[] libDirs = new File[]{new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$
for (File libDir : libDirs) {
File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (libJars == null) {
new Throwable("Could not access " + libDir).printStackTrace(); // NOSONAR No logging here
continue;
}
Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars
for (File libJar : libJars) {
try {
String s = libJar.getPath();
// Fix path to allow the use of UNC URLs
if (usesUNC) {
if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$
s = "\\\\" + s;// $NON-NLS-1$
} else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$
s = "//" + s;// $NON-NLS-1$
}
} // usesUNC
jars.add(new File(s).toURI().toURL());// See Java bug 4496398
classpath.append(CLASSPATH_SEPARATOR);
classpath.append(s);
} catch (MalformedURLException e) { // NOSONAR
EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:" + libJar.getAbsolutePath(), e));
}
}
}
// ClassFinder needs the classpath
System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString());
loader = createClassLoader(jars);
}
@SuppressWarnings("removal")
private static DynamicClassLoader createClassLoader(List<URL> jars) {
return java.security.AccessController.doPrivileged(
(java.security.PrivilegedAction<DynamicClassLoader>) () ->
new DynamicClassLoader(jars.toArray(new URL[jars.size()]), Thread.currentThread().getContextClassLoader())
);
}
/**
* Prevent instantiation.
*/
private NewDriver() {
}
/**
* Generate an array of jar files located in a directory.
* Jar files located in sub directories will not be added.
*
* @param dir to search for the jar files.
*/
private static File[] listJars(File dir) {
if (dir.isDirectory()) {
return dir.listFiles((f, name) -> {
if (name.endsWith(".jar")) {// $NON-NLS-1$
File jar = new File(f, name);
return jar.isFile() && jar.canRead();
}
return false;
});
}
return new File[0];
}
/**
* Add a URL to the loader classpath only; does not update the system classpath.
*
* @param path to be added.
* @throws MalformedURLException when <code>path</code> points to an invalid url
*/
public static void addURL(String path) throws MalformedURLException {
File furl = new File(path);
loader.addURL(furl.toURI().toURL()); // See Java bug 4496398
File[] jars = listJars(furl);
for (File jar : jars) {
loader.addURL(jar.toURI().toURL()); // See Java bug 4496398
}
}
/**
* Add a URL to the loader classpath only; does not update the system
* classpath.
*
* @param url The {@link URL} to add to the classpath
*/
public static void addURL(URL url) {
loader.addURL(url);
}
/**
* Add a directory or jar to the loader and system classpaths.
*
* @param path to add to the loader and system classpath
* @throws MalformedURLException if <code>path</code> can not be transformed to a valid
* {@link URL}
*/
public static void addPath(String path) throws MalformedURLException {
File file = new File(path);
// Ensure that directory URLs end in "/"
if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$
file = new File(path + "/");// $NON-NLS-1$
}
loader.addURL(file.toURI().toURL()); // See Java bug 4496398
StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH));
sb.append(CLASSPATH_SEPARATOR);
sb.append(path);
File[] jars = listJars(file);
for (File jar : jars) {
loader.addURL(jar.toURI().toURL()); // See Java bug 4496398
sb.append(CLASSPATH_SEPARATOR);
sb.append(jar.getPath());
}
// ClassFinder needs this
System.setProperty(JAVA_CLASS_PATH, sb.toString());
}
/**
* Get the directory where JMeter is installed. This is the absolute path
* name.
*
* @return the directory where JMeter is installed.
*/
public static String getJMeterDir() {
return JMETER_INSTALLATION_DIRECTORY;
}
/**
* The main program which actually runs JMeter.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
if (!EXCEPTIONS_IN_INIT.isEmpty()) {
System.err.println("Configuration error during init, see exceptions:" + exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use
} else {
Thread.currentThread().setContextClassLoader(loader);
setLoggingProperties(args);
try {
// Only set property if it has not been set explicitely
if (System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) {
System.setProperty(HEADLESS_MODE_PROPERTY, "true");
}
Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$
Object instance = initialClass.getDeclaredConstructor().newInstance();
Method startup = initialClass.getMethod("start", new Class[]{new String[0].getClass()});// $NON-NLS-1$
startup.invoke(instance, new Object[]{args});
} catch (Throwable e) { // NOSONAR We want to log home directory in case of exception
e.printStackTrace(); // NOSONAR No logger at this step
System.err.println("JMeter home directory was detected as: " + JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use
}
}
}
/**
* @param exceptionsInInit List of {@link Exception}
* @return String
*/
private static String exceptionsToString(List<Exception> exceptionsInInit) {
StringBuilder builder = new StringBuilder();
for (Exception exception : exceptionsInInit) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
exception.printStackTrace(printWriter); // NOSONAR
builder.append(stringWriter.toString())
.append("\r\n");
}
return builder.toString();
}
/*
* Set logging related system properties.
*/
private static void setLoggingProperties(String[] args) {
String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$
if (jmLogFile != null && !jmLogFile.isEmpty()) {
jmLogFile = replaceDateFormatInFileName(jmLogFile);
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$
} else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$
}
String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$
File logConfFile = null;
if (jmLogConf != null && !jmLogConf.isEmpty()) {
logConfFile = new File(jmLogConf);
} else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$
logConfFile = new File("log4j2.xml");// $NON-NLS-1$
if (!logConfFile.isFile()) {
logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$
}
}
if (logConfFile != null) {
System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$
}
}
private static boolean shouldBeHeadless(String[] args) {
for (String arg : args) {
if ("-n".equals(arg) || "-s".equals(arg) || "-g".equals(arg)) {
return true;
}
}
return false;
}
/*
* Find command line argument option value by the id and name.
*/
private static String getCommandLineArgument(String[] args, int id, String name) {
final String shortArgName = "-" + ((char) id);// $NON-NLS-1$
final String longArgName = "--" + name;// $NON-NLS-1$
String value = null;
for (int i = 0; i < args.length; i++) {
if ((shortArgName.equals(args[i]) && i < args.length - 1)
|| longArgName.equals(args[i])) {
if (!args[i + 1].startsWith("-")) {// $NON-NLS-1$
value = args[i + 1];
}
break;
} else if (!shortArgName.equals(args[i]) && args[i].startsWith(shortArgName)) {
value = args[i].substring(shortArgName.length());
break;
}
}
return value;
}
/*
* If the fileName contains at least one set of paired single-quotes, reformat using DateFormat
*/
private static String replaceDateFormatInFileName(String fileName) {
try {
StringBuilder builder = new StringBuilder();
final Instant date = Instant.now();
int fromIndex = 0;
int begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$
int end;
String format;
DateTimeFormatter dateFormat;
while (begin != -1) {
builder.append(fileName.substring(fromIndex, begin));
fromIndex = begin + 1;
end = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$
if (end == -1) {
throw new IllegalArgumentException("Invalid pairs of single-quotes in the file name: " + fileName);// $NON-NLS-1$
}
format = fileName.substring(begin + 1, end);
dateFormat = DateTimeFormatter.ofPattern(format).withZone(ZoneId.systemDefault());
builder.append(dateFormat.format(date));
fromIndex = end + 1;
begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$
}
if (fromIndex < fileName.length() - 1) {
builder.append(fileName.substring(fromIndex));
}
return builder.toString();
} catch (Exception ex) {
System.err.println("Error replacing date format in file name:" + fileName + ", error:" + ex.getMessage()); // NOSONAR
}
return fileName;
}
}

View File

@ -0,0 +1,20 @@
package org.apache.jmeter.assertions;
import org.apache.jmeter.samplers.SampleResult;
public class ErrorReportAssertion extends ResponseAssertion{
@Override
public AssertionResult getResult(SampleResult response) {
AssertionResult result = super.getResult(response);
String faliurMessage = result.getFailureMessage();
if(result.isError() || result.isFailure()){
faliurMessage = faliurMessage+" Final result is error";
} else {
faliurMessage = faliurMessage+" Final result is success";
}
result.setError(false);
result.setFailure(false);
result.setFailureMessage(faliurMessage);
return result;
}
}

View File

@ -0,0 +1,361 @@
/*
* 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 org.apache.jmeter.assertions;
import com.jayway.jsonpath.JsonPath;
import io.metersphere.utils.DocumentUtils;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.ThreadListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.oro.text.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* This is main class for JSONPath Assertion which verifies assertion on
* previous sample result using JSON path expression
*
* @since 4.0
*/
public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class);
private static final long serialVersionUID = 2L;
public static final String JSONPATH = "JSON_PATH";
public static final String EXPECTEDVALUE = "EXPECTED_VALUE";
public static final String JSONVALIDATION = "JSONVALIDATION";
public static final String EXPECT_NULL = "EXPECT_NULL";
public static final String INVERT = "INVERT";
public static final String ISREGEX = "ISREGEX";
private static final boolean USE_JAVA_REGEX = !JMeterUtils.getPropDefault(
"jmeter.regex.engine", "oro").equalsIgnoreCase("oro");
private static ThreadLocal<DecimalFormat> decimalFormatter =
ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat);
private static DecimalFormat createDecimalFormat() {
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
decimalFormatter.setMaximumFractionDigits(340); // java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340
decimalFormatter.setMinimumFractionDigits(1);
return decimalFormatter;
}
public String getOption() {
return getPropertyAsString("ASS_OPTION");
}
public String getElementCondition() {
return getPropertyAsString("ElementCondition");
}
public String getJsonPath() {
return getPropertyAsString(JSONPATH);
}
public void setJsonPath(String jsonPath) {
setProperty(JSONPATH, jsonPath);
}
public String getExpectedValue() {
return getPropertyAsString(EXPECTEDVALUE);
}
public void setExpectedValue(String expectedValue) {
setProperty(EXPECTEDVALUE, expectedValue);
}
public void setJsonValidationBool(boolean jsonValidation) {
setProperty(JSONVALIDATION, jsonValidation);
}
public void setExpectNull(boolean val) {
setProperty(EXPECT_NULL, val);
}
public boolean isExpectNull() {
return getPropertyAsBoolean(EXPECT_NULL);
}
public boolean isJsonValidationBool() {
return getPropertyAsBoolean(JSONVALIDATION);
}
public void setInvert(boolean invert) {
setProperty(INVERT, invert);
}
public boolean isInvert() {
return getPropertyAsBoolean(INVERT);
}
public void setIsRegex(boolean flag) {
setProperty(ISREGEX, flag);
}
public boolean isUseRegex() {
return getPropertyAsBoolean(ISREGEX, true);
}
private void doAssert(String jsonString) {
Object value = JsonPath.read(jsonString, getJsonPath());
if (!isJsonValidationBool()) {
if (value instanceof JSONArray) {
JSONArray arrayValue = (JSONArray) value;
if (arrayValue.isEmpty() && !JsonPath.isPathDefinite(getJsonPath())) {
throw new IllegalStateException("JSONPath is indefinite and the extracted Value is an empty Array." +
" Please use an assertion value, to be sure to get a correct result. " + getExpectedValue());
}
}
return;
}
if (value instanceof JSONArray) {
if (arrayMatched((JSONArray) value)) {
return;
}
} else {
if ((isExpectNull() && value == null)
|| isEquals(value)) {
return;
}
}
if (this.isExpectNull()) {
throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value));
} else {
String msg = "";
if (this.isUseRegex()) {
msg = "Value expected to match regexp '%s', but it did not match: '%s'";
} else if (StringUtils.isNotEmpty(getOption()) && !this.isEquals(value)) {
switch (getOption()) {
case "CONTAINS":
msg = "Value contains to be '%s', but found '%s'";
break;
case "NOT_CONTAINS":
msg = "Value not contains to be '%s', but found '%s'";
break;
case "EQUALS":
msg = "Value equals to be '%s', but found '%s'";
break;
case "NOT_EQUALS":
msg = "Value not equals to be '%s', but found '%s'";
break;
case "GT":
msg = "Value > '%s', but found '%s'";
break;
case "LT":
msg = "Value < '%s', but found '%s'";
break;
case "DOCUMENT":
msg = DocumentUtils.documentMsg(this.getName(), value, this.getElementCondition());
break;
}
} else {
msg = "Value expected to be '%s', but found '%s'";
}
throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter)));
}
}
private boolean arrayMatched(JSONArray value) {
List<Boolean> result = new ArrayList<>();
for (Object subj : value.toArray()) {
if (!StringUtils.equals(getOption(), "NOT_CONTAINS")) {
if (subj == null || isEquals(subj)) {
return true;
}
} else {
result.add(isEquals(subj));
}
}
if (CollectionUtils.isNotEmpty(result) && StringUtils.equals(getOption(), "NOT_CONTAINS")) {
if (result.stream().filter(item -> item == true).collect(Collectors.toList()).size() == result.size()) {
return true;
} else {
return false;
}
}
return isEquals(value);
}
private boolean isGt(String v1, String v2) {
try {
BigDecimal value1 = new BigDecimal(v1);
BigDecimal value2 = new BigDecimal(v2);
return value1.compareTo(value2) > 0;
} catch (Exception e) {
return false;
}
}
private boolean isLt(String v1, String v2) {
try {
BigDecimal value1 = new BigDecimal(v1);
BigDecimal value2 = new BigDecimal(v2);
return value1.compareTo(value2) < 0;
} catch (Exception e) {
return false;
}
}
private boolean isEquals(Object subj) {
String str = DocumentUtils.objectToString(subj, decimalFormatter);
if (isUseRegex()) {
if (USE_JAVA_REGEX) {
return JMeterUtils.compilePattern(getExpectedValue()).matcher(str).matches();
} else {
Pattern pattern = JMeterUtils.getPatternCache().getPattern(getExpectedValue());
return JMeterUtils.getMatcher().matches(str, pattern);
}
} else {
if (StringUtils.isNotEmpty(getOption())) {
boolean refFlag = false;
switch (getOption()) {
case "CONTAINS":
refFlag = str.contains(getExpectedValue());
break;
case "NOT_CONTAINS":
refFlag = !str.contains(getExpectedValue());
break;
case "EQUALS":
refFlag = valueEquals(str, getExpectedValue());
break;
case "NOT_EQUALS":
refFlag = valueNotEquals(str, getExpectedValue());
break;
case "GT":
refFlag = isGt(str, getExpectedValue());
break;
case "LT":
refFlag = isLt(str, getExpectedValue());
break;
case "DOCUMENT":
refFlag = DocumentUtils.documentChecked(subj, this.getElementCondition(), decimalFormatter);
break;
}
return refFlag;
}
Object expected = JSONValue.parse(getExpectedValue());
return Objects.equals(expected, subj);
}
}
private static boolean valueEquals(String v1, String v2) {
try {
Number number1 = NumberUtils.createNumber(v1);
Number number2 = NumberUtils.createNumber(v2);
return number1.equals(number2);
} catch (Exception e) {
return StringUtils.equals(v1, v2);
}
}
private static boolean valueNotEquals(String v1, String v2) {
try {
Number number1 = NumberUtils.createNumber(v1);
Number number2 = NumberUtils.createNumber(v2);
return !number1.equals(number2);
} catch (Exception e) {
return !StringUtils.equals(v1, v2);
}
}
@Override
public AssertionResult getResult(SampleResult samplerResult) {
AssertionResult result = new AssertionResult(getName());
String responseData = samplerResult.getResponseDataAsString();
if (responseData.isEmpty()) {
return result.setResultForNull();
}
result.setFailure(false);
result.setFailureMessage("");
if (!isInvert()) {
try {
doAssert(responseData);
} catch (Exception e) {
log.debug("Assertion failed", e);
result.setFailure(true);
result.setFailureMessage(e.getMessage());
}
} else {
try {
doAssert(responseData);
result.setFailure(true);
if (isJsonValidationBool()) {
if (isExpectNull()) {
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches null");
} else {
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches " + getExpectedValue());
}
} else {
result.setFailureMessage("Failed that JSONPath not exists: " + getJsonPath());
}
} catch (Exception e) {
log.debug("Assertion failed, as expected", e);
}
}
return result;
}
public static String objectToString(Object subj) {
String str;
if (subj == null) {
str = "null";
} else if (subj instanceof Map) {
//noinspection unchecked
str = new JSONObject((Map<String, ?>) subj).toJSONString();
} else if (subj instanceof Double || subj instanceof Float) {
str = decimalFormatter.get().format(subj);
} else {
str = subj.toString();
}
return str;
}
@Override
public void threadStarted() {
// nothing to do on thread start
}
@Override
public void threadFinished() {
decimalFormatter.remove();
}
}

View File

@ -0,0 +1,173 @@
/*
* 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 org.apache.jmeter.assertions;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Predicate;
import io.metersphere.utils.DocumentUtils;
import net.minidev.json.JSONArray;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.ThreadListener;
import org.json.JSONObject;
import org.json.XML;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.text.DecimalFormat;
/**
* Checks if the result is a well-formed XML content using {@link XMLReader}
*
*/
public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
private static final Logger log = LoggerFactory.getLogger(XMLAssertion.class);
private static ThreadLocal<DecimalFormat> decimalFormatter = ThreadLocal.withInitial(XMLAssertion::createDecimalFormat);
private static final long serialVersionUID = 242L;
public String getXmlPath() {
return this.getPropertyAsString("XML_PATH");
}
public String getExpectedValue() {
return this.getPropertyAsString("EXPECTED_VALUE");
}
public String getCondition() {
return getPropertyAsString("ElementCondition");
}
private static DecimalFormat createDecimalFormat() {
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
decimalFormatter.setMaximumFractionDigits(340);
decimalFormatter.setMinimumFractionDigits(1);
return decimalFormatter;
}
// one builder for all requests in a thread
private static final ThreadLocal<XMLReader> XML_READER = new ThreadLocal<XMLReader>() {
@Override
protected XMLReader initialValue() {
try {
XMLReader reader = SAXParserFactory.newInstance()
.newSAXParser()
.getXMLReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
return reader;
} catch (SAXException | ParserConfigurationException e) {
log.error("Error initializing XMLReader in XMLAssertion", e);
return null;
}
}
};
/**
* Returns the result of the Assertion.
* Here it checks whether the Sample data is XML.
* If so an AssertionResult containing a FailureMessage will be returned.
* Otherwise the returned AssertionResult will reflect the success of the Sample.
*/
@Override
public AssertionResult getResult(SampleResult response) {
// no error as default
AssertionResult result = new AssertionResult(getName());
String resultData = response.getResponseDataAsString();
if (resultData.length() == 0) {
return result.setResultForNull();
}
result.setFailure(false);
XMLReader builder = XML_READER.get();
if (builder != null) {
try {
builder.setErrorHandler(new LogErrorHandler());
builder.parse(new InputSource(new StringReader(resultData)));
try {
JSONObject xmlJSONObj = XML.toJSONObject(resultData);
String jsonPrettyPrintString = xmlJSONObj.toString(4);
doAssert(jsonPrettyPrintString);
} catch (Exception e) {
result.setError(true);
result.setFailure(true);
result.setFailureMessage(e.getMessage());
}
} catch (SAXException | IOException e) {
result.setError(true);
result.setFailure(true);
result.setFailureMessage(e.getMessage());
}
} else {
result.setError(true);
result.setFailureMessage("Cannot initialize XMLReader in element:" + getName() + ", check jmeter.log file");
}
return result;
}
private void doAssert(String jsonString) {
Object value = JsonPath.read(jsonString, this.getXmlPath(), new Predicate[0]);
if (value instanceof JSONArray) {
if (this.arrayMatched((JSONArray) value)) {
return;
}
}
if (!this.isEquals(value)) {
String msg = DocumentUtils.documentMsg(this.getName(), value, this.getCondition());
throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter)));
}
}
private boolean isEquals(Object subj) {
String str = DocumentUtils.objectToString(subj, decimalFormatter);
return DocumentUtils.documentChecked(str, this.getCondition(), decimalFormatter);
}
private boolean arrayMatched(JSONArray value) {
if (value.isEmpty() && "[]".equals(getExpectedValue())) {
return true;
}
for (Object subj : value.toArray()) {
if (subj == null || isEquals(subj)) {
return true;
}
}
return isEquals(value);
}
@Override
public void threadStarted() {
// nothing to do on thread start
}
@Override
public void threadFinished() {
XML_READER.remove();
}
}

View File

@ -0,0 +1,353 @@
/*
* 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 org.apache.jmeter.config;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.engine.event.LoopIterationListener;
import org.apache.jmeter.engine.util.NoConfigMerge;
import org.apache.jmeter.gui.GUIMenuSortOrder;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.save.CSVSaveService;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.util.JMeterStopThreadException;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.io.IOException;
import java.util.ResourceBundle;
/**
* Read lines from a file and split int variables.
*
* The iterationStart() method is used to set up each set of values.
*
* By default, the same file is shared between all threads
* (and other thread groups, if they use the same file name).
*
* The shareMode can be set to:
* <ul>
* <li>All threads - default, as described above</li>
* <li>Current thread group</li>
* <li>Current thread</li>
* <li>Identifier - all threads sharing the same identifier</li>
* </ul>
*
* The class uses the FileServer alias mechanism to provide the different share modes.
* For all threads, the file alias is set to the file name.
* Otherwise, a suffix is appended to the filename to make it unique within the required context.
* For current thread group, the thread group identityHashcode is used;
* for individual threads, the thread hashcode is used as the suffix.
* Or the user can provide their own suffix, in which case the file is shared between all
* threads with the same suffix.
*
*/
@GUIMenuSortOrder(1)
@TestElementMetadata(labelResource = "displayName")
public class CSVDataSet extends ConfigTestElement
implements TestBean, LoopIterationListener, NoConfigMerge {
private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class);
private static final long serialVersionUID = 233L;
private static final String EOFVALUE = // value to return at EOF
JMeterUtils.getPropDefault("csvdataset.eofstring", "<EOF>"); //$NON-NLS-1$ //$NON-NLS-2$
private transient String filename;
private transient String fileEncoding;
private transient String variableNames;
private transient String delimiter;
private transient boolean quoted;
private transient boolean recycle = true;
private transient boolean stopThread;
private transient String[] vars;
private transient String alias;
private transient String shareMode;
private boolean firstLineIsNames = false;
private boolean ignoreFirstLine = false;
private final static String THREAD_SPLIT = " ";
private Object readResolve() {
recycle = true;
return this;
}
/**
* Override the setProperty method in order to convert
* the original String shareMode property.
* This used the locale-dependent display value, so caused
* problems when the language was changed.
* If the "shareMode" value matches a resource value then it is converted
* into the resource key.
* To reduce the need to look up resources, we only attempt to
* convert values with spaces in them, as these are almost certainly
* not variables (and they are definitely not resource keys).
*/
@Override
public void setProperty(JMeterProperty property) {
if (!(property instanceof StringProperty)) {
super.setProperty(property);
return;
}
final String propName = property.getName();
if (!"shareMode".equals(propName)) {
super.setProperty(property);
return;
}
final String propValue = property.getStringValue();
if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation
try {
final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass());
final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
for (String resKey : CSVDataSetBeanInfo.getShareTags()) {
if (propValue.equals(rb.getString(resKey))) {
if (log.isDebugEnabled()) {
log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale());
}
((StringProperty) property).setValue(resKey); // reset the value
super.setProperty(property);
return;
}
}
// This could perhaps be a variable name
log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale());
} catch (IntrospectionException e) {
LoggerUtil.error("Could not find BeanInfo; cannot translate shareMode entries", e);
}
}
super.setProperty(property);
}
@Override
public void iterationStart(LoopIterationEvent iterEvent) {
FileServer server = FileServer.getFileServer();
final JMeterContext context = getThreadContext();
String delim = getDelimiter();
if ("\\t".equals(delim)) { // $NON-NLS-1$
delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$
} else if (delim.isEmpty()) {
log.debug("Empty delimiter, will use ','");
delim = ",";
}
if (vars == null) {
initVars(server, context, delim);
}
// TODO: fetch this once as per vars above?
JMeterVariables threadVars = context.getVariables();
String[] lineValues = {};
try {
if (getQuotedData()) {
lineValues = server.getParsedLine(alias, recycle,
firstLineIsNames || ignoreFirstLine, delim.charAt(0));
} else {
String line = server.readLine(alias, recycle,
firstLineIsNames || ignoreFirstLine);
lineValues = JOrphanUtils.split(line, delim, false);
}
for (int a = 0; a < vars.length && a < lineValues.length; a++) {
threadVars.put(vars[a], lineValues[a]);
}
} catch (IOException e) { // treat the same as EOF
LoggerUtil.error(e.toString());
}
if (lineValues.length == 0) {// i.e. EOF
if (getStopThread()) {
throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:"
+ getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle());
}
for (String var : vars) {
threadVars.put(var, EOFVALUE);
}
}
}
private void initVars(FileServer server, final JMeterContext context, String delim) {
String fileName = getFilename().trim();
LoggerUtil.info("初始化csv内容" + fileName);
setAlias(context, fileName);
final String names = getVariableNames();
if (StringUtils.isEmpty(names)) {
String header = server.reserveFile(fileName, getFileEncoding(), alias, true);
try {
vars = CSVSaveService.csvSplitString(header, delim.charAt(0));
firstLineIsNames = true;
} catch (IOException e) {
throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e);
}
} else {
server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine);
vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$
}
trimVarNames(vars);
}
private void setAlias(final JMeterContext context, String alias) {
String mode = getShareMode();
int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode);
String threadName = StringUtils.substringBeforeLast(context.getThread().getThreadName(), THREAD_SPLIT);
switch (modeInt) {
case CSVDataSetBeanInfo.SHARE_ALL:
this.alias = alias;
break;
case CSVDataSetBeanInfo.SHARE_GROUP:
this.alias = alias + "@" + "GROUP_" + threadName + "@" + System.identityHashCode(context.getThreadGroup());
break;
case CSVDataSetBeanInfo.SHARE_THREAD:
this.alias = alias + "@" + threadName + "@" + System.identityHashCode(context.getThread());
break;
default:
this.alias = alias + "@" + mode; // user-specified key
break;
}
}
/**
* trim content of array varNames
* @param varsNames
*/
private void trimVarNames(String[] varsNames) {
for (int i = 0; i < varsNames.length; i++) {
varsNames[i] = varsNames[i].trim();
}
}
/**
* @return Returns the filename.
*/
public String getFilename() {
return filename;
}
/**
* @param filename The filename to set.
*/
public void setFilename(String filename) {
this.filename = filename;
}
/**
* @return Returns the file encoding.
*/
public String getFileEncoding() {
return fileEncoding;
}
/**
* @param fileEncoding
* The fileEncoding to set.
*/
public void setFileEncoding(String fileEncoding) {
this.fileEncoding = fileEncoding;
}
/**
* @return Returns the variableNames.
*/
public String getVariableNames() {
return variableNames;
}
/**
* @param variableNames
* The variableNames to set.
*/
public void setVariableNames(String variableNames) {
this.variableNames = variableNames;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public boolean getQuotedData() {
return quoted;
}
public void setQuotedData(boolean quoted) {
this.quoted = quoted;
}
public boolean getRecycle() {
return recycle;
}
public void setRecycle(boolean recycle) {
this.recycle = recycle;
}
public boolean getStopThread() {
return stopThread;
}
public void setStopThread(boolean value) {
this.stopThread = value;
}
public String getShareMode() {
return shareMode;
}
public void setShareMode(String value) {
this.shareMode = value;
}
/**
* @return the ignoreFirstLine
*/
public boolean isIgnoreFirstLine() {
return ignoreFirstLine;
}
/**
* @param ignoreFirstLine the ignoreFirstLine to set
*/
public void setIgnoreFirstLine(boolean ignoreFirstLine) {
this.ignoreFirstLine = ignoreFirstLine;
}
}

View File

@ -0,0 +1,174 @@
/*
* 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 org.apache.jmeter.config;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.SSLManager;
import org.apache.jorphan.util.JMeterStopTestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Configure Keystore
*/
@TestElementMetadata(labelResource = "displayName")
public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(KeystoreConfig.class);
private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$
private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$
private String startIndex;
private String endIndex;
private String preload;
private String clientCertAliasVarName;
public KeystoreConfig() {
super();
}
@Override
public void testEnded() {
testEnded(null);
}
@Override
public void testEnded(String host) {
log.info("Destroying Keystore");
SSLManager.getInstance().destroyKeystore();
}
@Override
public void testStarted() {
testStarted(null);
}
@Override
public void testStarted(String host) {
String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context");
if (StringUtils.isEmpty(reuseSSLContext) || "true".equals(reuseSSLContext)) {
log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used");
}
int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0);
int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, -1);
if (!StringUtils.isEmpty(this.startIndex)) {
try {
startIndexAsInt = Integer.parseInt(this.startIndex);
} catch (NumberFormatException e) {
log.warn("Failed parsing startIndex: {}, will default to: {}, error message: {}", this.startIndex,
startIndexAsInt, e, e);
}
}
if (!StringUtils.isEmpty(this.endIndex)) {
try {
endIndexAsInt = Integer.parseInt(this.endIndex);
} catch (NumberFormatException e) {
log.warn("Failed parsing endIndex: {}, will default to: {}, error message: {}", this.endIndex,
endIndexAsInt, e, e);
}
}
if (endIndexAsInt != -1 && startIndexAsInt > endIndexAsInt) {
throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index");
}
log.info(
"Configuring Keystore with (preload: '{}', startIndex: {}, endIndex: {}, clientCertAliasVarName: '{}')",
preload, startIndexAsInt, endIndexAsInt, clientCertAliasVarName);
// 加载认证文件
String path = this.getPropertyAsString("MS-KEYSTORE-FILE-PATH");
String password = this.getPropertyAsString("MS-KEYSTORE-FILE-PASSWORD");
InputStream in = null;
try {
in = new FileInputStream(new File(path));
} catch (IOException e) {
log.error(e.getMessage());
}
SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload),
startIndexAsInt,
endIndexAsInt,
clientCertAliasVarName, in, password);
}
/**
* @return the endIndex
*/
public String getEndIndex() {
return endIndex;
}
/**
* @param endIndex the endIndex to set
*/
public void setEndIndex(String endIndex) {
this.endIndex = endIndex;
}
/**
* @return the startIndex
*/
public String getStartIndex() {
return startIndex;
}
/**
* @param startIndex the startIndex to set
*/
public void setStartIndex(String startIndex) {
this.startIndex = startIndex;
}
/**
* @return the preload
*/
public String getPreload() {
return preload;
}
/**
* @param preload the preload to set
*/
public void setPreload(String preload) {
this.preload = preload;
}
/**
* @return the clientCertAliasVarName
*/
public String getClientCertAliasVarName() {
return clientCertAliasVarName;
}
/**
* @param clientCertAliasVarName the clientCertAliasVarName to set
*/
public void setClientCertAliasVarName(String clientCertAliasVarName) {
this.clientCertAliasVarName = clientCertAliasVarName;
}
}

View File

@ -0,0 +1,151 @@
/*
* 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 org.apache.jmeter.engine;
import io.metersphere.utils.CustomizeFunctionUtil;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.engine.util.ValueReplacer;
import org.apache.jmeter.functions.InvalidVariableException;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.visualizers.backend.Backend;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.HashTreeTraverser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Class to replace function and variable references in the test tree.
*
*/
public class PreCompiler implements HashTreeTraverser {
private static final Logger log = LoggerFactory.getLogger(PreCompiler.class);
private final ValueReplacer replacer;
// Used by both StandardJMeterEngine and ClientJMeterEngine.
// In the latter case, only ResultCollectors are updated,
// as only these are relevant to the client, and updating
// other elements causes all sorts of problems.
private final boolean isClientSide; // skip certain processing for remote tests
private JMeterVariables clientSideVariables;
public PreCompiler() {
replacer = new ValueReplacer();
isClientSide = false;
}
public PreCompiler(boolean remote) {
replacer = new ValueReplacer();
isClientSide = remote;
}
/** {@inheritDoc} */
@Override
public void addNode(Object node, HashTree subTree) {
if(isClientSide) {
if(node instanceof ResultCollector || node instanceof Backend) {
try {
replacer.replaceValues((TestElement) node);
} catch (InvalidVariableException e) {
log.error("invalid variables in node {}", ((TestElement)node).getName(), e);
}
}
if (node instanceof TestPlan) {
this.clientSideVariables = createVars((TestPlan)node);
}
if (node instanceof Arguments) {
// Don't store User Defined Variables in the context for client side
Map<String, String> args = createArgumentsMap((Arguments) node);
clientSideVariables.putAll(args);
}
} else {
if(node instanceof TestElement) {
try {
replacer.replaceValues((TestElement) node);
} catch (InvalidVariableException e) {
log.error("invalid variables in node {}", ((TestElement)node).getName(), e);
}
}
if (node instanceof TestPlan) {
JMeterVariables vars = createVars((TestPlan)node);
JMeterContextService.getContext().setVariables(vars);
// 加载自定义函数
CustomizeFunctionUtil.initCustomizeClass((TestPlan) node);
}
if (node instanceof Arguments) {
Map<String, String> args = createArgumentsMap((Arguments) node);
JMeterContextService.getContext().getVariables().putAll(args);
}
}
}
/**
* Create Map of Arguments
* @param arguments {@link Arguments}
* @return {@link Map}
*/
private Map<String, String> createArgumentsMap(Arguments arguments) {
arguments.setRunningVersion(true);
Map<String, String> args = arguments.getArgumentsAsMap();
replacer.addVariables(args);
return args;
}
/**
* Create variables for testPlan
* @param testPlan {@link JMeterVariables}
* @return {@link JMeterVariables}
*/
private JMeterVariables createVars(TestPlan testPlan) {
testPlan.prepareForPreCompile(); //A hack to make user-defined variables in the testplan element more dynamic
Map<String, String> args = testPlan.getUserDefinedVariables();
replacer.setUserDefinedVariables(args);
JMeterVariables vars = new JMeterVariables();
vars.putAll(args);
return vars;
}
/** {@inheritDoc} */
@Override
public void subtractNode() {
}
/** {@inheritDoc} */
@Override
public void processPath() {
}
/**
* @return the clientSideVariables
*/
public JMeterVariables getClientSideVariables() {
return clientSideVariables;
}
}

View File

@ -0,0 +1,250 @@
/*
* 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 org.apache.jmeter.engine.util;
import io.metersphere.utils.CustomizeFunctionUtil;
import org.apache.commons.collections4.MapUtils;
import org.apache.jmeter.functions.Function;
import org.apache.jmeter.functions.InvalidVariableException;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.reflect.ClassFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* CompoundFunction.
*/
public class CompoundVariable implements Function {
private static final Logger log = LoggerFactory.getLogger(CompoundVariable.class);
private String rawParameters;
private static final FunctionParser functionParser = new FunctionParser();
// Created during class init; not modified thereafter
private static final Map<String, Class<? extends Function>> functions = new HashMap<>();
private boolean hasFunction;
private boolean isDynamic;
private String permanentResults;
// Type is ArrayList, so we can use ArrayList#clone
private ArrayList<Object> compiledComponents = new ArrayList<>();
static {
try {
final String contain = // Classnames must contain this string [.functions.]
JMeterUtils.getProperty("classfinder.functions.contain"); // $NON-NLS-1$
final String notContain = // Classnames must not contain this string [.gui.]
JMeterUtils.getProperty("classfinder.functions.notContain"); // $NON-NLS-1$
if (contain != null) {
log.info("Note: Function class names must contain the string: '{}'", contain);
}
if (notContain != null) {
log.info("Note: Function class names must not contain the string: '{}'", notContain);
}
List<String> classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(),
new Class[]{Function.class}, true, contain, notContain);
for (String clazzName : classes) {
Function tempFunc = Class.forName(clazzName)
.asSubclass(Function.class)
.getDeclaredConstructor().newInstance();
String referenceKey = tempFunc.getReferenceKey();
if (referenceKey.length() > 0) { // ignore self
functions.put(referenceKey, tempFunc.getClass());
}
}
if (functions.isEmpty()) {
log.warn("Did not find any functions");
} else {
log.debug("Function count: {}", functions.size());
}
} catch (Exception err) {
log.error("Exception occurred in static initialization of CompoundVariable.", err);
}
}
public CompoundVariable() {
hasFunction = false;
}
public CompoundVariable(String parameters) {
this();
try {
setParameters(parameters);
} catch (InvalidVariableException e) {
// TODO should level be more than debug ?
log.debug("Invalid variable: {}", parameters, e);
}
}
public String execute() {
if (isDynamic || permanentResults == null) {
JMeterContext context = JMeterContextService.getContext();
SampleResult previousResult = context.getPreviousResult();
Sampler currentSampler = context.getCurrentSampler();
return execute(previousResult, currentSampler);
}
return permanentResults; // $NON-NLS-1$
}
/**
* Allows the retrieval of the original String prior to it being compiled.
*
* @return String
*/
public String getRawParameters() {
return rawParameters;
}
/**
* {@inheritDoc}
*/
@Override
public String execute(SampleResult previousResult, Sampler currentSampler) {
if (compiledComponents == null || compiledComponents.isEmpty()) {
return ""; // $NON-NLS-1$
}
StringBuilder results = new StringBuilder();
for (Object item : compiledComponents) {
if (item instanceof Function) {
try {
results.append(((Function) item).execute(previousResult, currentSampler));
} catch (InvalidVariableException e) {
// TODO should level be more than debug ?
log.debug("Invalid variable: {}", item, e);
}
} else if (item instanceof SimpleVariable) {
results.append(((SimpleVariable) item).toString());
} else {
results.append(item);
}
}
if (!isDynamic) {
permanentResults = results.toString();
}
return results.toString();
}
@SuppressWarnings("unchecked") // clone will produce correct type
public CompoundVariable getFunction() {
CompoundVariable func = new CompoundVariable();
func.compiledComponents = (ArrayList<Object>) compiledComponents.clone();
func.rawParameters = rawParameters;
func.hasFunction = hasFunction;
func.isDynamic = isDynamic;
return func;
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getArgumentDesc() {
return new ArrayList<>();
}
public void clear() {
// TODO should this also clear isDynamic, rawParameters, permanentResults?
hasFunction = false;
compiledComponents.clear();
}
public void setParameters(String parameters) throws InvalidVariableException {
this.rawParameters = parameters;
if (parameters == null || parameters.length() == 0) {
return;
}
compiledComponents = functionParser.compileString(parameters);
if (compiledComponents.size() > 1 || !(compiledComponents.get(0) instanceof String)) {
hasFunction = true;
}
permanentResults = null; // To be calculated and cached on first execution
isDynamic = false;
for (Object item : compiledComponents) {
if (item instanceof Function || item instanceof SimpleVariable) {
isDynamic = true;
break;
}
}
}
static Object getNamedFunction(String functionName) throws InvalidVariableException {
if (functions.containsKey(functionName)) {
try {
return functions.get(functionName).getDeclaredConstructor().newInstance();
} catch (Exception e) {
log.error("Exception occurred while instantiating a function: {}", functionName, e); // $NON-NLS-1$
throw new InvalidVariableException(e);
}
}
Map<String, Class<? extends Function>> customizeFunctions = CustomizeFunctionUtil.getFunctions();
if (MapUtils.isNotEmpty(customizeFunctions) && customizeFunctions.containsKey(functionName)) {
try {
log.info("ms custom function handling", functionName);
return CustomizeFunctionUtil.getFunctions().get(functionName).getDeclaredConstructor().newInstance();
} catch (Exception e) {
log.error("Exception occurred while instantiating a function: {}", functionName, e); // $NON-NLS-1$
throw new InvalidVariableException(e);
}
}
return new SimpleVariable(functionName);
}
// For use by FunctionHelper
public static Class<? extends Function> getFunctionClass(String className) {
return functions.get(className);
}
// For use by FunctionHelper
public static String[] getFunctionNames() {
return functions.keySet().toArray(new String[functions.size()]);
}
public boolean hasFunction() {
return hasFunction;
}
// Dummy methods needed by Function interface
/**
* {@inheritDoc}
*/
@Override
public String getReferenceKey() {
return ""; // $NON-NLS-1$
}
/**
* {@inheritDoc}
*/
@Override
public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {
}
}

View File

@ -0,0 +1,224 @@
/*
* 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 org.apache.jmeter.protocol.http.sampler;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.samplers.Interruptible;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
/**
* Proxy class that dispatches to the appropriate HTTP sampler.
* <p>
* This class is stored in the test plan, and holds all the configuration settings.
* The actual implementation is created at run-time, and is passed a reference to this class
* so it can get access to all the settings stored by HTTPSamplerProxy.
*/
public final class HTTPSamplerProxy extends HTTPSamplerBase implements Interruptible {
private static final long serialVersionUID = 1L;
private static final String HTTP_PREFIX = HTTPConstants.PROTOCOL_HTTP+"://"; // $NON-NLS-1$
private static final String HTTPS_PREFIX = HTTPConstants.PROTOCOL_HTTPS+"://"; // $NON-NLS-1$
private static final String QRY_PFX = "?"; // $NON-NLS-1$
private static final String QRY_SEP = "&"; // $NON-NLS-1$
private transient HTTPAbstractImpl impl;
public HTTPSamplerProxy() {
super();
}
/**
* Convenience method used to initialise the implementation.
*
* @param impl the implementation to use.
*/
public HTTPSamplerProxy(String impl) {
super();
setImplementation(impl);
}
protected String toExternalForm(URL u) {
int len = u.getProtocol().length() + 1;
if (u.getAuthority() != null && u.getAuthority().length() > 0)
len += 2 + u.getAuthority().length();
if (u.getPath() != null) {
len += u.getPath().length();
}
if (u.getQuery() != null) {
len += 1 + u.getQuery().length();
}
if (u.getRef() != null)
len += 1 + u.getRef().length();
StringBuffer result = new StringBuffer(len);
result.append(u.getProtocol());
result.append(":");
if (u.getAuthority() != null && u.getAuthority().length() > 0) {
result.append("//");
result.append(u.getAuthority());
}
if (StringUtils.isNotEmpty(u.getPath())) {
int index = 0;
for (int i = 0; i < u.getPath().length(); i++) {
char ch = u.getPath().charAt(i);
if (String.valueOf(ch).equals("/")) {
index++;
} else {
break;
}
}
result.append("/" + u.getPath().substring(index));
}
if (u.getQuery() != null) {
result.append('?');
result.append(u.getQuery());
}
if (u.getRef() != null) {
result.append("#");
result.append(u.getRef());
}
return result.toString();
}
/**
* {@inheritDoc}
*/
@Override
protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth) {
// When Retrieve Embedded resources + Concurrent Pool is used
// as the instance of Proxy is cloned, we end up with impl being null
// testIterationStart will not be executed but it's not a problem for 51380 as it's download of resources
// so SSL context is to be reused
if (impl == null) { // Not called from multiple threads, so this is OK
try {
impl = HTTPSamplerFactory.getImplementation(getImplementation(), this);
} catch (Exception ex) {
return errorResult(ex, new HTTPSampleResult());
}
}
try {
String url = toExternalForm(u);
if (StringUtils.isNotEmpty(url) && url.startsWith("http:/http")) {
url = url.substring(6);
}
u = new URL(url);
} catch (Exception ex) {
}
return impl.sample(u, method, areFollowingRedirect, depth);
}
// N.B. It's not possible to forward threadStarted() to the implementation class.
// This is because Config items are not processed until later, and HTTPDefaults may define the implementation
@Override
public void threadFinished() {
if (impl != null) {
impl.threadFinished(); // Forward to sampler
}
}
@Override
public boolean interrupt() {
if (impl != null) {
return impl.interrupt(); // Forward to sampler
}
return false;
}
/* (non-Javadoc)
* @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
*/
@Override
public void testIterationStart(LoopIterationEvent event) {
if (impl != null) {
impl.notifyFirstSampleAfterLoopRestart();
}
}
/**
* 重写getUrl方法 防止获取的domain是完整的地址
* 重新切割domain重新赋值给domain和protocol
* @return
* @throws MalformedURLException
*/
@Override
public URL getUrl() throws MalformedURLException {
String path = this.getPath();
if (path.startsWith("//")) {
path.replaceFirst("/" ,"");
}
// Hack to allow entire URL to be provided in host field
if (path.startsWith(HTTP_PREFIX)
|| path.startsWith(HTTPS_PREFIX)) {
return new URL(path);
}
String domain = getDomain();
String protocol = null;
if (domain.startsWith(HTTP_PREFIX) || domain.startsWith(HTTPS_PREFIX)) {
try {
URL url = new URL(domain);
domain = url.getHost();
protocol = url.getProtocol();
} catch (Exception e) {
e.printStackTrace();
}
}
protocol = StringUtils.isNotEmpty(protocol) ? protocol : getProtocol();
String method = getMethod();
StringBuilder pathAndQuery = new StringBuilder(100);
if (PROTOCOL_FILE.equalsIgnoreCase(protocol)) {
domain = null; // allow use of relative file URLs
} else {
// HTTP URLs must be absolute, allow file to be relative
if (!path.startsWith("/")) { // $NON-NLS-1$
pathAndQuery.append('/'); // $NON-NLS-1$
}
}
pathAndQuery.append(path);
// Add the query string if it is a HTTP GET or DELETE request
if (HTTPConstants.GET.equals(method)
|| HTTPConstants.DELETE.equals(method)
|| HTTPConstants.OPTIONS.equals(method)) {
// Get the query string encoded in specified encoding
// If no encoding is specified by user, we will get it
// encoded in UTF-8, which is what the HTTP spec says
String queryString = getQueryString(getContentEncoding());
if (queryString.length() > 0) {
if (path.contains(QRY_PFX)) {// Already contains a prefix
pathAndQuery.append(QRY_SEP);
} else {
pathAndQuery.append(QRY_PFX);
}
pathAndQuery.append(queryString);
}
}
// If default port for protocol is used, we do not include port in URL
if (isProtocolDefaultPort()) {
return new URL(protocol, domain, pathAndQuery.toString());
}
return new URL(protocol, domain, getPort(), pathAndQuery.toString());
}
}

View File

@ -0,0 +1,108 @@
/*
* 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 org.apache.jmeter.protocol.tcp.sampler;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
/**
* Sample TCPClient implementation.
* Reads data until the defined EOL byte is reached.
* If there is no EOL byte defined, then reads until
* the end of the stream is reached.
* The EOL byte is defined by the property "tcp.eolByte".
*/
public class MsTCPClientImpl extends TCPClientImpl {
private static final Logger log = LoggerFactory.getLogger(MsTCPClientImpl.class);
private static final int EOL_INT = JMeterUtils.getPropDefault("tcp.eolByte", 1000); // $NON-NLS-1$
public void write(OutputStream os, String s , String charset) throws IOException{
if(log.isDebugEnabled()) {
log.debug("WriteS: {}", showEOL(s));
}
os.write(s.getBytes(charset));
os.flush();
}
public void write(OutputStream os, InputStream is , String charset) throws IOException{
byte[] buff = new byte[512];
while(is.read(buff) > 0){
if(log.isDebugEnabled()) {
log.debug("WriteIS: {}", showEOL(new String(buff, charset)));
}
os.write(buff);
os.flush();
}
}
private String showEOL(final String input) {
StringBuilder sb = new StringBuilder(input.length()*2);
for(int i=0; i < input.length(); i++) {
char ch = input.charAt(i);
if (ch < ' ') {
sb.append('[');
sb.append((int)ch);
sb.append(']');
} else {
sb.append(ch);
}
}
return sb.toString();
}
public String read(InputStream is, SampleResult sampleResult, String charset) throws ReadException {
ByteArrayOutputStream w = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[4096];
int x;
boolean first = true;
while ((x = is.read(buffer)) > -1) {
if (first) {
sampleResult.latencyEnd();
first = false;
}
w.write(buffer, 0, x);
if (useEolByte && (buffer[x - 1] == eolByte)) {
break;
}
}
// do we need to close byte array (or flush it?)
if(log.isDebugEnabled()) {
log.debug("Read: {}\n{}", w.size(), w.toString(charset));
}
return w.toString(charset);
} catch (UnsupportedEncodingException e) {
throw new ReadException("Error decoding bytes from server with " + charset + ", bytes read: " + w.size(),
e, "<Read bytes with bad encoding>");
} catch (IOException e) {
String decodedBytes;
try {
decodedBytes = w.toString(charset);
} catch (UnsupportedEncodingException uee) {
// we should never get here, as it would have crashed earlier
decodedBytes = "<Read bytes with bad encoding>";
}
throw new ReadException("Error reading from server, bytes read: " + w.size(), e, decodedBytes);
}
}
}

View File

@ -0,0 +1,566 @@
/*
* 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 org.apache.jmeter.protocol.tcp.sampler;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.Interruptible;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.ThreadListener;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* A sampler which understands Tcp requests.
*
*/
public class TCPSampler extends AbstractSampler implements ThreadListener, Interruptible {
private static final long serialVersionUID = 280L;
private static final Logger log = LoggerFactory.getLogger(TCPSampler.class);
private static final Set<String> APPLIABLE_CONFIG_CLASSES = new HashSet<>(
Arrays.asList(
"org.apache.jmeter.config.gui.LoginConfigGui",
"org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui",
"org.apache.jmeter.config.gui.SimpleConfigGui"
));
public static final String SERVER = "TCPSampler.server"; //$NON-NLS-1$
public static final String PORT = "TCPSampler.port"; //$NON-NLS-1$
public static final String FILENAME = "TCPSampler.filename"; //$NON-NLS-1$
public static final String CLASSNAME = "TCPSampler.classname";//$NON-NLS-1$
public static final String NODELAY = "TCPSampler.nodelay"; //$NON-NLS-1$
public static final String TIMEOUT = "TCPSampler.timeout"; //$NON-NLS-1$
public static final String TIMEOUT_CONNECT = "TCPSampler.ctimeout"; //$NON-NLS-1$
public static final String REQUEST = "TCPSampler.request"; //$NON-NLS-1$
public static final String RE_USE_CONNECTION = "TCPSampler.reUseConnection"; //$NON-NLS-1$
public static final boolean RE_USE_CONNECTION_DEFAULT = true;
public static final String CLOSE_CONNECTION = "TCPSampler.closeConnection"; //$NON-NLS-1$
public static final boolean CLOSE_CONNECTION_DEFAULT = false;
public static final String SO_LINGER = "TCPSampler.soLinger"; //$NON-NLS-1$
public static final String EOL_BYTE = "TCPSampler.EolByte"; //$NON-NLS-1$
private static final String TCPKEY = "TCP"; //$NON-NLS-1$ key for HashMap
private static final String ERRKEY = "ERR"; //$NON-NLS-1$ key for HashMap
// the response is scanned for these strings
private static final String STATUS_PREFIX = JMeterUtils.getPropDefault("tcp.status.prefix", ""); //$NON-NLS-1$
private static final String STATUS_SUFFIX = JMeterUtils.getPropDefault("tcp.status.suffix", ""); //$NON-NLS-1$
private static final String STATUS_PROPERTIES = JMeterUtils.getPropDefault("tcp.status.properties", ""); //$NON-NLS-1$
private static final Properties STATUS_PROPS = new Properties();
private static final String PROTO_PREFIX = "org.apache.jmeter.protocol.tcp.sampler."; //$NON-NLS-1$
private static final boolean HAVE_STATUS_PROPS;
//设置tcp编码
public static String charset = "UTF-8";
public void setCharset(String charset) {
this.setProperty("CHARSET", charset);
}
public String getCharset() {
return getPropertyAsString("CHARSET");
}
static {
boolean hsp = false;
log.debug("Status prefix={}, suffix={}, properties={}",
STATUS_PREFIX, STATUS_SUFFIX, STATUS_PROPERTIES); //$NON-NLS-1$
if (STATUS_PROPERTIES.length() > 0) {
File f = new File(STATUS_PROPERTIES);
try (FileInputStream fis = new FileInputStream(f)){
STATUS_PROPS.load(fis);
log.debug("Successfully loaded properties"); //$NON-NLS-1$
hsp = true;
} catch (FileNotFoundException e) {
log.debug("Property file {} not found", STATUS_PROPERTIES); //$NON-NLS-1$
} catch (IOException e) {
log.debug("Error reading property file {} error {}", STATUS_PROPERTIES, e.toString()); //$NON-NLS-1$
}
}
HAVE_STATUS_PROPS = hsp;
}
/** the cache of TCP Connections */
// KEY = TCPKEY or ERRKEY, Entry= Socket or String
private static final ThreadLocal<Map<String, Object>> tp =
ThreadLocal.withInitial(HashMap::new);
private transient TCPClient protocolHandler;
private transient boolean firstSample; // Are we processing the first sample?
private transient volatile Socket currentSocket; // used for handling interrupt
public TCPSampler() {
log.debug("Created {}", this); //$NON-NLS-1$
}
private String getError() {
Map<String, Object> cp = tp.get();
return (String) cp.get(ERRKEY);
}
private Socket getSocket(String socketKey) {
Map<String, Object> cp = tp.get();
Socket con = null;
if (isReUseConnection()) {
con = (Socket) cp.get(socketKey);
if (con != null) {
log.debug("{} Reusing connection {}", this, con); //$NON-NLS-1$
}
}
if (con == null) {
// Not in cache, so create new one and cache it
try {
closeSocket(socketKey); // Bug 44910 - close previous socket (if any)
SocketAddress sockaddr = new InetSocketAddress(getServer(), getPort());
con = new Socket(); // NOSONAR socket is either cache in ThreadLocal for reuse and closed at end of thread or closed here
if (getPropertyAsString(SO_LINGER,"").length() > 0){
con.setSoLinger(true, getSoLinger());
}
con.connect(sockaddr, getConnectTimeout());
if(log.isDebugEnabled()) {
log.debug("Created new connection {}", con); //$NON-NLS-1$
}
cp.put(socketKey, con);
} catch (UnknownHostException e) {
log.warn("Unknown host for {}", getLabel(), e);//$NON-NLS-1$
cp.put(ERRKEY, e.toString());
return null;
} catch (IOException e) {
log.warn("Could not create socket for {}", getLabel(), e); //$NON-NLS-1$
cp.put(ERRKEY, e.toString());
return null;
}
}
// (re-)Define connection params - Bug 50977
try {
con.setSoTimeout(getTimeout());
con.setTcpNoDelay(getNoDelay());
if(log.isDebugEnabled()) {
log.debug("{} Timeout={}, NoDelay={}", this, getTimeout(), getNoDelay()); //$NON-NLS-1$
}
} catch (SocketException se) {
log.warn("Could not set timeout or nodelay for {}", getLabel(), se); //$NON-NLS-1$
cp.put(ERRKEY, se.toString());
}
return con;
}
/**
* @return String socket key in cache Map
*/
private String getSocketKey() {
return TCPKEY+"#"+getServer()+"#"+getPort()+"#"+getUsername()+"#"+getPassword();
}
public String getUsername() {
return getPropertyAsString(ConfigTestElement.USERNAME);
}
public String getPassword() {
return getPropertyAsString(ConfigTestElement.PASSWORD);
}
public void setServer(String newServer) {
this.setProperty(SERVER, newServer);
}
public String getServer() {
return getPropertyAsString(SERVER);
}
public boolean isReUseConnection() {
return getPropertyAsBoolean(RE_USE_CONNECTION, RE_USE_CONNECTION_DEFAULT);
}
public void setCloseConnection(String close) {
this.setProperty(CLOSE_CONNECTION, close, "");
}
public boolean isCloseConnection() {
return getPropertyAsBoolean(CLOSE_CONNECTION, CLOSE_CONNECTION_DEFAULT);
}
public void setSoLinger(String soLinger) {
this.setProperty(SO_LINGER, soLinger, "");
}
public int getSoLinger() {
return getPropertyAsInt(SO_LINGER);
}
public void setEolByte(String eol) {
this.setProperty(EOL_BYTE, eol, "");
}
public int getEolByte() {
return getPropertyAsInt(EOL_BYTE);
}
public void setPort(String newFilename) {
this.setProperty(PORT, newFilename);
}
public int getPort() {
return getPropertyAsInt(PORT);
}
public void setFilename(String newFilename) {
this.setProperty(FILENAME, newFilename);
}
public String getFilename() {
return getPropertyAsString(FILENAME);
}
public void setRequestData(String newRequestData) {
this.setProperty(REQUEST, newRequestData);
}
public String getRequestData() {
return getPropertyAsString(REQUEST);
}
public void setTimeout(String newTimeout) {
this.setProperty(TIMEOUT, newTimeout);
}
public int getTimeout() {
return getPropertyAsInt(TIMEOUT);
}
public void setConnectTimeout(String newTimeout) {
this.setProperty(TIMEOUT_CONNECT, newTimeout, "");
}
public int getConnectTimeout() {
return getPropertyAsInt(TIMEOUT_CONNECT, 0);
}
public boolean getNoDelay() {
return getPropertyAsBoolean(NODELAY);
}
public void setClassname(String classname) {
this.setProperty(CLASSNAME, classname, ""); //$NON-NLS-1$
}
public String getClassname() {
String clazz = getPropertyAsString(CLASSNAME,"");
if (clazz==null || clazz.length()==0){
clazz = JMeterUtils.getPropDefault("tcp.handler", "MsClientImpl"); //$NON-NLS-1$ $NON-NLS-2$
}
return clazz;
}
/**
* Returns a formatted string label describing this sampler Example output:
* Tcp://Tcp.nowhere.com/pub/README.txt
*
* @return a formatted string label describing this sampler
*/
public String getLabel() {
return "tcp://" + this.getServer() + ":" + this.getPort();//$NON-NLS-1$ $NON-NLS-2$
}
private Class<?> getClass(String className) {
Class<?> c = null;
try {
c = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
try {
c = Class.forName(PROTO_PREFIX + className, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e1) {
log.error("Could not find protocol class '{}'", className); //$NON-NLS-1$
}
}
return c;
}
private TCPClient getProtocol() {
TCPClient tcpClient = null;
Class<?> javaClass = getClass(getClassname());
if (javaClass == null){
return null;
}
try {
tcpClient = (TCPClient) javaClass.getDeclaredConstructor().newInstance();
if (getPropertyAsString(EOL_BYTE, "").length()>0){
tcpClient.setEolByte(getEolByte());
log.info("Using eolByte={}", getEolByte());
}
if (log.isDebugEnabled()) {
log.debug("{} Created: {}@{}", this, getClassname(), Integer.toHexString(tcpClient.hashCode())); //$NON-NLS-1$
}
} catch (Exception e) {
log.error("{} Exception creating: {} ", this, getClassname(), e); //$NON-NLS-1$
}
return tcpClient;
}
@Override
public SampleResult sample(Entry e)// Entry tends to be ignored ...
{
if (firstSample) { // Do stuff we cannot do as part of threadStarted()
initSampling();
firstSample=false;
}
final boolean reUseConnection = isReUseConnection();
final boolean closeConnection = isCloseConnection();
String socketKey = getSocketKey();
if (log.isDebugEnabled()){
log.debug(getLabel() + " " + getFilename() + " " + getUsername() + " " + getPassword());
}
SampleResult res = new SampleResult();
boolean isSuccessful = false;
res.setSampleLabel(getName());// Use the test element name for the label
String sb = "Host: " + getServer() +
" Port: " + getPort() + "\n" +
"Reuse: " + reUseConnection +
" Close: " + closeConnection + "\n[" +
"SOLINGER: " + getSoLinger() +
" EOL: " + getEolByte() +
" noDelay: " + getNoDelay() +
"]";
res.setSamplerData(sb);
res.sampleStart();
try {
Socket sock;
try {
sock = getSocket(socketKey);
} finally {
res.connectEnd();
}
if (sock == null) {
res.setResponseCode("500"); //$NON-NLS-1$
res.setResponseMessage(getError());
} else if (protocolHandler == null){
res.setResponseCode("500"); //$NON-NLS-1$
res.setResponseMessage("Protocol handler not found");
} else {
currentSocket = sock;
InputStream is = sock.getInputStream();
OutputStream os = sock.getOutputStream();
String req = getRequestData();
// TODO handle filenames
res.setSamplerData(req);
String in = null;
//替换原来的编码
if (protocolHandler instanceof MsTCPClientImpl) {
((MsTCPClientImpl) protocolHandler).write(os, req , getCharset());
in = ((MsTCPClientImpl) protocolHandler).read(is, res , getCharset());
} else {
protocolHandler.write(os, req);
in = protocolHandler.read(is, res);
}
isSuccessful = setupSampleResult(res, in, null, protocolHandler);
}
} catch (ReadException ex) {
log.error("", ex);
isSuccessful=setupSampleResult(res, ex.getPartialResponse(), ex,protocolHandler);
closeSocket(socketKey);
} catch (Exception ex) {
log.error("", ex);
isSuccessful=setupSampleResult(res, "", ex, protocolHandler);
closeSocket(socketKey);
} finally {
currentSocket = null;
// Calculate response time
res.sampleEnd();
// Set if we were successful or not
res.setSuccessful(isSuccessful);
if (!reUseConnection || closeConnection) {
closeSocket(socketKey);
}
}
return res;
}
/**
* Fills SampleResult object
* @param sampleResult {@link SampleResult}
* @param readResponse Response read until error occurred
* @param exception Source exception
* @param protocolHandler {@link TCPClient}
* @return boolean if sample is considered as successful
*/
private boolean setupSampleResult(SampleResult sampleResult,
String readResponse,
Exception exception,
TCPClient protocolHandler) {
//替换原来的编码
sampleResult.setResponseData(readResponse,
protocolHandler != null ? this.getCharset() : null);
sampleResult.setDataType(SampleResult.TEXT);
if(exception==null) {
sampleResult.setResponseCodeOK();
sampleResult.setResponseMessage("OK"); //$NON-NLS-1$
} else {
sampleResult.setResponseCode("500"); //$NON-NLS-1$
sampleResult.setResponseMessage(exception.toString()); //$NON-NLS-1$
}
boolean isSuccessful = exception == null;
// Reset the status code if the message contains one
if (!StringUtils.isEmpty(readResponse) && STATUS_PREFIX.length() > 0) {
int i = readResponse.indexOf(STATUS_PREFIX);
int j = readResponse.indexOf(STATUS_SUFFIX, i + STATUS_PREFIX.length());
if (i != -1 && j > i) {
String rc = readResponse.substring(i + STATUS_PREFIX.length(), j);
sampleResult.setResponseCode(rc);
isSuccessful = isSuccessful && checkResponseCode(rc);
if (HAVE_STATUS_PROPS) {
sampleResult.setResponseMessage(STATUS_PROPS.getProperty(rc, "Status code not found in properties")); //$NON-NLS-1$
} else {
sampleResult.setResponseMessage("No status property file");
}
} else {
sampleResult.setResponseCode("999"); //$NON-NLS-1$
sampleResult.setResponseMessage("Status value not found");
isSuccessful = false;
}
}
return isSuccessful;
}
/**
* @param rc response code
* @return whether this represents success or not
*/
private boolean checkResponseCode(String rc) {
int responseCode = Integer.parseInt(rc);
return responseCode >= 400 && responseCode <= 599;
}
@Override
public void threadStarted() {
log.debug("Thread Started"); //$NON-NLS-1$
firstSample = true;
}
// Cannot do this as part of threadStarted() because the Config elements have not been processed.
private void initSampling() {
protocolHandler = getProtocol();
if (log.isDebugEnabled()) {
log.debug("Using Protocol Handler: {}", //$NON-NLS-1$
protocolHandler == null ? "NONE" : protocolHandler.getClass().getName()); //$NON-NLS-1$
}
if (protocolHandler != null){
protocolHandler.setupTest();
}
}
/**
* Close socket of current sampler
*/
private void closeSocket(String socketKey) {
Map<String, Object> cp = tp.get();
Socket con = (Socket) cp.remove(socketKey);
if (con != null) {
log.debug("{} Closing connection {}", this, con); //$NON-NLS-1$
try {
con.close();
} catch (IOException e) {
log.warn("Error closing socket {}", e); //$NON-NLS-1$
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void threadFinished() {
log.debug("Thread Finished"); //$NON-NLS-1$
tearDown();
if (protocolHandler != null){
protocolHandler.teardownTest();
}
}
/**
* Closes all connections, clears Map and remove thread local Map
*/
private void tearDown() {
Map<String, Object> cp = tp.get();
cp.forEach((k, v) -> {
if(k.startsWith(TCPKEY)) {
try {
((Socket)v).close();
} catch (IOException e) {
// NOOP
}
}
});
cp.clear();
tp.remove();
}
/**
* @see AbstractSampler#applies(ConfigTestElement)
*/
@Override
public boolean applies(ConfigTestElement configElement) {
String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
return APPLIABLE_CONFIG_CLASSES.contains(guiClass);
}
@Override
public boolean interrupt() {
Optional<Socket> sock = Optional.ofNullable(currentSocket); // fetch in case gets nulled later
if (sock.isPresent()) {
try {
sock.get().close();
} catch (IOException ignored) {
// NOOP
}
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,98 @@
/*
* 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 org.apache.jmeter.reporters;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.OnErrorTestElement;
import org.apache.jmeter.threads.JMeterContext.TestLogicalAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
/**
* ResultAction - take action based on the status of the last Result
*
*/
public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener {
private static final long serialVersionUID = 242L;
private static final Logger log = LoggerFactory.getLogger(ResultAction.class);
/**
* Constructor is initially called once for each occurrence in the test plan
* For GUI, several more instances are created Then clear is called at start
* of test Called several times during test startup The name will not
* necessarily have been set at this point.
*/
public ResultAction() {
super();
}
/**
* Examine the sample(s) and take appropriate action
*
* @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent)
*/
@Override
public void sampleOccurred(SampleEvent e) {
SampleResult s = e.getResult();
if (log.isDebugEnabled()) {
log.debug("ResultStatusHandler {} for {} OK? {}", getName(), s.getSampleLabel(), s.isSuccessful());
}
if (!s.isSuccessful()) {
if (isStopTestNow()) {
s.setStopTestNow(true);
} else if (isStopTest()) {
s.setStopTest(true);
} else if (isStopThread()) {
s.setStopThread(true);
} else if (isStartNextThreadLoop()) {
s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_THREAD);
} else if (isStartNextIterationOfCurrentLoop()) {
s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_CURRENT_LOOP);
} else if (isBreakCurrentLoop()) {
s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP);
}
} else {
if (getErrorAction() == 1000) {
s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void sampleStarted(SampleEvent e) {
// not used
}
/**
* {@inheritDoc}
*/
@Override
public void sampleStopped(SampleEvent e) {
// not used
}
}

View File

@ -0,0 +1,613 @@
/*
* 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 org.apache.jmeter.services;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.JMeterFileFilter;
import org.apache.jmeter.save.CSVSaveService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
/**
* This class provides thread-safe access to files, and to
* provide some simplifying assumptions about where to find files and how to
* name them. For instance, putting supporting files in the same directory as
* the saved test plan file allows users to refer to the file with just it's
* name - this FileServer class will find the file without a problem.
* Eventually, I want all in-test file access to be done through here, with the
* goal of packaging up entire test plans as a directory structure that can be
* sent via rmi to remote servers (currently, one must make sure the remote
* server has all support files in a relative-same location) and to package up
* test plans to execute on unknown boxes that only have Java installed.
*/
public class FileServer {
private static final Logger log = LoggerFactory.getLogger(FileServer.class);
/**
* The default base used for resolving relative files, i.e.<br/>
* {@code System.getProperty("user.dir")}
*/
private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$
/**
* Default base prefix: {@value}
*/
private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$
private static final String BASE_PREFIX =
JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$
BASE_PREFIX_DEFAULT);
private File base;
private final Map<String, FileEntry> files = new HashMap<>();
private static final FileServer server = new FileServer();
// volatile needed to ensure safe publication
private volatile String scriptName;
// Cannot be instantiated
private FileServer() {
base = new File(DEFAULT_BASE);
log.info("Default base='{}'", DEFAULT_BASE);
}
/**
* @return the singleton instance of the server.
*/
public static FileServer getFileServer() {
return server;
}
/**
* Resets the current base to DEFAULT_BASE.
*/
public synchronized void resetBase() {
checkForOpenFiles();
base = new File(DEFAULT_BASE);
log.info("Reset base to '{}'", base);
}
/**
* Sets the current base directory for relative file names from the provided path.
* If the path does not refer to an existing directory, then its parent is used.
* Normally the provided path is a file, so using the parent directory is appropriate.
*
* @param basedir the path to set, or {@code null} if the GUI is being cleared
* @throws IllegalStateException if files are still open
*/
public synchronized void setBasedir(String basedir) {
checkForOpenFiles(); // TODO should this be called if basedir == null?
if (basedir != null) {
File newBase = new File(basedir);
if (!newBase.isDirectory()) {
newBase = newBase.getParentFile();
}
base = newBase;
log.info("Set new base='{}'", base);
}
}
/**
* Sets the current base directory for relative file names from the provided script file.
* The parameter is assumed to be the path to a JMX file, so the base directory is derived
* from its parent.
*
* @param scriptPath the path of the script file; must be not be {@code null}
* @throws IllegalStateException if files are still open
* @throws IllegalArgumentException if scriptPath parameter is null
*/
public synchronized void setBaseForScript(File scriptPath) {
if (scriptPath == null) {
throw new IllegalArgumentException("scriptPath must not be null");
}
setScriptName(scriptPath.getName());
// getParentFile() may not work on relative paths
setBase(scriptPath.getAbsoluteFile().getParentFile());
}
/**
* Sets the current base directory for relative file names.
*
* @param jmxBase the path of the script file base directory, cannot be null
* @throws IllegalStateException if files are still open
* @throws IllegalArgumentException if {@code basepath} is null
*/
public synchronized void setBase(File jmxBase) {
if (jmxBase == null) {
throw new IllegalArgumentException("jmxBase must not be null");
}
checkForOpenFiles();
base = jmxBase;
log.info("Set new base='{}'", base);
}
/**
* Check if there are entries in use.
* <p>
* Caller must ensure that access to the files map is single-threaded as
* there is a window between checking the files Map and clearing it.
*
* @throws IllegalStateException if there are any entries still in use
*/
private void checkForOpenFiles() throws IllegalStateException {
if (filesOpen()) { // checks for entries in use
throw new IllegalStateException("Files are still open, cannot change base directory");
}
files.clear(); // tidy up any unused entries
}
public synchronized String getBaseDir() {
return base.getAbsolutePath();
}
public static String getDefaultBase() {
return DEFAULT_BASE;
}
/**
* Calculates the relative path from DEFAULT_BASE to the current base,
* which must be the same as or a child of the default.
*
* @return the relative path, or {@code "."} if the path cannot be determined
*/
public synchronized File getBaseDirRelative() {
// Must first convert to absolute path names to ensure parents are available
File parent = new File(DEFAULT_BASE).getAbsoluteFile();
File f = base.getAbsoluteFile();
ArrayDeque<String> l = new ArrayDeque<>();
while (f != null) {
if (f.equals(parent)) {
if (l.isEmpty()) {
break;
}
File rel = new File(l.pop());
while (!l.isEmpty()) {
rel = new File(rel, l.pop());
}
return rel;
}
l.push(f.getName());
f = f.getParentFile();
}
return new File(".");
}
/**
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null)
*/
public void reserveFile(String filename) {
reserveFile(filename,null);
}
/**
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null)
* @param charsetName - the character set encoding to use for the file (may be null)
*/
public void reserveFile(String filename, String charsetName) {
reserveFile(filename, charsetName, filename, false);
}
/**
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null)
* @param charsetName - the character set encoding to use for the file (may be null)
* @param alias - the name to be used to access the object (must not be null)
*/
public void reserveFile(String filename, String charsetName, String alias) {
reserveFile(filename, charsetName, alias, false);
}
/**
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null or empty)
* @param charsetName - the character set encoding to use for the file (may be null)
* @param alias - the name to be used to access the object (must not be null)
* @param hasHeader true if the file has a header line describing the contents
* @return the header line; may be null
* @throws IllegalArgumentException if header could not be read or filename is null or empty
*/
public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) {
if (filename == null || filename.isEmpty()) {
throw new IllegalArgumentException("Filename must not be null or empty");
}
if (alias == null) {
throw new IllegalArgumentException("Alias must not be null");
}
FileEntry fileEntry = files.get(alias);
if (fileEntry == null) {
fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName);
if (filename.equals(alias)) {
log.info("Stored: {}", filename);
} else {
log.info("Stored: {} Alias: {}", filename, alias);
}
files.put(alias, fileEntry);
if (hasHeader) {
try {
fileEntry.headerLine = readLine(alias, false);
if (fileEntry.headerLine == null) {
fileEntry.exception = new EOFException("File is empty: " + fileEntry.file);
}
} catch (IOException | IllegalArgumentException e) {
fileEntry.exception = e;
}
}
}
if (hasHeader && fileEntry.headerLine == null) {
throw new IllegalArgumentException("Could not read file header line for file " + filename,
fileEntry.exception);
}
return fileEntry.headerLine;
}
/**
* Resolves file name into {@link File} instance.
* When filename is not absolute and not found from current working dir,
* it tries to find it under current base directory
* @param filename original file name
* @return {@link File} instance
*/
private File resolveFileFromPath(String filename) {
File f = new File(filename);
if (f.isAbsolute() || f.exists()) {
return f;
} else {
return new File(base, filename);
}
}
/**
* Get the next line of the named file, recycle by default.
*
* @param filename the filename or alias that was used to reserve the file
* @return String containing the next line in the file
* @throws IOException when reading of the file fails, or the file was not reserved properly
*/
public String readLine(String filename) throws IOException {
return readLine(filename, true);
}
/**
* Get the next line of the named file, first line is name to false
*
* @param filename the filename or alias that was used to reserve the file
* @param recycle - should file be restarted at EOF?
* @return String containing the next line in the file (null if EOF reached and not recycle)
* @throws IOException when reading of the file fails, or the file was not reserved properly
*/
public String readLine(String filename, boolean recycle) throws IOException {
return readLine(filename, recycle, false);
}
/**
* Get the next line of the named file
*
* @param filename the filename or alias that was used to reserve the file
* @param recycle - should file be restarted at EOF?
* @param ignoreFirstLine - Ignore first line
* @return String containing the next line in the file (null if EOF reached and not recycle)
* @throws IOException when reading of the file fails, or the file was not reserved properly
*/
public synchronized String readLine(String filename, boolean recycle,
boolean ignoreFirstLine) throws IOException {
FileEntry fileEntry = files.get(filename);
if (fileEntry != null) {
if (fileEntry.inputOutputObject == null) {
fileEntry.inputOutputObject = createBufferedReader(fileEntry);
} else if (!(fileEntry.inputOutputObject instanceof Reader)) {
throw new IOException("File " + filename + " already in use");
}
BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject;
String line = reader.readLine();
if (line == null && recycle) {
reader.close();
reader = createBufferedReader(fileEntry);
fileEntry.inputOutputObject = reader;
if (ignoreFirstLine) {
// read first line and forget
reader.readLine();//NOSONAR
}
line = reader.readLine();
}
log.debug("Read:{}", line);
return line;
}
throw new IOException("File never reserved: "+filename);
}
/**
*
* @param alias the file name or alias
* @param recycle whether the file should be re-started on EOF
* @param ignoreFirstLine whether the file contains a file header which will be ignored
* @param delim the delimiter to use for parsing
* @return the parsed line, will be empty if the file is at EOF
* @throws IOException when reading of the aliased file fails, or the file was not reserved properly
*/
public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException {
BufferedReader reader = getReader(alias, recycle, ignoreFirstLine);
return CSVSaveService.csvReadFile(reader, delim);
}
/**
* Return BufferedReader handling close if EOF reached and recycle is true
* and ignoring first line if ignoreFirstLine is true
*
* @param alias String alias
* @param recycle Recycle at eof
* @param ignoreFirstLine Ignore first line
* @return {@link BufferedReader}
*/
private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException {
FileEntry fileEntry = files.get(alias);
if (fileEntry != null) {
BufferedReader reader;
if (fileEntry.inputOutputObject == null) {
reader = createBufferedReader(fileEntry);
fileEntry.inputOutputObject = reader;
if (ignoreFirstLine) {
// read first line and forget
reader.readLine(); //NOSONAR
}
} else if (!(fileEntry.inputOutputObject instanceof Reader)) {
throw new IOException("File " + alias + " already in use");
} else {
reader = (BufferedReader) fileEntry.inputOutputObject;
if (recycle) { // need to check if we are at EOF already
reader.mark(1);
int peek = reader.read();
if (peek == -1) { // already at EOF
reader.close();
reader = createBufferedReader(fileEntry);
fileEntry.inputOutputObject = reader;
if (ignoreFirstLine) {
// read first line and forget
reader.readLine(); //NOSONAR
}
} else { // OK, we still have some data, restore it
reader.reset();
}
}
}
return reader;
} else {
throw new IOException("File never reserved: "+alias);
}
}
private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException {
if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) {
throw new IllegalArgumentException("File " + fileEntry.file.getName() + " must exist and be readable");
}
BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR
InputStreamReader isr = null;
// If file encoding is specified, read using that encoding, otherwise use default platform encoding
String charsetName = fileEntry.charSetEncoding;
if (!JOrphanUtils.isBlank(charsetName)) {
isr = new InputStreamReader(fis, charsetName);
} else if (fis.hasBOM()) {
isr = new InputStreamReader(fis, fis.getBOM().getCharsetName());
} else {
@SuppressWarnings("DefaultCharset") final InputStreamReader withPlatformEncoding = new InputStreamReader(fis);
isr = withPlatformEncoding;
}
return new BufferedReader(isr);
}
public synchronized void write(String filename, String value) throws IOException {
FileEntry fileEntry = files.get(filename);
if (fileEntry != null) {
if (fileEntry.inputOutputObject == null) {
fileEntry.inputOutputObject = createBufferedWriter(fileEntry);
} else if (!(fileEntry.inputOutputObject instanceof Writer)) {
throw new IOException("File " + filename + " already in use");
}
BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject;
log.debug("Write:{}", value);
writer.write(value);
} else {
throw new IOException("File never reserved: "+filename);
}
}
private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException {
OutputStream fos = Files.newOutputStream(fileEntry.file.toPath());
OutputStreamWriter osw;
// If file encoding is specified, write using that encoding, otherwise use default platform encoding
String charsetName = fileEntry.charSetEncoding;
if (!JOrphanUtils.isBlank(charsetName)) {
osw = new OutputStreamWriter(fos, charsetName);
} else {
@SuppressWarnings("DefaultCharset") final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos);
osw = withPlatformEncoding;
}
return new BufferedWriter(osw);
}
public synchronized void closeFiles() throws IOException {
for (Map.Entry<String, FileEntry> me : files.entrySet()) {
closeFile(me.getKey(), me.getValue() );
}
files.clear();
}
/**
* 根据线程名字清理掉当前线程缓存的csv
*
* @param name 线程组名称
*/
public synchronized void closeCsv(String name) {
try {
if (StringUtils.isNotEmpty(name)) {
List<String> list = new ArrayList<>();
for (Iterator<String> iterator = files.keySet().iterator(); iterator.hasNext(); ) {
String key = iterator.next();
if (key.contains(name)) {
FileEntry fileEntry = files.get(key);
closeFile(name, fileEntry);
list.add(key);
}
}
if (CollectionUtils.isNotEmpty(list)) {
for (String key : list) {
files.remove(key);
}
}
}
LoggerUtil.info("在处理中的CSV数量" + files.size());
} catch (Exception e) {
LoggerUtil.error("关闭CSV异常" + name, e);
}
}
public int fileSize() {
return files.size();
}
/**
* @param name the name or alias of the file to be closed
* @throws IOException when closing of the aliased file fails
*/
public synchronized void closeFile(String name) throws IOException {
FileEntry fileEntry = files.get(name);
closeFile(name, fileEntry);
}
private void closeFile(String name, FileEntry fileEntry) throws IOException {
if (fileEntry != null && fileEntry.inputOutputObject != null) {
log.info("Close: {}", name);
fileEntry.inputOutputObject.close();
fileEntry.inputOutputObject = null;
}
}
boolean filesOpen() { // package access for test code only
return files.values().stream()
.anyMatch(fileEntry -> fileEntry.inputOutputObject != null);
}
/**
* Method will get a random file in a base directory
* <p>
* TODO hey, not sure this method belongs here.
* FileServer is for thread safe File access relative to current test's base directory.
*
* @param basedir name of the directory in which the files can be found
* @param extensions array of allowed extensions, if <code>null</code> is given,
* any file be allowed
* @return a random File from the <code>basedir</code> that matches one of
* the extensions
*/
public File getRandomFile(String basedir, String[] extensions) {
File input = null;
if (basedir != null) {
File src = new File(basedir);
File[] lfiles = src.listFiles(new JMeterFileFilter(extensions));
if (lfiles != null) {
// lfiles cannot be null as it has been checked before
int count = lfiles.length;
input = lfiles[ThreadLocalRandom.current().nextInt(count)];
}
}
return input;
}
/**
* Get {@link File} instance for provided file path,
* resolve file location relative to base dir or script dir when needed
*
* @param path original path to file, maybe relative
* @return {@link File} instance
*/
public File getResolvedFile(String path) {
reserveFile(path);
return files.get(path).file;
}
private static class FileEntry {
private String headerLine;
private Throwable exception;
private final File file;
private Closeable inputOutputObject;
private final String charSetEncoding;
FileEntry(File f, Closeable o, String e) {
file = f;
inputOutputObject = o;
charSetEncoding = e;
}
}
/**
* Resolve a file name that may be relative to the base directory. If the
* name begins with the value of the JMeter property
* "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is
* assumed to be relative to the basename.
*
* @param relativeName
* filename that should be checked for
* <code>jmeter.save.saveservice.base_prefix</code>
* @return the updated filename
*/
public static String resolveBaseRelativeName(String relativeName) {
if (relativeName.startsWith(BASE_PREFIX)) {
String newName = relativeName.substring(BASE_PREFIX.length());
return new File(getFileServer().getBaseDir(), newName).getAbsolutePath();
}
return relativeName;
}
/**
* @return JMX Script name
* @since 2.6
*/
public String getScriptName() {
return scriptName;
}
/**
* @param scriptName Script name
* @since 2.6
*/
public void setScriptName(String scriptName) {
this.scriptName = scriptName;
}
}

View File

@ -0,0 +1,276 @@
/*
* 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 org.apache.jmeter.testelement;
import io.metersphere.utils.LoggerUtil;
import org.apache.jmeter.NewDriver;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.TestElementProperty;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class TestPlan extends AbstractTestElement implements Serializable, TestStateListener {
private static final long serialVersionUID = 234L;
private static final Logger log = LoggerFactory.getLogger(TestPlan.class);
//+ JMX field names - do not change values
private static final String FUNCTIONAL_MODE = "TestPlan.functional_mode"; //$NON-NLS-1$
private static final String USER_DEFINED_VARIABLES = "TestPlan.user_defined_variables"; //$NON-NLS-1$
private static final String SERIALIZE_THREADGROUPS = "TestPlan.serialize_threadgroups"; //$NON-NLS-1$
private static final String CLASSPATHS = "TestPlan.user_define_classpath"; //$NON-NLS-1$
private static final String TEARDOWN_ON_SHUTDOWN = "TestPlan.tearDown_on_shutdown"; //$NON-NLS-1$
//- JMX field names
private static final String CLASSPATH_SEPARATOR = ","; //$NON-NLS-1$
private static final String BASEDIR = "basedir";
private transient List<AbstractThreadGroup> threadGroups = new ArrayList<>();
// There's only 1 test plan, so can cache the mode here
private static volatile boolean functionalMode = false;
public TestPlan() {
super();
}
public TestPlan(String name) {
setName(name);
}
// create transient item
protected Object readResolve() {
threadGroups = new ArrayList<>();
return this;
}
public void prepareForPreCompile() {
getVariables().setRunningVersion(true);
}
/**
* Fetches the functional mode property
*
* @return functional mode
*/
public boolean isFunctionalMode() {
return getPropertyAsBoolean(FUNCTIONAL_MODE);
}
public void setUserDefinedVariables(Arguments vars) {
setProperty(new TestElementProperty(USER_DEFINED_VARIABLES, vars));
}
public JMeterProperty getUserDefinedVariablesAsProperty() {
return getProperty(USER_DEFINED_VARIABLES);
}
public String getBasedir() {
return getPropertyAsString(BASEDIR);
}
// Does not appear to be used yet
public void setBasedir(String b) {
setProperty(BASEDIR, b);
}
public Arguments getArguments() {
return getVariables();
}
public Map<String, String> getUserDefinedVariables() {
Arguments args = getVariables();
return args.getArgumentsAsMap();
}
private Arguments getVariables() {
Arguments args = (Arguments) getProperty(USER_DEFINED_VARIABLES).getObjectValue();
if (args == null) {
args = new Arguments();
setUserDefinedVariables(args);
}
return args;
}
public void setFunctionalMode(boolean funcMode) {
setProperty(new BooleanProperty(FUNCTIONAL_MODE, funcMode));
setGlobalFunctionalMode(funcMode);
}
/**
* Set JMeter in functional mode
*
* @param funcMode boolean functional mode
*/
private static void setGlobalFunctionalMode(boolean funcMode) {
functionalMode = funcMode;
}
/**
* Gets the static copy of the functional mode
*
* @return mode
*/
public static boolean getFunctionalMode() {
return functionalMode;
}
public void setSerialized(boolean serializeTGs) {
setProperty(new BooleanProperty(SERIALIZE_THREADGROUPS, serializeTGs));
}
public void setTearDownOnShutdown(boolean tearDown) {
setProperty(TEARDOWN_ON_SHUTDOWN, tearDown, false);
}
public boolean isTearDownOnShutdown() {
return getPropertyAsBoolean(TEARDOWN_ON_SHUTDOWN, false);
}
/**
* Set the classpath for the test plan. If the classpath is made up from
* more then one path, the parts must be separated with
* {@link TestPlan#CLASSPATH_SEPARATOR}.
*
* @param text the classpath to be set
*/
public void setTestPlanClasspath(String text) {
setProperty(CLASSPATHS, text);
}
public void setTestPlanClasspathArray(String[] text) {
StringBuilder cat = new StringBuilder();
for (int idx = 0; idx < text.length; idx++) {
if (idx > 0) {
cat.append(CLASSPATH_SEPARATOR);
}
cat.append(text[idx]);
}
this.setTestPlanClasspath(cat.toString());
}
public String[] getTestPlanClasspathArray() {
return JOrphanUtils.split(this.getTestPlanClasspath(), CLASSPATH_SEPARATOR);
}
/**
* Returns the classpath
*
* @return classpath
*/
public String getTestPlanClasspath() {
return getPropertyAsString(CLASSPATHS);
}
/**
* Fetch the serialize threadgroups property
*
* @return serialized setting
*/
public boolean isSerialized() {
return getPropertyAsBoolean(SERIALIZE_THREADGROUPS);
}
public void addParameter(String name, String value) {
getVariables().addArgument(name, value);
}
@Override
public void addTestElement(TestElement tg) {
super.addTestElement(tg);
if (tg instanceof AbstractThreadGroup && !isRunningVersion()) {
addThreadGroup((AbstractThreadGroup) tg);
}
}
/**
* Adds a feature to the AbstractThreadGroup attribute of the TestPlan object.
*
* @param group the feature to be added to the AbstractThreadGroup attribute
*/
public void addThreadGroup(AbstractThreadGroup group) {
threadGroups.add(group);
}
/**
* {@inheritDoc}
*/
@Override
public void testEnded() {
LoggerUtil.info("Test plan " + this.getName() + "test end");
}
/**
* {@inheritDoc}
*/
@Override
public void testEnded(String host) {
testEnded();
}
/**
* {@inheritDoc}
*/
@Override
public void testStarted() {
LoggerUtil.info("测试计划开始执行:" + this.getName());
if (getBasedir() != null && getBasedir().length() > 0) {
try {
FileServer.getFileServer().setBasedir(FileServer.getFileServer().getBaseDir() + getBasedir());
} catch (IllegalStateException e) {
log.error("Failed to set file server base dir with {}", getBasedir(), e);
}
}
// we set the classpath
String[] paths = this.getTestPlanClasspathArray();
for (String path : paths) {
try {
NewDriver.addURL(path);
log.info("added {} to classpath", path);
} catch (MalformedURLException e) {
// TODO Should we continue the test or fail ?
log.error("Error adding {} to classpath", path, e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void testStarted(String host) {
testStarted();
}
}

View File

@ -0,0 +1,91 @@
/*
* 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 org.apache.jmeter.util;
import org.apache.jmeter.testbeans.TestBean;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import java.util.*;
/**
* 解决JSR233加载 ScriptEngineFactory 空指针问题
*/
public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport {
private static final String[] LANGUAGE_TAGS;
/**
* Will be removed in next version following 3.2
* @deprecated use {@link JSR223BeanInfoSupport#getLanguageNames()}
*/
@Deprecated
public static final String[][] LANGUAGE_NAMES; // NOSONAR Kept for backward compatibility
private static final String[][] CONSTANT_LANGUAGE_NAMES;
static {
Map<String, ScriptEngineFactory> nameMap = new HashMap<>();
ScriptEngineManager sem = new ScriptEngineManager();
final List<ScriptEngineFactory> engineFactories = sem.getEngineFactories();
for(ScriptEngineFactory fact : engineFactories){
List<String> names = fact.getNames();
for(String shortName : names) {
if (shortName != null) {
nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact);
}
}
}
LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]);
Arrays.sort(LANGUAGE_TAGS);
CONSTANT_LANGUAGE_NAMES = new String[nameMap.size()][2];
int i = 0;
for (Map.Entry<String, ScriptEngineFactory> me : nameMap.entrySet()) {
final String key = me.getKey();
CONSTANT_LANGUAGE_NAMES[i][0] = key;
final ScriptEngineFactory fact = me.getValue();
CONSTANT_LANGUAGE_NAMES[i++][1] = key +
" (" // $NON-NLS-1$
+ fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$
+ " / " // $NON-NLS-1$
+ fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$
+ ")"; // $NON-NLS-1$
}
LANGUAGE_NAMES = getLanguageNames(); // NOSONAR Kept for backward compatibility
}
private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() {
@Override
protected Object[][] getContents() {
return CONSTANT_LANGUAGE_NAMES;
}
};
protected JSR223BeanInfoSupport(Class<? extends TestBean> beanClass) {
super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE);
}
/**
* @return String array of 2 columns array containing Script engine short name / Script Language details
*/
public static final String[][] getLanguageNames() {
return CONSTANT_LANGUAGE_NAMES.clone();
}
}

View File

@ -0,0 +1,353 @@
/*
* 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 org.apache.jmeter.util;
import groovy.lang.GroovyClassLoader;
import io.metersphere.utils.CustomizeFunctionUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jorphan.util.JOrphanUtils;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
/**
* Base class for JSR223 Test elements
*/
public abstract class JSR223TestElement extends ScriptingTestElement
implements Serializable, TestStateListener {
private static final long serialVersionUID = 233L;
private static final Logger logger = LoggerFactory.getLogger(JSR223TestElement.class);
/**
* Cache of compiled scripts
*/
private static final Map<String, CompiledScript> compiledScriptsCache =
Collections.synchronizedMap(
new LRUMap<>(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100)));
/**
* If not empty then script in ScriptText will be compiled and cached
*/
private String cacheKey = "";
/**
* md5 of the script, used as an unique key for the cache
*/
private String scriptMd5 = null;
/**
* Initialization On Demand Holder pattern
*/
private static class LazyHolder {
private LazyHolder() {
super();
}
public static final ScriptEngineManager INSTANCE = new ScriptEngineManager();
}
/**
* @return ScriptEngineManager singleton
*/
public static ScriptEngineManager getInstance() {
return LazyHolder.INSTANCE;
}
protected JSR223TestElement() {
super();
}
/**
* @return {@link ScriptEngine} for language defaulting to groovy if language is not set
* @throws ScriptException when no {@link ScriptEngine} could be found
*/
protected ScriptEngine getScriptEngine() throws ScriptException {
String lang = getScriptLanguageWithDefault();
ScriptEngine scriptEngine = getInstance().getEngineByName(lang);
if (scriptEngine == null) {
throw new ScriptException("Cannot find engine named: '" + lang + "', ensure you set language field in JSR223 Test Element: " + getName());
}
return scriptEngine;
}
/**
* @return script language or DEFAULT_SCRIPT_LANGUAGE if none is set
*/
private String getScriptLanguageWithDefault() {
String lang = getScriptLanguage();
if (StringUtils.isNotEmpty(lang)) {
return lang;
}
return DEFAULT_SCRIPT_LANGUAGE;
}
/**
* Populate variables to be passed to scripts
*
* @param bindings Bindings
*/
protected void populateBindings(Bindings bindings) {
final String label = getName();
final String fileName = getFilename();
final String scriptParameters = getParameters();
// Use actual class name for log
final Logger elementLogger = LoggerFactory.getLogger(getClass().getName() + "." + getName());
bindings.put("log", elementLogger); // $NON-NLS-1$ (this name is fixed)
bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed)
bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed)
bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed)
String[] args = JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$
bindings.put("args", args); // $NON-NLS-1$ (this name is fixed)
// Add variables for access to context and variables
JMeterContext jmctx = JMeterContextService.getContext();
bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed)
JMeterVariables vars = jmctx.getVariables();
bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed)
Properties props = JMeterUtils.getJMeterProperties();
bindings.put("props", props); // $NON-NLS-1$ (this name is fixed)
// For use in debugging:
bindings.put("OUT", System.out); // NOSONAR $NON-NLS-1$ (this name is fixed)
// Most subclasses will need these:
Sampler sampler = jmctx.getCurrentSampler();
bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed)
SampleResult prev = jmctx.getPreviousResult();
bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed)
}
/**
* This method will run inline script or file script with special behaviour for file script:
* - If ScriptEngine implements Compilable script will be compiled and cached
* - If not if will be run
*
* @param scriptEngine ScriptEngine
* @param pBindings {@link Bindings} might be null
* @return Object returned by script
* @throws IOException when reading the script fails
* @throws ScriptException when compiling or evaluation of the script fails
*/
protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings pBindings)
throws IOException, ScriptException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// 设置自定义类加载器
Object dynamicClassLoader = JMeterContextService.getContext().getVariables().getObject(CustomizeFunctionUtil.MS_CLASS_LOADER);
if (dynamicClassLoader != null) {
GroovyClassLoader classLoader = (GroovyClassLoader) dynamicClassLoader;
if (scriptEngine instanceof GroovyScriptEngineImpl) {
GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine;
groovyScriptEngine.setClassLoader(classLoader);
} else {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
Bindings bindings = pBindings;
if (bindings == null) {
bindings = scriptEngine.createBindings();
}
populateBindings(bindings);
File scriptFile = new File(getFilename());
// Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws
// "java.lang.Error: unimplemented"
boolean supportsCompilable = false;
try {
if (!StringUtils.isEmpty(getFilename())) {
if (scriptFile.exists() && scriptFile.canRead()) {
if (supportsCompilable) {
String newCacheKey = getScriptLanguage() + "#" + // $NON-NLS-1$
scriptFile.getAbsolutePath() + "#" + // $NON-NLS-1$
scriptFile.lastModified();
CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey);
if (compiledScript == null) {
synchronized (compiledScriptsCache) {
compiledScript = compiledScriptsCache.get(newCacheKey);
if (compiledScript == null) {
try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) {
compiledScript = ((Compilable) scriptEngine).compile(fileReader);
compiledScriptsCache.put(newCacheKey, compiledScript);
}
}
}
}
return compiledScript.eval(bindings);
} else {
try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) {
return scriptEngine.eval(fileReader, bindings);
}
}
} else {
throw new ScriptException("Script file '" + scriptFile.getAbsolutePath()
+ "' does not exist or is unreadable for element:" + getName());
}
} else if (!StringUtils.isEmpty(getScript())) {
if (supportsCompilable &&
!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) {
computeScriptMD5();
CompiledScript compiledScript = compiledScriptsCache.get(this.scriptMd5);
if (compiledScript == null) {
synchronized (compiledScriptsCache) {
compiledScript = compiledScriptsCache.get(this.scriptMd5);
if (compiledScript == null) {
compiledScript = ((Compilable) scriptEngine).compile(getScript());
compiledScriptsCache.put(this.scriptMd5, compiledScript);
}
}
}
return compiledScript.eval(bindings);
} else {
return scriptEngine.eval(getScript(), bindings);
}
} else {
throw new ScriptException("Both script file and script text are empty for element:" + getName());
}
} catch (ScriptException ex) {
Throwable rootCause = ex.getCause();
if (isStopCondition(rootCause)) {
throw (RuntimeException) ex.getCause();
} else {
throw ex;
}
} finally {
// 恢复原始类加载器
Thread.currentThread().setContextClassLoader(loader);
}
}
/**
* @return boolean true if element is not compilable or if compilation succeeds
* @throws IOException if script is missing
* @throws ScriptException if compilation fails
*/
public boolean compile()
throws ScriptException, IOException {
String lang = getScriptLanguageWithDefault();
ScriptEngine scriptEngine = getInstance().getEngineByName(lang);
boolean supportsCompilable = scriptEngine instanceof Compilable
&& !"bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName()); // NOSONAR // $NON-NLS-1$
if (!supportsCompilable) {
return true;
}
if (!StringUtils.isEmpty(getScript())) {
try {
((Compilable) scriptEngine).compile(getScript());
return true;
} catch (ScriptException e) { // NOSONAR
logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage());
return false;
}
} else {
File scriptFile = new File(getFilename());
try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) {
try {
((Compilable) scriptEngine).compile(fileReader);
return true;
} catch (ScriptException e) { // NOSONAR
logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage());
return false;
}
}
}
}
/**
* compute MD5 if it is null
*/
private void computeScriptMD5() {
// compute the md5 of the script if needed
if (scriptMd5 == null) {
scriptMd5 = DigestUtils.md5Hex(getScript());
}
}
/**
* @return the cacheKey
*/
public String getCacheKey() {
return cacheKey;
}
/**
* @param cacheKey the cacheKey to set
*/
public void setCacheKey(String cacheKey) {
this.cacheKey = cacheKey;
}
/**
* @see org.apache.jmeter.testelement.TestStateListener#testStarted()
*/
@Override
public void testStarted() {
// NOOP
}
/**
* @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String)
*/
@Override
public void testStarted(String host) {
// NOOP
}
/**
* @see org.apache.jmeter.testelement.TestStateListener#testEnded()
*/
@Override
public void testEnded() {
testEnded("");
}
/**
* @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String)
*/
@Override
public void testEnded(String host) {
compiledScriptsCache.clear();
this.scriptMd5 = null;
}
public String getScriptLanguage() {
return scriptLanguage;
}
public void setScriptLanguage(String s) {
scriptLanguage = s;
}
}

View File

@ -0,0 +1,387 @@
/*
* 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 org.apache.jmeter.util;
import org.apache.commons.lang3.Validate;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.util.keystore.JmeterKeyStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Locale;
/**
* The SSLManager handles the KeyStore information for JMeter. Basically, it
* handles all the logic for loading and initializing all the JSSE parameters
* and selecting the alias to authenticate against if it is available.
* SSLManager will try to automatically select the client certificate for you,
* but if it can't make a decision, it will pop open a dialog asking you for
* more information.
* <p>
* TODO? - N.B. does not currently allow the selection of a client certificate.
*
*/
public abstract class SSLManager {
private static final Logger log = LoggerFactory.getLogger(SSLManager.class);
private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$
private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ NOSONAR no hard coded password
public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$
private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$
private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$
/** Singleton instance of the manager */
private static SSLManager manager;
private static final boolean IS_SSL_SUPPORTED = true;
/** Cache the KeyStore instance */
private JmeterKeyStore keyStore;
/** Cache the TrustStore instance - null if no truststore name was provided */
private KeyStore trustStore = null;
// Have we yet tried to load the truststore?
private volatile boolean truststoreLoaded=false;
/** Have the password available */
protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD);
private int keystoreAliasStartIndex;
private int keystoreAliasEndIndex;
private String clientCertAliasVarName;
/**
* Resets the SSLManager so that we can create a new one with a new keystore
*/
public static synchronized void reset() {
SSLManager.manager = null;
}
public abstract void setContext(HttpURLConnection conn);
/**
* Default implementation of setting the Provider
*
* @param provider
* the provider to use
*/
protected void setProvider(Provider provider) {
if (null != provider) {
Security.addProvider(provider);
}
}
protected synchronized JmeterKeyStore getKeyStore() {
if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
try {
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
log.info("KeyStore created OK");
} catch (Exception e) {
this.keyStore = null;
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e);
}
try {
// The string 'NONE' is used for the keystore location when using PKCS11
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE
if ("NONE".equalsIgnoreCase(fileName)) {
retryLoadKeys(null, false);
log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount());
} else {
File initStore = new File(fileName);
if (fileName.length() > 0 && initStore.exists()) {
retryLoadKeys(initStore, true);
if (log.isInfoEnabled()) {
log.info("Total of {} aliases loaded OK from keystore {}",
keyStore.getAliasCount(), fileName);
}
} else {
log.warn("Keystore file not found, loading empty keystore");
this.defaultpw = ""; // Ensure not null
this.keyStore.load(null, "");
}
}
} catch (Exception e) {
log.error("Problem loading keystore: {}", e.getMessage(), e);
}
if (log.isDebugEnabled()) {
log.debug("JmeterKeyStore type: {}", this.keyStore.getClass());
}
}
return this.keyStore;
}
/**
* Opens and initializes the KeyStore. If the password for the KeyStore is
* not set, this method will prompt you to enter it. Unfortunately, there is
* no PasswordEntryField available from JOptionPane.
*
* @return the configured {@link JmeterKeyStore}
*/
protected synchronized JmeterKeyStore getKeyStore(InputStream is,String password) {
if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
try {
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
log.info("KeyStore created OK");
} catch (Exception e) {
this.keyStore = null;
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e);
}
try {
// The string 'NONE' is used for the keystore location when using PKCS11
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE
if ("NONE".equalsIgnoreCase(fileName)) {
retryLoadKeys(null, false);
log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount());
} else {
File initStore = new File(fileName);
if (fileName.length() > 0 && initStore.exists()) {
retryLoadKeys(initStore, true);
if (log.isInfoEnabled()) {
log.info("Total of {} aliases loaded OK from keystore {}",
keyStore.getAliasCount(), fileName);
}
} else {
log.warn("Keystore file not found, loading empty keystore");
this.defaultpw = ""; // Ensure not null
this.keyStore.load(is, password);
}
}
} catch (IOException e) {
log.error("Can't load keystore '{}'. Wrong password?", fileName, e);
} catch (UnrecoverableKeyException e) {
log.error("Can't recover keys from keystore '{}'", fileName, e);
} catch (NoSuchAlgorithmException e) {
log.error("Problem finding the correct algorithm while loading keys from keystore '{}'", fileName, e);
} catch (CertificateException e) {
log.error("Problem with one of the certificates/keys in keystore '{}'", fileName, e);
} catch (KeyStoreException e) {
log.error("Problem loading keystore: {}", e.getMessage(), e);
}
if (log.isDebugEnabled()) {
log.debug("JmeterKeyStore type: {}", this.keyStore.getClass());
}
}
return this.keyStore;
}
private void retryLoadKeys(File initStore, boolean allowEmptyPassword) throws NoSuchAlgorithmException,
CertificateException, KeyStoreException, UnrecoverableKeyException {
for (int i = 0; i < 3; i++) {
String password = getPassword();
if (!allowEmptyPassword) {
Validate.notNull(password, "Password for keystore must not be null");
}
try {
if (initStore == null) {
this.keyStore.load(null, password);
} else {
try (InputStream fis = new FileInputStream(initStore)) {
this.keyStore.load(fis, password);
}
}
return;
} catch (IOException e) {
log.warn("Could not load keystore '{}'. Wrong password for keystore?", initStore, e);
}
this.defaultpw = null;
}
}
/*
* The password can be defined as a property; this dialogue is provided to allow it
* to be entered at run-time.
*/
private String getPassword() {
String password = this.defaultpw;
if (null == password) {
final GuiPackage guiInstance = GuiPackage.getInstance();
// if (guiInstance != null) {
// JPanel panel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill, grow]"));
// JLabel passwordLabel = new JLabel("Password: ");
// JPasswordField pwf = new JPasswordField(64);
// pwf.setEchoChar('*');
// passwordLabel.setLabelFor(pwf);
// panel.add(passwordLabel);
// panel.add(pwf);
// int choice = JOptionPane.showConfirmDialog(guiInstance.getMainFrame(), panel,
// JMeterUtils.getResString("ssl_pass_prompt"), JOptionPane.OK_CANCEL_OPTION,
// JOptionPane.PLAIN_MESSAGE);
// if (choice == JOptionPane.OK_OPTION) {
// char[] pwchars = pwf.getPassword();
// this.defaultpw = new String(pwchars);
// Arrays.fill(pwchars, '*');
// }
// System.setProperty(KEY_STORE_PASSWORD, this.defaultpw);
// password = this.defaultpw;
// }
} else {
log.warn("No password provided, and no GUI present so cannot prompt");
}
return password;
}
/**
* Opens and initializes the TrustStore.
*
* There are 3 possibilities:
* <ul>
* <li>no truststore name provided, in which case the default Java truststore
* should be used</li>
* <li>truststore name is provided, and loads OK</li>
* <li>truststore name is provided, but is not found or does not load OK, in
* which case an empty
* truststore is created</li>
* </ul>
* If the KeyStore object cannot be created, then this is currently treated the
* same as if no truststore name was provided.
*
* @return
* {@code null} when Java truststore should be used.
* Otherwise the truststore, which may be empty if the file could not be
* loaded.
*
*/
protected KeyStore getTrustStore() {
if (!truststoreLoaded) {
truststoreLoaded=true;// we've tried ...
String fileName = System.getProperty(SSL_TRUST_STORE);
if (fileName == null) {
return null;
}
log.info("TrustStore Location: {}", fileName);
try {
this.trustStore = KeyStore.getInstance("JKS");
log.info("TrustStore created OK, Type: JKS");
} catch (Exception e) {
this.trustStore = null;
throw new RuntimeException("Problem creating truststore: "+e.getMessage(), e);
}
try {
File initStore = new File(fileName);
if (initStore.exists()) {
try (InputStream fis = new FileInputStream(initStore)) {
this.trustStore.load(fis, null);
log.info("Truststore loaded OK from file");
}
} else {
log.warn("Truststore file not found, loading empty truststore");
this.trustStore.load(null, null);
}
} catch (Exception e) {
throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e);
}
}
return this.trustStore;
}
/**
* Protected Constructor to remove the possibility of directly instantiating
* this object. Create the SSLContext, and wrap all the X509KeyManagers with
* our X509KeyManager so that we can choose our alias.
*/
protected SSLManager() {
}
/**
* Static accessor for the SSLManager object. The SSLManager is a singleton.
*
* @return the singleton {@link SSLManager}
*/
public static synchronized SSLManager getInstance() {
if (null == SSLManager.manager) {
SSLManager.manager = new JsseSSLManager(null);
}
return SSLManager.manager;
}
/**
* Test whether SSL is supported or not.
*
* @return flag whether SSL is supported
*/
public static boolean isSSLSupported() {
return SSLManager.IS_SSL_SUPPORTED;
}
/**
* Configure Keystore
*
* @param preload
* flag whether the keystore should be opened within this method,
* or the opening should be delayed
* @param startIndex
* first index to consider for a key
* @param endIndex
* last index to consider for a key
* @param clientCertAliasVarName
* name of the default key, if empty the first key will be used
* as default key
*/
public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName,InputStream is,String password) {
this.keystoreAliasStartIndex = startIndex;
this.keystoreAliasEndIndex = endIndex;
this.clientCertAliasVarName = clientCertAliasVarName;
if(preload) {
keyStore = getKeyStore(is,password);
}
}
/**
* Destroy Keystore
*/
public synchronized void destroyKeystore() {
keyStore=null;
}
}

View File

@ -19,6 +19,7 @@
<module>domain</module>
<module>sdk</module>
<module>xpack-interface</module>
<module>jmeter</module>
</modules>
<build>