fix(接口自动化): 修复csv 并行取值问题
This commit is contained in:
parent
00f5e6f1f7
commit
6bdb0e7386
|
@ -205,8 +205,8 @@ public abstract class MsTestElement {
|
||||||
csvDataSet.setProperty("filename", BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName());
|
csvDataSet.setProperty("filename", BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName());
|
||||||
}
|
}
|
||||||
csvDataSet.setIgnoreFirstLine(false);
|
csvDataSet.setIgnoreFirstLine(false);
|
||||||
csvDataSet.setRecycle(true);
|
// csvDataSet.setProperty("quotedData",true);
|
||||||
//csvDataSet.setProperty("shareMode","shareMode.group");
|
csvDataSet.setProperty("shareMode","shareMode.group");
|
||||||
csvDataSet.setProperty("recycle", true);
|
csvDataSet.setProperty("recycle", true);
|
||||||
csvDataSet.setProperty("delimiter", item.getDelimiter());
|
csvDataSet.setProperty("delimiter", item.getDelimiter());
|
||||||
csvDataSet.setComment(StringUtils.isEmpty(item.getDescription()) ? "" : item.getDescription());
|
csvDataSet.setComment(StringUtils.isEmpty(item.getDescription()) ? "" : item.getDescription());
|
||||||
|
|
|
@ -61,7 +61,6 @@ import java.util.*;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public class ApiAutomationService {
|
public class ApiAutomationService {
|
||||||
|
@ -785,7 +784,7 @@ public class ApiAutomationService {
|
||||||
batchMapper.insert(report);
|
batchMapper.insert(report);
|
||||||
|
|
||||||
// 调用执行方法
|
// 调用执行方法
|
||||||
// jMeterService.runDefinition(report.getId(), hashTree, request.getReportId(), request.getRunMode());
|
//jMeterService.runDefinition(report.getId(), hashTree, request.getReportId(), request.getRunMode());
|
||||||
map.put(report.getId(), hashTree);
|
map.put(report.getId(), hashTree);
|
||||||
// 重置报告ID
|
// 重置报告ID
|
||||||
reportId = UUID.randomUUID().toString();
|
reportId = UUID.randomUUID().toString();
|
||||||
|
@ -794,6 +793,7 @@ public class ApiAutomationService {
|
||||||
// 开始执行
|
// 开始执行
|
||||||
ExecutorService executorService = Executors.newFixedThreadPool(map.size());
|
ExecutorService executorService = Executors.newFixedThreadPool(map.size());
|
||||||
for (String key : map.keySet()) {
|
for (String key : map.keySet()) {
|
||||||
|
// jMeterService.runDefinition(key, map.get(key), request.getReportId(), request.getRunMode());
|
||||||
executorService.submit(new ParallelExecTask(jMeterService, key, map.get(key), request));
|
executorService.submit(new ParallelExecTask(jMeterService, key, map.get(key), request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1079,7 +1079,6 @@ public class ApiAutomationService {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
MSException.throwException(e.getMessage());
|
MSException.throwException(e.getMessage());
|
||||||
}
|
}
|
||||||
System.out.println(request.getTestElement().getJmx(hashTree));
|
|
||||||
|
|
||||||
// 调用执行方法
|
// 调用执行方法
|
||||||
APIScenarioReportResult report = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
|
APIScenarioReportResult report = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
|
||||||
|
|
|
@ -163,15 +163,16 @@ public class ApiTestCaseService {
|
||||||
public ApiTestCaseWithBLOBs get(String id) {
|
public ApiTestCaseWithBLOBs get(String id) {
|
||||||
// ApiTestCaseWithBLOBs returnBlobs = apiTestCaseMapper.selectByPrimaryKey(id);
|
// ApiTestCaseWithBLOBs returnBlobs = apiTestCaseMapper.selectByPrimaryKey(id);
|
||||||
ApiTestCaseInfo model = extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id);
|
ApiTestCaseInfo model = extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id);
|
||||||
if(model != null ){
|
if (model != null) {
|
||||||
if(StringUtils.equalsIgnoreCase(model.getApiMethod(),"esb")){
|
if (StringUtils.equalsIgnoreCase(model.getApiMethod(), "esb")) {
|
||||||
esbApiParamService.handleApiEsbParams(model);
|
esbApiParamService.handleApiEsbParams(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
public ApiTestCaseInfo getResult(String id){
|
|
||||||
return extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id);
|
public ApiTestCaseInfo getResult(String id) {
|
||||||
|
return extApiTestCaseMapper.selectApiCaseInfoByPrimaryKey(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiTestCase create(SaveApiTestCaseRequest request, List<MultipartFile> bodyFiles) {
|
public ApiTestCase create(SaveApiTestCaseRequest request, List<MultipartFile> bodyFiles) {
|
||||||
|
@ -278,7 +279,11 @@ public class ApiTestCaseService {
|
||||||
test.setPriority(request.getPriority());
|
test.setPriority(request.getPriority());
|
||||||
test.setUpdateTime(System.currentTimeMillis());
|
test.setUpdateTime(System.currentTimeMillis());
|
||||||
test.setDescription(request.getDescription());
|
test.setDescription(request.getDescription());
|
||||||
test.setTags(request.getTags());
|
if (StringUtils.equals("[]", request.getTags())) {
|
||||||
|
test.setTags(null);
|
||||||
|
} else {
|
||||||
|
test.setTags(request.getTags());
|
||||||
|
}
|
||||||
apiTestCaseMapper.updateByPrimaryKeySelective(test);
|
apiTestCaseMapper.updateByPrimaryKeySelective(test);
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
@ -304,7 +309,11 @@ public class ApiTestCaseService {
|
||||||
test.setUpdateTime(System.currentTimeMillis());
|
test.setUpdateTime(System.currentTimeMillis());
|
||||||
test.setDescription(request.getDescription());
|
test.setDescription(request.getDescription());
|
||||||
test.setNum(getNextNum(request.getApiDefinitionId()));
|
test.setNum(getNextNum(request.getApiDefinitionId()));
|
||||||
test.setTags(request.getTags());
|
if (StringUtils.equals("[]", request.getTags())) {
|
||||||
|
test.setTags(null);
|
||||||
|
} else {
|
||||||
|
test.setTags(request.getTags());
|
||||||
|
}
|
||||||
apiTestCaseMapper.insert(test);
|
apiTestCaseMapper.insert(test);
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
@ -476,7 +485,7 @@ public class ApiTestCaseService {
|
||||||
public Map<String, String> getRequest(ApiTestCaseRequest request) {
|
public Map<String, String> getRequest(ApiTestCaseRequest request) {
|
||||||
List<ApiTestCaseInfo> list = extApiTestCaseMapper.getRequest(request);
|
List<ApiTestCaseInfo> list = extApiTestCaseMapper.getRequest(request);
|
||||||
for (ApiTestCaseInfo model : list) {
|
for (ApiTestCaseInfo model : list) {
|
||||||
if(StringUtils.equalsIgnoreCase(model.getApiMethod(),"esb")){
|
if (StringUtils.equalsIgnoreCase(model.getApiMethod(), "esb")) {
|
||||||
esbApiParamService.handleApiEsbParams(model);
|
esbApiParamService.handleApiEsbParams(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
/*
|
||||||
|
* 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.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.
|
||||||
|
* <p>
|
||||||
|
* The iterationStart() method is used to set up each set of values.
|
||||||
|
* <p>
|
||||||
|
* By default, the same file is shared between all threads
|
||||||
|
* (and other thread groups, if they use the same file name).
|
||||||
|
* <p>
|
||||||
|
* 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>
|
||||||
|
* <p>
|
||||||
|
* 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 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) {
|
||||||
|
log.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
|
||||||
|
log.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();
|
||||||
|
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);
|
||||||
|
switch (modeInt) {
|
||||||
|
case CSVDataSetBeanInfo.SHARE_ALL:
|
||||||
|
this.alias = alias;
|
||||||
|
break;
|
||||||
|
case CSVDataSetBeanInfo.SHARE_GROUP:
|
||||||
|
this.alias = alias + "@" + System.identityHashCode(context.getThreadGroup());
|
||||||
|
break;
|
||||||
|
case CSVDataSetBeanInfo.SHARE_THREAD:
|
||||||
|
this.alias = alias + "@" + 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,582 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 org.apache.commons.io.input.BOMInputStream;
|
|
||||||
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.ArrayDeque;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
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 ){
|
|
||||||
this.reserveFile(filename);
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e50f0463826ac4d7837ea3a237333827774a1b19
|
Subproject commit e7709b9a340394e78610b91105b2cec0f1b8289d
|
Loading…
Reference in New Issue