feat(接口测试): 添加提取变量的结果数据展示
This commit is contained in:
parent
c62a403798
commit
3476c93bea
|
@ -158,6 +158,20 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
||||||
responseResult.setResponseTime(result.getTime());
|
responseResult.setResponseTime(result.getTime());
|
||||||
responseResult.setResponseMessage(result.getResponseMessage());
|
responseResult.setResponseMessage(result.getResponseMessage());
|
||||||
|
|
||||||
|
if (result.getVars() != null && !result.getVars().entrySet().isEmpty()) {
|
||||||
|
List<String> vars = new LinkedList<>();
|
||||||
|
result.getVars().entrySet().parallelStream().reduce(vars, (first, second) -> {
|
||||||
|
first.add(second.getKey() + ":" + second.getValue());
|
||||||
|
return first;
|
||||||
|
}, (first, second) -> {
|
||||||
|
if (first == second) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
first.addAll(second);
|
||||||
|
return first;
|
||||||
|
});
|
||||||
|
responseResult.setVars(StringUtils.join(vars, "\n"));
|
||||||
|
}
|
||||||
for (AssertionResult assertionResult : result.getAssertionResults()) {
|
for (AssertionResult assertionResult : result.getAssertionResults()) {
|
||||||
ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult);
|
ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult);
|
||||||
if (responseAssertionResult.isPass()) {
|
if (responseAssertionResult.isPass()) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ public class ResponseResult {
|
||||||
|
|
||||||
private String body;
|
private String body;
|
||||||
|
|
||||||
|
private String vars;
|
||||||
|
|
||||||
private final List<ResponseAssertionResult> assertions = new ArrayList<>();
|
private final List<ResponseAssertionResult> assertions = new ArrayList<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,526 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.extractor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
|
import org.apache.jmeter.processor.PostProcessor;
|
||||||
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
|
import org.apache.jmeter.testelement.AbstractScopedTestElement;
|
||||||
|
import org.apache.jmeter.testelement.property.IntegerProperty;
|
||||||
|
import org.apache.jmeter.threads.JMeterContext;
|
||||||
|
import org.apache.jmeter.threads.JMeterVariables;
|
||||||
|
import org.apache.jmeter.util.Document;
|
||||||
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
|
import org.apache.oro.text.MalformedCachePatternException;
|
||||||
|
import org.apache.oro.text.regex.MatchResult;
|
||||||
|
import org.apache.oro.text.regex.Pattern;
|
||||||
|
import org.apache.oro.text.regex.PatternMatcher;
|
||||||
|
import org.apache.oro.text.regex.PatternMatcherInput;
|
||||||
|
import org.apache.oro.text.regex.Perl5Compiler;
|
||||||
|
import org.apache.oro.text.regex.Perl5Matcher;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class RegexExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 242L;
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RegexExtractor.class);
|
||||||
|
|
||||||
|
// What to match against. N.B. do not change the string value or test plans will break!
|
||||||
|
private static final String MATCH_AGAINST = "RegexExtractor.useHeaders"; // $NON-NLS-1$
|
||||||
|
/*
|
||||||
|
* Permissible values:
|
||||||
|
* true - match against headers
|
||||||
|
* false or absent - match against body (this was the original default)
|
||||||
|
* URL - match against URL
|
||||||
|
* These are passed to the setUseField() method
|
||||||
|
*
|
||||||
|
* Do not change these values!
|
||||||
|
*/
|
||||||
|
public static final String USE_HDRS = "true"; // $NON-NLS-1$
|
||||||
|
public static final String USE_REQUEST_HDRS = "request_headers"; // $NON-NLS-1$
|
||||||
|
public static final String USE_BODY = "false"; // $NON-NLS-1$
|
||||||
|
public static final String USE_BODY_UNESCAPED = "unescaped"; // $NON-NLS-1$
|
||||||
|
public static final String USE_BODY_AS_DOCUMENT = "as_document"; // $NON-NLS-1$
|
||||||
|
public static final String USE_URL = "URL"; // $NON-NLS-1$
|
||||||
|
public static final String USE_CODE = "code"; // $NON-NLS-1$
|
||||||
|
public static final String USE_MESSAGE = "message"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String REGEX_PROP = "RegexExtractor.regex"; // $NON-NLS-1$
|
||||||
|
private static final String REFNAME_PROP = "RegexExtractor.refname"; // $NON-NLS-1$
|
||||||
|
private static final String MATCH_NUMBER_PROP = "RegexExtractor.match_number"; // $NON-NLS-1$
|
||||||
|
private static final String DEFAULT_PROP = "RegexExtractor.default"; // $NON-NLS-1$
|
||||||
|
private static final String DEFAULT_EMPTY_VALUE_PROP = "RegexExtractor.default_empty_value"; // $NON-NLS-1$
|
||||||
|
private static final String TEMPLATE_PROP = "RegexExtractor.template"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String UNDERSCORE = "_"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final boolean DEFAULT_VALUE_FOR_DEFAULT_EMPTY_VALUE = false;
|
||||||
|
|
||||||
|
private transient List<Object> template;
|
||||||
|
|
||||||
|
private JMeterVariables regexVars;
|
||||||
|
/**
|
||||||
|
* Parses the response data using regular expressions and saving the results
|
||||||
|
* into variables for use later in the test.
|
||||||
|
*
|
||||||
|
* @see org.apache.jmeter.processor.PostProcessor#process()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
initTemplate();
|
||||||
|
regexVars = new JMeterVariables();
|
||||||
|
JMeterContext context = getThreadContext();
|
||||||
|
SampleResult previousResult = context.getPreviousResult();
|
||||||
|
if (previousResult == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug("RegexExtractor processing result");
|
||||||
|
|
||||||
|
// Fetch some variables
|
||||||
|
JMeterVariables vars = context.getVariables();
|
||||||
|
String refName = getRefName();
|
||||||
|
int matchNumber = getMatchNumber();
|
||||||
|
|
||||||
|
final String defaultValue = getDefaultValue();
|
||||||
|
if (defaultValue.length() > 0 || isEmptyDefaultValue()) {// Only replace default if it is provided or empty default value is explicitly requested
|
||||||
|
vars.put(refName, defaultValue);
|
||||||
|
regexVars.put(refName, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Perl5Matcher matcher = JMeterUtils.getMatcher();
|
||||||
|
String regex = getRegex();
|
||||||
|
Pattern pattern = null;
|
||||||
|
try {
|
||||||
|
pattern = JMeterUtils.getPatternCache().getPattern(regex, Perl5Compiler.READ_ONLY_MASK);
|
||||||
|
List<MatchResult> matches = processMatches(pattern, regex, previousResult, matchNumber, vars);
|
||||||
|
int prevCount = 0;
|
||||||
|
String prevString = vars.get(refName + REF_MATCH_NR);
|
||||||
|
if (prevString != null) {
|
||||||
|
vars.remove(refName + REF_MATCH_NR);// ensure old value is not left defined
|
||||||
|
try {
|
||||||
|
prevCount = Integer.parseInt(prevString);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
log.warn("Could not parse number: '{}'", prevString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int matchCount = 0;// Number of refName_n variable sets to keep
|
||||||
|
try {
|
||||||
|
MatchResult match;
|
||||||
|
if (matchNumber >= 0) {// Original match behaviour
|
||||||
|
match = getCorrectMatch(matches, matchNumber);
|
||||||
|
if (match != null) {
|
||||||
|
vars.put(refName, generateResult(match));
|
||||||
|
regexVars.put(refName, generateResult(match));
|
||||||
|
|
||||||
|
saveGroups(vars, refName, match);
|
||||||
|
} else {
|
||||||
|
// refname has already been set to the default (if present)
|
||||||
|
removeGroups(vars, refName);
|
||||||
|
}
|
||||||
|
} else // < 0 means we save all the matches
|
||||||
|
{
|
||||||
|
removeGroups(vars, refName); // remove any single matches
|
||||||
|
matchCount = matches.size();
|
||||||
|
vars.put(refName + REF_MATCH_NR, Integer.toString(matchCount));// Save the count
|
||||||
|
regexVars.put(refName + REF_MATCH_NR, Integer.toString(matchCount));// Save the count
|
||||||
|
|
||||||
|
for (int i = 1; i <= matchCount; i++) {
|
||||||
|
match = getCorrectMatch(matches, i);
|
||||||
|
if (match != null) {
|
||||||
|
final String refName_n = refName + UNDERSCORE + i;
|
||||||
|
vars.put(refName_n, generateResult(match));
|
||||||
|
regexVars.put(refName_n, generateResult(match));
|
||||||
|
|
||||||
|
saveGroups(vars, refName_n, match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove any left-over variables
|
||||||
|
for (int i = matchCount + 1; i <= prevCount; i++) {
|
||||||
|
final String refName_n = refName + UNDERSCORE + i;
|
||||||
|
vars.remove(refName_n);
|
||||||
|
removeGroups(vars, refName_n);
|
||||||
|
}
|
||||||
|
previousResult.addVars(regexVars);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.warn("Error while generating result");
|
||||||
|
}
|
||||||
|
} catch (MalformedCachePatternException e) {
|
||||||
|
log.error("Error in pattern: '{}'", regex);
|
||||||
|
} finally {
|
||||||
|
JMeterUtils.clearMatcherMemory(matcher, pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getInputString(SampleResult result) {
|
||||||
|
String inputString = useUrl() ? result.getUrlAsString() // Bug 39707
|
||||||
|
: useHeaders() ? result.getResponseHeaders()
|
||||||
|
: useRequestHeaders() ? result.getRequestHeaders()
|
||||||
|
: useCode() ? result.getResponseCode() // Bug 43451
|
||||||
|
: useMessage() ? result.getResponseMessage() // Bug 43451
|
||||||
|
: useUnescapedBody() ? StringEscapeUtils.unescapeHtml4(result.getResponseDataAsString())
|
||||||
|
: useBodyAsDocument() ? Document.getTextFromDocument(result.getResponseData())
|
||||||
|
: result.getResponseDataAsString() // Bug 36898
|
||||||
|
;
|
||||||
|
log.debug("Input = '{}'", inputString);
|
||||||
|
return inputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MatchResult> processMatches(Pattern pattern, String regex, SampleResult result, int matchNumber, JMeterVariables vars) {
|
||||||
|
log.debug("Regex = '{}'", regex);
|
||||||
|
|
||||||
|
Perl5Matcher matcher = JMeterUtils.getMatcher();
|
||||||
|
List<MatchResult> matches = new ArrayList<>();
|
||||||
|
int found = 0;
|
||||||
|
|
||||||
|
if (isScopeVariable()) {
|
||||||
|
String inputString = vars.get(getVariableName());
|
||||||
|
if (inputString == null) {
|
||||||
|
if (log.isWarnEnabled()) {
|
||||||
|
log.warn("No variable '{}' found to process by RegexExtractor '{}', skipping processing",
|
||||||
|
getVariableName(), getName());
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
matchStrings(matchNumber, matcher, pattern, matches, found,
|
||||||
|
inputString);
|
||||||
|
} else {
|
||||||
|
List<SampleResult> sampleList = getSampleList(result);
|
||||||
|
for (SampleResult sr : sampleList) {
|
||||||
|
String inputString = getInputString(sr);
|
||||||
|
found = matchStrings(matchNumber, matcher, pattern, matches, found,
|
||||||
|
inputString);
|
||||||
|
if (matchNumber > 0 && found == matchNumber) {// no need to process further
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int matchStrings(int matchNumber, Perl5Matcher matcher,
|
||||||
|
Pattern pattern, List<MatchResult> matches, int found,
|
||||||
|
String inputString) {
|
||||||
|
PatternMatcherInput input = new PatternMatcherInput(inputString);
|
||||||
|
while (matchNumber <= 0 || found != matchNumber) {
|
||||||
|
if (matcher.contains(input, pattern)) {
|
||||||
|
log.debug("RegexExtractor: Match found!");
|
||||||
|
matches.add(matcher.getMatch());
|
||||||
|
found++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the variables:<br/>
|
||||||
|
* basename_gn, where n=0...# of groups<br/>
|
||||||
|
* basename_g = number of groups (apart from g0)
|
||||||
|
*/
|
||||||
|
private void saveGroups(JMeterVariables vars, String basename, MatchResult match) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append(basename);
|
||||||
|
buf.append("_g"); // $NON-NLS-1$
|
||||||
|
int pfxlen = buf.length();
|
||||||
|
String prevString = vars.get(buf.toString());
|
||||||
|
int previous = 0;
|
||||||
|
if (prevString != null) {
|
||||||
|
try {
|
||||||
|
previous = Integer.parseInt(prevString);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
log.warn("Could not parse number: '{}'.", prevString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Note: match.groups() includes group 0
|
||||||
|
final int groups = match.groups();
|
||||||
|
for (int x = 0; x < groups; x++) {
|
||||||
|
buf.append(x);
|
||||||
|
vars.put(buf.toString(), match.group(x));
|
||||||
|
buf.setLength(pfxlen);
|
||||||
|
}
|
||||||
|
vars.put(buf.toString(), Integer.toString(groups - 1));
|
||||||
|
for (int i = groups; i <= previous; i++) {
|
||||||
|
buf.append(i);
|
||||||
|
vars.remove(buf.toString());// remove the remaining _gn vars
|
||||||
|
buf.setLength(pfxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the variables:<br/>
|
||||||
|
* basename_gn, where n=0...# of groups<br/>
|
||||||
|
* basename_g = number of groups (apart from g0)
|
||||||
|
*/
|
||||||
|
private void removeGroups(JMeterVariables vars, String basename) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append(basename);
|
||||||
|
buf.append("_g"); // $NON-NLS-1$
|
||||||
|
int pfxlen = buf.length();
|
||||||
|
// How many groups are there?
|
||||||
|
int groups;
|
||||||
|
try {
|
||||||
|
groups = Integer.parseInt(vars.get(buf.toString()));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
groups = 0;
|
||||||
|
}
|
||||||
|
vars.remove(buf.toString());// Remove the group count
|
||||||
|
for (int i = 0; i <= groups; i++) {
|
||||||
|
buf.append(i);
|
||||||
|
vars.remove(buf.toString());// remove the g0,g1...gn vars
|
||||||
|
buf.setLength(pfxlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateResult(MatchResult match) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (Object obj : template) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("RegexExtractor: Template piece {} ({})", obj, obj.getClass());
|
||||||
|
}
|
||||||
|
if (obj instanceof Integer) {
|
||||||
|
result.append(match.group((Integer) obj));
|
||||||
|
} else {
|
||||||
|
result.append(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("Regex Extractor result = '{}'", result);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTemplate() {
|
||||||
|
if (template != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Contains Strings and Integers
|
||||||
|
List<Object> combined = new ArrayList<>();
|
||||||
|
String rawTemplate = getTemplate();
|
||||||
|
PatternMatcher matcher = JMeterUtils.getMatcher();
|
||||||
|
Pattern templatePattern = JMeterUtils.getPatternCache().getPattern("\\$(\\d+)\\$" // $NON-NLS-1$
|
||||||
|
, Perl5Compiler.READ_ONLY_MASK
|
||||||
|
& Perl5Compiler.SINGLELINE_MASK);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Pattern = '{}', template = '{}'", templatePattern.getPattern(), rawTemplate);
|
||||||
|
}
|
||||||
|
int beginOffset = 0;
|
||||||
|
MatchResult currentResult;
|
||||||
|
PatternMatcherInput pinput = new PatternMatcherInput(rawTemplate);
|
||||||
|
while (matcher.contains(pinput, templatePattern)) {
|
||||||
|
currentResult = matcher.getMatch();
|
||||||
|
final int beginMatch = currentResult.beginOffset(0);
|
||||||
|
if (beginMatch > beginOffset) { // string is not empty
|
||||||
|
combined.add(rawTemplate.substring(beginOffset, beginMatch));
|
||||||
|
}
|
||||||
|
combined.add(Integer.valueOf(currentResult.group(1)));// add match as Integer
|
||||||
|
beginOffset = currentResult.endOffset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beginOffset < rawTemplate.length()) { // trailing string is not empty
|
||||||
|
combined.add(rawTemplate.substring(beginOffset, rawTemplate.length()));
|
||||||
|
}
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Template item count: {}", combined.size());
|
||||||
|
int i = 0;
|
||||||
|
for (Object o : combined) {
|
||||||
|
log.debug("Template item-{}: {} '{}'", i++, o.getClass(), o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template = combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grab the appropriate result from the list.
|
||||||
|
*
|
||||||
|
* @param matches list of matches
|
||||||
|
* @param entry the entry number in the list
|
||||||
|
* @return MatchResult
|
||||||
|
*/
|
||||||
|
private MatchResult getCorrectMatch(List<MatchResult> matches, int entry) {
|
||||||
|
int matchSize = matches.size();
|
||||||
|
|
||||||
|
if (matchSize <= 0 || entry > matchSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry == 0) // Random match
|
||||||
|
{
|
||||||
|
return matches.get(JMeterUtils.getRandomInt(matchSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches.get(entry - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the regex to be used
|
||||||
|
*
|
||||||
|
* @param regex The string representation of the regex
|
||||||
|
*/
|
||||||
|
public void setRegex(String regex) {
|
||||||
|
setProperty(REGEX_PROP, regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the regex which is to be used
|
||||||
|
*
|
||||||
|
* @return string representing the regex
|
||||||
|
*/
|
||||||
|
public String getRegex() {
|
||||||
|
return getPropertyAsString(REGEX_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the prefix name of the variable to be used to store the regex matches
|
||||||
|
*
|
||||||
|
* @param refName prefix of the variables to be used
|
||||||
|
*/
|
||||||
|
public void setRefName(String refName) {
|
||||||
|
setProperty(REFNAME_PROP, refName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the prefix name of the variable to be used to store the regex matches
|
||||||
|
*
|
||||||
|
* @return The prefix of the variables to be used
|
||||||
|
*/
|
||||||
|
public String getRefName() {
|
||||||
|
return getPropertyAsString(REFNAME_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set which Match to use. This can be any positive number, indicating the
|
||||||
|
* exact match to use, or <code>0</code>, which is interpreted as meaning
|
||||||
|
* random.
|
||||||
|
*
|
||||||
|
* @param matchNumber The number of the match to be used, or <code>0</code> if a
|
||||||
|
* random match should be used.
|
||||||
|
*/
|
||||||
|
public void setMatchNumber(int matchNumber) {
|
||||||
|
setProperty(new IntegerProperty(MATCH_NUMBER_PROP, matchNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMatchNumber(String matchNumber) {
|
||||||
|
setProperty(MATCH_NUMBER_PROP, matchNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMatchNumber() {
|
||||||
|
return getPropertyAsInt(MATCH_NUMBER_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMatchNumberAsString() {
|
||||||
|
return getPropertyAsString(MATCH_NUMBER_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the variable if no matches are found
|
||||||
|
*
|
||||||
|
* @param defaultValue The default value for the variable
|
||||||
|
*/
|
||||||
|
public void setDefaultValue(String defaultValue) {
|
||||||
|
setProperty(DEFAULT_PROP, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default value to "" value when if it's empty
|
||||||
|
*
|
||||||
|
* @param defaultEmptyValue The default value for the variable
|
||||||
|
*/
|
||||||
|
public void setDefaultEmptyValue(boolean defaultEmptyValue) {
|
||||||
|
setProperty(DEFAULT_EMPTY_VALUE_PROP, defaultEmptyValue, DEFAULT_VALUE_FOR_DEFAULT_EMPTY_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default value for the variable, which should be used, if no
|
||||||
|
* matches are found
|
||||||
|
*
|
||||||
|
* @return The default value for the variable
|
||||||
|
*/
|
||||||
|
public String getDefaultValue() {
|
||||||
|
return getPropertyAsString(DEFAULT_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do we set default value to "" value when if it's empty
|
||||||
|
*
|
||||||
|
* @return true if we should set default value to "" if variable cannot be extracted
|
||||||
|
*/
|
||||||
|
public boolean isEmptyDefaultValue() {
|
||||||
|
return getPropertyAsBoolean(DEFAULT_EMPTY_VALUE_PROP, DEFAULT_VALUE_FOR_DEFAULT_EMPTY_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTemplate(String template) {
|
||||||
|
setProperty(TEMPLATE_PROP, template);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTemplate() {
|
||||||
|
return getPropertyAsString(TEMPLATE_PROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useHeaders() {
|
||||||
|
return USE_HDRS.equalsIgnoreCase(getPropertyAsString(MATCH_AGAINST));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useRequestHeaders() {
|
||||||
|
return USE_REQUEST_HDRS.equalsIgnoreCase(getPropertyAsString(MATCH_AGAINST));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for property not yet being set (probably only applies to Test cases)
|
||||||
|
public boolean useBody() {
|
||||||
|
String prop = getPropertyAsString(MATCH_AGAINST);
|
||||||
|
return prop.length() == 0 || USE_BODY.equalsIgnoreCase(prop);// $NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useUnescapedBody() {
|
||||||
|
String prop = getPropertyAsString(MATCH_AGAINST);
|
||||||
|
return USE_BODY_UNESCAPED.equalsIgnoreCase(prop);// $NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useBodyAsDocument() {
|
||||||
|
String prop = getPropertyAsString(MATCH_AGAINST);
|
||||||
|
return USE_BODY_AS_DOCUMENT.equalsIgnoreCase(prop);// $NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useUrl() {
|
||||||
|
String prop = getPropertyAsString(MATCH_AGAINST);
|
||||||
|
return USE_URL.equalsIgnoreCase(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useCode() {
|
||||||
|
String prop = getPropertyAsString(MATCH_AGAINST);
|
||||||
|
return USE_CODE.equalsIgnoreCase(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useMessage() {
|
||||||
|
String prop = getPropertyAsString(MATCH_AGAINST);
|
||||||
|
return USE_MESSAGE.equalsIgnoreCase(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseField(String actionCommand) {
|
||||||
|
setProperty(MATCH_AGAINST, actionCommand);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,283 @@
|
||||||
|
/*
|
||||||
|
* 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.extractor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.stream.FactoryConfigurationError;
|
||||||
|
|
||||||
|
import org.apache.jmeter.assertions.AssertionResult;
|
||||||
|
import org.apache.jmeter.processor.PostProcessor;
|
||||||
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
|
import org.apache.jmeter.testelement.AbstractScopedTestElement;
|
||||||
|
import org.apache.jmeter.testelement.property.IntegerProperty;
|
||||||
|
import org.apache.jmeter.threads.JMeterContext;
|
||||||
|
import org.apache.jmeter.threads.JMeterVariables;
|
||||||
|
import org.apache.jmeter.util.XPathUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.sf.saxon.s9api.SaxonApiException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts text from (X)HTML response using XPath query language
|
||||||
|
* Example XPath queries:
|
||||||
|
* <dl>
|
||||||
|
* <dt>/html/head/title</dt>
|
||||||
|
* <dd>extracts Title from HTML response</dd>
|
||||||
|
* <dt>//form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value
|
||||||
|
* <dd>extracts value attribute of option element that match text 'Czech Republic'
|
||||||
|
* inside of select element with name attribute 'country' inside of
|
||||||
|
* form with name attribute 'countryForm'</dd>
|
||||||
|
* <dt>//head</dt>
|
||||||
|
* <dd>extracts the XML fragment for head node.</dd>
|
||||||
|
* <dt>//head/text()</dt>
|
||||||
|
* <dd>extracts the text content for head node.</dd>
|
||||||
|
* </dl>
|
||||||
|
* see org.apache.jmeter.extractor.TestXPathExtractor for unit tests
|
||||||
|
*/
|
||||||
|
public class XPath2Extractor
|
||||||
|
extends AbstractScopedTestElement
|
||||||
|
implements PostProcessor, Serializable {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(XPath2Extractor.class);
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 242L;
|
||||||
|
|
||||||
|
private static final int DEFAULT_VALUE = 0;
|
||||||
|
public static final String DEFAULT_VALUE_AS_STRING = Integer.toString(DEFAULT_VALUE);
|
||||||
|
|
||||||
|
private static final String REF_MATCH_NR = "matchNr"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
//+ JMX file attributes
|
||||||
|
private static final String XPATH_QUERY = "XPathExtractor2.xpathQuery"; // $NON-NLS-1$
|
||||||
|
private static final String REFNAME = "XPathExtractor2.refname"; // $NON-NLS-1$
|
||||||
|
private static final String DEFAULT = "XPathExtractor2.default"; // $NON-NLS-1$
|
||||||
|
private static final String FRAGMENT = "XPathExtractor2.fragment"; // $NON-NLS-1$
|
||||||
|
private static final String NAMESPACES = "XPathExtractor2.namespaces"; // $NON-NLS-1$
|
||||||
|
private static final String MATCH_NUMBER = "XPathExtractor2.matchNumber"; // $NON-NLS-1$
|
||||||
|
private JMeterVariables regexVars;
|
||||||
|
|
||||||
|
//- JMX file attributes
|
||||||
|
|
||||||
|
private String concat(String s1, String s2) {
|
||||||
|
return s1 + "_" + s2; // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
private String concat(String s1, int i) {
|
||||||
|
return s1 + "_" + i; // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the job - extract value from (X)HTML response using XPath Query.
|
||||||
|
* Return value as variable defined by REFNAME. Returns DEFAULT value
|
||||||
|
* if not found.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
JMeterContext context = getThreadContext();
|
||||||
|
final SampleResult previousResult = context.getPreviousResult();
|
||||||
|
if (previousResult == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JMeterVariables vars = context.getVariables();
|
||||||
|
String refName = getRefName();
|
||||||
|
vars.put(refName, getDefaultValue());
|
||||||
|
final String matchNR = concat(refName, REF_MATCH_NR);
|
||||||
|
int prevCount = 0; // number of previous matches
|
||||||
|
try {
|
||||||
|
prevCount = Integer.parseInt(vars.get(matchNR));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.put(matchNR, "0"); // In case parse fails // $NON-NLS-1$
|
||||||
|
vars.remove(concat(refName, "1")); // In case parse fails // $NON-NLS-1$
|
||||||
|
|
||||||
|
int matchNumber = getMatchNumber();
|
||||||
|
List<String> matches = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
if (isScopeVariable()) {
|
||||||
|
String inputString = vars.get(getVariableName());
|
||||||
|
if (inputString != null) {
|
||||||
|
if (inputString.length() > 0) {
|
||||||
|
getValuesForXPath(getXPathQuery(), matches, matchNumber, inputString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (log.isWarnEnabled()) {
|
||||||
|
log.warn("No variable '{}' found to process by XPathExtractor '{}', skipping processing",
|
||||||
|
getVariableName(), getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<SampleResult> samples = getSampleList(previousResult);
|
||||||
|
int size = samples.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
getValuesForXPath(getXPathQuery(), matches, matchNumber, previousResult.getResponseDataAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int matchCount = matches.size();
|
||||||
|
vars.put(matchNR, String.valueOf(matchCount));
|
||||||
|
if (matchCount > 0) {
|
||||||
|
String value = matches.get(0);
|
||||||
|
if (value != null) {
|
||||||
|
vars.put(refName, value);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < matchCount; i++) {
|
||||||
|
value = matches.get(i);
|
||||||
|
if (value != null) {
|
||||||
|
vars.put(concat(refName, i + 1), matches.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vars.remove(concat(refName, matchCount + 1)); // Just in case
|
||||||
|
// Clear any other remaining variables
|
||||||
|
for (int i = matchCount + 2; i <= prevCount; i++) {
|
||||||
|
vars.remove(concat(refName, i));
|
||||||
|
}
|
||||||
|
previousResult.addVars(vars);
|
||||||
|
} catch (Exception e) {// Saxon exception
|
||||||
|
if (log.isWarnEnabled()) {
|
||||||
|
log.warn("Exception while processing '{}', message:{}", getXPathQuery(), e.getMessage());
|
||||||
|
}
|
||||||
|
addAssertionFailure(previousResult, e, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAssertionFailure(final SampleResult previousResult,
|
||||||
|
final Throwable thrown, final boolean setFailed) {
|
||||||
|
AssertionResult ass = new AssertionResult(getName()); // $NON-NLS-1$
|
||||||
|
ass.setFailure(true);
|
||||||
|
ass.setFailureMessage(thrown.getLocalizedMessage() + "\nSee log file for further details.");
|
||||||
|
previousResult.addAssertionResult(ass);
|
||||||
|
if (setFailed) {
|
||||||
|
previousResult.setSuccessful(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*============= object properties ================*/
|
||||||
|
public void setXPathQuery(String val) {
|
||||||
|
setProperty(XPATH_QUERY, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getXPathQuery() {
|
||||||
|
return getPropertyAsString(XPATH_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefName(String refName) {
|
||||||
|
setProperty(REFNAME, refName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRefName() {
|
||||||
|
return getPropertyAsString(REFNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setDefaultValue(String val) {
|
||||||
|
setProperty(DEFAULT, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultValue() {
|
||||||
|
return getPropertyAsString(DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we return fragment as text, rather than text of fragment?
|
||||||
|
*
|
||||||
|
* @return true if we should return fragment rather than text
|
||||||
|
*/
|
||||||
|
public boolean getFragment() {
|
||||||
|
return getPropertyAsBoolean(FRAGMENT, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we return fragment as text, rather than text of fragment?
|
||||||
|
*
|
||||||
|
* @param selected true to return fragment.
|
||||||
|
*/
|
||||||
|
public void setFragment(boolean selected) {
|
||||||
|
setProperty(FRAGMENT, selected, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*================= internal business =================*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract value from String responseData by XPath query.
|
||||||
|
*
|
||||||
|
* @param query the query to execute
|
||||||
|
* @param matchStrings list of matched strings (may include nulls)
|
||||||
|
* @param matchNumber int Match Number
|
||||||
|
* @param responseData String that contains the entire Document
|
||||||
|
* @throws SaxonApiException
|
||||||
|
* @throws FactoryConfigurationError
|
||||||
|
*/
|
||||||
|
private void getValuesForXPath(String query, List<String> matchStrings, int matchNumber, String responseData)
|
||||||
|
throws SaxonApiException, FactoryConfigurationError {
|
||||||
|
XPathUtil.putValuesForXPathInListUsingSaxon(responseData, query, matchStrings, getFragment(), matchNumber, getNamespaces());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set which Match to use. This can be any positive number, indicating the
|
||||||
|
* exact match to use, or <code>0</code>, which is interpreted as meaning random.
|
||||||
|
*
|
||||||
|
* @param matchNumber The number of the match to be used
|
||||||
|
*/
|
||||||
|
public void setMatchNumber(int matchNumber) {
|
||||||
|
setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set which Match to use. This can be any positive number, indicating the
|
||||||
|
* exact match to use, or <code>0</code>, which is interpreted as meaning random.
|
||||||
|
*
|
||||||
|
* @param matchNumber The number of the match to be used
|
||||||
|
*/
|
||||||
|
public void setMatchNumber(String matchNumber) {
|
||||||
|
setProperty(MATCH_NUMBER, matchNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return which Match to use. This can be any positive number, indicating the
|
||||||
|
* exact match to use, or <code>0</code>, which is interpreted as meaning random.
|
||||||
|
*
|
||||||
|
* @return matchNumber The number of the match to be used
|
||||||
|
*/
|
||||||
|
public int getMatchNumber() {
|
||||||
|
return getPropertyAsInt(MATCH_NUMBER, DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return which Match to use. This can be any positive number, indicating the
|
||||||
|
* exact match to use, or <code>0</code>, which is interpreted as meaning random.
|
||||||
|
*
|
||||||
|
* @return matchNumber The number of the match to be used
|
||||||
|
*/
|
||||||
|
public String getMatchNumberAsString() {
|
||||||
|
return getPropertyAsString(MATCH_NUMBER, DEFAULT_VALUE_AS_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNamespaces(String namespaces) {
|
||||||
|
setProperty(NAMESPACES, namespaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNamespaces() {
|
||||||
|
return getPropertyAsString(NAMESPACES);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
/*
|
||||||
|
* 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.extractor.json.jsonpath;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.jmeter.processor.PostProcessor;
|
||||||
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
|
import org.apache.jmeter.testelement.AbstractScopedTestElement;
|
||||||
|
import org.apache.jmeter.testelement.ThreadListener;
|
||||||
|
import org.apache.jmeter.threads.JMeterContext;
|
||||||
|
import org.apache.jmeter.threads.JMeterVariables;
|
||||||
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
|
import org.apache.jorphan.util.JOrphanUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON-PATH based extractor
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
public class JSONPostProcessor
|
||||||
|
extends AbstractScopedTestElement
|
||||||
|
implements Serializable, PostProcessor, ThreadListener {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JSONPostProcessor.class);
|
||||||
|
|
||||||
|
private static final String JSON_PATH_EXPRESSIONS = "JSONPostProcessor.jsonPathExprs"; // $NON-NLS-1$
|
||||||
|
private static final String REFERENCE_NAMES = "JSONPostProcessor.referenceNames"; // $NON-NLS-1$
|
||||||
|
private static final String DEFAULT_VALUES = "JSONPostProcessor.defaultValues"; // $NON-NLS-1$
|
||||||
|
private static final String MATCH_NUMBERS = "JSONPostProcessor.match_numbers"; // $NON-NLS-1$
|
||||||
|
private static final String COMPUTE_CONCATENATION = "JSONPostProcessor.compute_concat"; // $NON-NLS-1$
|
||||||
|
private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$
|
||||||
|
private static final String ALL_SUFFIX = "_ALL"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String JSON_CONCATENATION_SEPARATOR = ","; //$NON-NLS-1$
|
||||||
|
private static final String SEPARATOR = ";"; // $NON-NLS-1$
|
||||||
|
public static final boolean COMPUTE_CONCATENATION_DEFAULT_VALUE = false;
|
||||||
|
|
||||||
|
private static final ThreadLocal<JSONManager> localMatcher = ThreadLocal.withInitial(JSONManager::new);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
JMeterContext context = getThreadContext();
|
||||||
|
JMeterVariables vars = context.getVariables();
|
||||||
|
String jsonResponse = extractJsonResponse(context, vars);
|
||||||
|
String[] refNames = getRefNames().split(SEPARATOR);
|
||||||
|
String[] jsonPathExpressions = getJsonPathExpressions().split(SEPARATOR);
|
||||||
|
String[] defaultValues = getDefaultValues().split(SEPARATOR);
|
||||||
|
int[] matchNumbers = getMatchNumbersAsInt(defaultValues.length);
|
||||||
|
|
||||||
|
validateSameLengthOfArguments(refNames, jsonPathExpressions, defaultValues);
|
||||||
|
|
||||||
|
for (int i = 0; i < jsonPathExpressions.length; i++) {
|
||||||
|
int matchNumber = matchNumbers[i];
|
||||||
|
String currentRefName = refNames[i].trim();
|
||||||
|
String currentJsonPath = jsonPathExpressions[i].trim();
|
||||||
|
clearOldRefVars(vars, currentRefName);
|
||||||
|
try {
|
||||||
|
if (StringUtils.isEmpty(jsonResponse)) {
|
||||||
|
handleEmptyResponse(vars, defaultValues, i, currentRefName);
|
||||||
|
} else {
|
||||||
|
List<Object> extractedValues = localMatcher.get()
|
||||||
|
.extractWithJsonPath(jsonResponse, currentJsonPath);
|
||||||
|
// if no values extracted, default value added
|
||||||
|
if (extractedValues.isEmpty()) {
|
||||||
|
handleEmptyResult(vars, defaultValues, i, matchNumber, currentRefName);
|
||||||
|
} else {
|
||||||
|
handleNonEmptyResult(vars, defaultValues, i, matchNumber, currentRefName, extractedValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SampleResult previousResult = context.getPreviousResult();
|
||||||
|
previousResult.addVars(vars);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// if something wrong, default value added
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.error("Error processing JSON content in {}, message: {}", getName(), e.getLocalizedMessage(), e);
|
||||||
|
} else {
|
||||||
|
log.error("Error processing JSON content in {}, message: {}", getName(), e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
vars.put(currentRefName, defaultValues[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNonEmptyResult(JMeterVariables vars, String[] defaultValues, int i, int matchNumber,
|
||||||
|
String currentRefName, List<Object> extractedValues) {
|
||||||
|
// if more than one value extracted, suffix with "_index"
|
||||||
|
if (extractedValues.size() > 1) {
|
||||||
|
handleListResult(vars, defaultValues, i, matchNumber, currentRefName, extractedValues);
|
||||||
|
} else {
|
||||||
|
// else just one value extracted
|
||||||
|
handleSingleResult(vars, matchNumber, currentRefName, extractedValues);
|
||||||
|
}
|
||||||
|
if (matchNumber != 0) {
|
||||||
|
vars.put(currentRefName + REF_MATCH_NR, Integer.toString(extractedValues.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateSameLengthOfArguments(String[] refNames, String[] jsonPathExpressions,
|
||||||
|
String[] defaultValues) {
|
||||||
|
if (refNames.length != jsonPathExpressions.length ||
|
||||||
|
refNames.length != defaultValues.length) {
|
||||||
|
log.error(
|
||||||
|
"Number of JSON Path variables must match number of default values and json-path expressions,"
|
||||||
|
+ " check you use separator ';' if you have many values"); // $NON-NLS-1$
|
||||||
|
throw new IllegalArgumentException(JMeterUtils
|
||||||
|
.getResString("jsonpp_error_number_arguments_mismatch_error")); // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSingleResult(JMeterVariables vars, final int matchNumber, String currentRefName,
|
||||||
|
List<Object> extractedValues) {
|
||||||
|
String suffix = (matchNumber < 0) ? "_1" : "";
|
||||||
|
placeObjectIntoVars(vars, currentRefName + suffix, extractedValues, 0);
|
||||||
|
if (matchNumber < 0 && getComputeConcatenation()) {
|
||||||
|
vars.put(currentRefName + ALL_SUFFIX, vars.get(currentRefName + suffix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleListResult(JMeterVariables vars, String[] defaultValues, final int i, final int matchNumber,
|
||||||
|
String currentRefName, List<Object> extractedValues) {
|
||||||
|
if (matchNumber < 0) {
|
||||||
|
// Extract all
|
||||||
|
int index = 1;
|
||||||
|
StringBuilder concat =
|
||||||
|
new StringBuilder(getComputeConcatenation()
|
||||||
|
? extractedValues.size() * 20
|
||||||
|
: 1);
|
||||||
|
for (Object extractedObject : extractedValues) {
|
||||||
|
String extractedString = stringify(extractedObject);
|
||||||
|
vars.put(currentRefName + "_" + index,
|
||||||
|
extractedString); //$NON-NLS-1$
|
||||||
|
if (getComputeConcatenation()) {
|
||||||
|
concat.append(extractedString)
|
||||||
|
.append(JSONPostProcessor.JSON_CONCATENATION_SEPARATOR);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (getComputeConcatenation()) {
|
||||||
|
concat.setLength(concat.length() - 1);
|
||||||
|
vars.put(currentRefName + ALL_SUFFIX, concat.toString());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (matchNumber == 0) {
|
||||||
|
// Random extraction
|
||||||
|
int matchSize = extractedValues.size();
|
||||||
|
int matchNr = JMeterUtils.getRandomInt(matchSize);
|
||||||
|
placeObjectIntoVars(vars, currentRefName,
|
||||||
|
extractedValues, matchNr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// extract at position
|
||||||
|
if (matchNumber > extractedValues.size()) {
|
||||||
|
if(log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"matchNumber({}) exceeds number of items found({}), default value will be used",
|
||||||
|
matchNumber, extractedValues.size());
|
||||||
|
}
|
||||||
|
vars.put(currentRefName, defaultValues[i]);
|
||||||
|
} else {
|
||||||
|
placeObjectIntoVars(vars, currentRefName, extractedValues, matchNumber - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEmptyResult(JMeterVariables vars, String[] defaultValues, int i, int matchNumber,
|
||||||
|
String currentRefName) {
|
||||||
|
vars.put(currentRefName, defaultValues[i]);
|
||||||
|
vars.put(currentRefName + REF_MATCH_NR, "0"); //$NON-NLS-1$
|
||||||
|
if (matchNumber < 0 && getComputeConcatenation()) {
|
||||||
|
log.debug("No value extracted, storing empty in: {}{}", currentRefName, ALL_SUFFIX);
|
||||||
|
vars.put(currentRefName + ALL_SUFFIX, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEmptyResponse(JMeterVariables vars, String[] defaultValues, int i, String currentRefName) {
|
||||||
|
if(log.isDebugEnabled()) {
|
||||||
|
log.debug("Response or source variable is null or empty for {}", getName());
|
||||||
|
}
|
||||||
|
vars.put(currentRefName, defaultValues[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractJsonResponse(JMeterContext context, JMeterVariables vars) {
|
||||||
|
String jsonResponse = "";
|
||||||
|
if (isScopeVariable()) {
|
||||||
|
jsonResponse = vars.get(getVariableName());
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("JSON Extractor is using variable: {}, which content is: {}", getVariableName(), jsonResponse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SampleResult previousResult = context.getPreviousResult();
|
||||||
|
if (previousResult != null) {
|
||||||
|
jsonResponse = previousResult.getResponseDataAsString();
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("JSON Extractor {} working on Response: {}", getName(), jsonResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearOldRefVars(JMeterVariables vars, String refName) {
|
||||||
|
vars.remove(refName + REF_MATCH_NR);
|
||||||
|
for (int i=1; vars.get(refName + "_" + i) != null; i++) {
|
||||||
|
vars.remove(refName + "_" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void placeObjectIntoVars(JMeterVariables vars, String currentRefName,
|
||||||
|
List<Object> extractedValues, int matchNr) {
|
||||||
|
vars.put(currentRefName,
|
||||||
|
stringify(extractedValues.get(matchNr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stringify(Object obj) {
|
||||||
|
return obj == null ? "" : obj.toString(); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJsonPathExpressions() {
|
||||||
|
return getPropertyAsString(JSON_PATH_EXPRESSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJsonPathExpressions(String jsonPath) {
|
||||||
|
setProperty(JSON_PATH_EXPRESSIONS, jsonPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRefNames() {
|
||||||
|
return getPropertyAsString(REFERENCE_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefNames(String refName) {
|
||||||
|
setProperty(REFERENCE_NAMES, refName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultValues() {
|
||||||
|
return getPropertyAsString(DEFAULT_VALUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultValues(String defaultValue) {
|
||||||
|
setProperty(DEFAULT_VALUES, defaultValue, ""); // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getComputeConcatenation() {
|
||||||
|
return getPropertyAsBoolean(COMPUTE_CONCATENATION, COMPUTE_CONCATENATION_DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComputeConcatenation(boolean computeConcatenation) {
|
||||||
|
setProperty(COMPUTE_CONCATENATION, computeConcatenation, COMPUTE_CONCATENATION_DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void threadStarted() {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void threadFinished() {
|
||||||
|
localMatcher.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMatchNumbers(String matchNumber) {
|
||||||
|
setProperty(MATCH_NUMBERS, matchNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMatchNumbers() {
|
||||||
|
return getPropertyAsString(MATCH_NUMBERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getMatchNumbersAsInt(int arraySize) {
|
||||||
|
|
||||||
|
String matchNumbersAsString = getMatchNumbers();
|
||||||
|
int[] result = new int[arraySize];
|
||||||
|
if (JOrphanUtils.isBlank(matchNumbersAsString)) {
|
||||||
|
Arrays.fill(result, 0);
|
||||||
|
} else {
|
||||||
|
String[] matchNumbersAsStringArray =
|
||||||
|
matchNumbersAsString.split(SEPARATOR);
|
||||||
|
for (int i = 0; i < matchNumbersAsStringArray.length; i++) {
|
||||||
|
result[i] = Integer.parseInt(matchNumbersAsStringArray[i].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -22,6 +22,10 @@
|
||||||
</template>
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||||
|
<pre>{{response.vars}}</pre>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-collapse-transition>
|
</el-collapse-transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue