From c62a4037989b5f2dd11c8fdf8463a4deed6201a3 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sun, 27 Sep 2020 11:17:55 +0800 Subject: [PATCH 01/10] =?UTF-8?q?fix(=E6=B5=8B=E8=AF=95=E8=B7=9F=E8=B8=AA)?= =?UTF-8?q?:=20=E5=88=86=E9=A1=B5=E5=B1=95=E7=A4=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../track/case/components/TestCaseList.vue | 722 +++++++++--------- 1 file changed, 362 insertions(+), 360 deletions(-) diff --git a/frontend/src/business/components/track/case/components/TestCaseList.vue b/frontend/src/business/components/track/case/components/TestCaseList.vue index 7eb79d0494..9ebbd70f26 100644 --- a/frontend/src/business/components/track/case/components/TestCaseList.vue +++ b/frontend/src/business/components/track/case/components/TestCaseList.vue @@ -4,7 +4,8 @@ + +
{{response.vars}}
+
+ From 364c0083517865e6934754ec058d549257e858a0 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sun, 27 Sep 2020 14:10:47 +0800 Subject: [PATCH 03/10] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E9=87=8D=E6=9E=84=E6=8F=90=E5=8F=96=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/jmeter/extractor/RegexExtractor.java | 11 +---------- .../org/apache/jmeter/extractor/XPath2Extractor.java | 2 +- .../extractor/json/jsonpath/JSONPostProcessor.java | 9 +++++---- .../java/org/apache/jmeter/samplers/SampleResult.java | 7 +++++-- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java b/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java index 356b19ba1d..606a77d652 100644 --- a/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java +++ b/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java @@ -83,7 +83,6 @@ public class RegexExtractor extends AbstractScopedTestElement implements PostPro private transient List template; - private JMeterVariables regexVars; /** * Parses the response data using regular expressions and saving the results * into variables for use later in the test. @@ -93,7 +92,6 @@ public class RegexExtractor extends AbstractScopedTestElement implements PostPro @Override public void process() { initTemplate(); - regexVars = new JMeterVariables(); JMeterContext context = getThreadContext(); SampleResult previousResult = context.getPreviousResult(); if (previousResult == null) { @@ -109,7 +107,6 @@ public class RegexExtractor extends AbstractScopedTestElement implements PostPro 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(); @@ -135,8 +132,6 @@ public class RegexExtractor extends AbstractScopedTestElement implements PostPro 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) @@ -147,15 +142,11 @@ public class RegexExtractor extends AbstractScopedTestElement implements PostPro 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); } } @@ -166,7 +157,7 @@ public class RegexExtractor extends AbstractScopedTestElement implements PostPro vars.remove(refName_n); removeGroups(vars, refName_n); } - previousResult.addVars(regexVars); + previousResult.addVars(refName, vars.get(refName)); } catch (RuntimeException e) { log.warn("Error while generating result"); } diff --git a/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java b/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java index 4946ecafe2..8d0412704a 100644 --- a/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java +++ b/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java @@ -152,7 +152,7 @@ public class XPath2Extractor for (int i = matchCount + 2; i <= prevCount; i++) { vars.remove(concat(refName, i)); } - previousResult.addVars(vars); + previousResult.addVars(refName,vars.get(refName)); } catch (Exception e) {// Saxon exception if (log.isWarnEnabled()) { log.warn("Exception while processing '{}', message:{}", getXPathQuery(), e.getMessage()); diff --git a/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java b/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java index f879b2f5fb..b035faadb0 100644 --- a/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java +++ b/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java @@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory; /** * JSON-PATH based extractor + * * @since 3.0 */ public class JSONPostProcessor @@ -89,7 +90,7 @@ public class JSONPostProcessor } } SampleResult previousResult = context.getPreviousResult(); - previousResult.addVars(vars); + previousResult.addVars(currentRefName, vars.get(currentRefName)); } catch (Exception e) { // if something wrong, default value added if (log.isDebugEnabled()) { @@ -172,7 +173,7 @@ public class JSONPostProcessor } // extract at position if (matchNumber > extractedValues.size()) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug( "matchNumber({}) exceeds number of items found({}), default value will be used", matchNumber, extractedValues.size()); @@ -194,7 +195,7 @@ public class JSONPostProcessor } private void handleEmptyResponse(JMeterVariables vars, String[] defaultValues, int i, String currentRefName) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("Response or source variable is null or empty for {}", getName()); } vars.put(currentRefName, defaultValues[i]); @@ -221,7 +222,7 @@ public class JSONPostProcessor private void clearOldRefVars(JMeterVariables vars, String refName) { vars.remove(refName + REF_MATCH_NR); - for (int i=1; vars.get(refName + "_" + i) != null; i++) { + for (int i = 1; vars.get(refName + "_" + i) != null; i++) { vars.remove(refName + "_" + i); } } diff --git a/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java index 4d1c3af09e..359a8615fa 100644 --- a/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -1612,8 +1612,11 @@ public class SampleResult implements Serializable, Cloneable, Searchable { this.testLogicalAction = testLogicalAction; } - public void addVars(JMeterVariables vars) { - this.vars = vars; + public void addVars(String key, String value) { + if (this.vars == null) { + this.vars = new JMeterVariables(); + } + this.vars.put(key, value); } public JMeterVariables getVars() { From 3ab95127c1a7c8dadece539ffc3e5d269cf9f007 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sun, 27 Sep 2020 17:08:16 +0800 Subject: [PATCH 04/10] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E9=87=8D=E6=9E=84=E6=8F=90=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 7 +- .../io/metersphere/api/jmeter/JMeterVars.java | 15 + .../jmeter/extractor/RegexExtractor.java | 517 ------ .../jmeter/extractor/XPath2Extractor.java | 283 --- .../json/jsonpath/JSONPostProcessor.java | 305 ---- .../apache/jmeter/samplers/SampleResult.java | 1625 ----------------- 6 files changed, 19 insertions(+), 2733 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java delete mode 100644 backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java delete mode 100644 backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java delete mode 100644 backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java delete mode 100644 backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 38927181d6..11082e3c36 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -13,6 +13,7 @@ import io.metersphere.notice.service.NoticeService; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; @@ -71,7 +72,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); - queue.forEach(result -> { // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); @@ -154,9 +154,10 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl responseResult.setResponseTime(result.getTime()); responseResult.setResponseMessage(result.getResponseMessage()); - if (result.getVars() != null && !result.getVars().entrySet().isEmpty()) { + if (JMeterVars.variables != null && JMeterVars.variables.get(result.getThreadName()) != null) { + JMeterVars.variables.get(result.getThreadName()).remove("TESTSTART.MS"); //去除系统变量 List vars = new LinkedList<>(); - result.getVars().entrySet().parallelStream().reduce(vars, (first, second) -> { + JMeterVars.variables.get(result.getThreadName()).entrySet().parallelStream().reduce(vars, (first, second) -> { first.add(second.getKey() + ":" + second.getValue()); return first; }, (first, second) -> { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java new file mode 100644 index 0000000000..380fd0078f --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -0,0 +1,15 @@ +package io.metersphere.api.jmeter; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterVariables; + +import java.util.HashMap; +import java.util.Map; + +public class JMeterVars { + public static Map variables = new HashMap<>(); + + public static void addVars(String testId, JMeterVariables vars) { + variables.put(testId, vars); + } +} diff --git a/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java b/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java deleted file mode 100644 index 606a77d652..0000000000 --- a/backend/src/main/java/org/apache/jmeter/extractor/RegexExtractor.java +++ /dev/null @@ -1,517 +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.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 template; - - /** - * 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(); - 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); - } - - Perl5Matcher matcher = JMeterUtils.getMatcher(); - String regex = getRegex(); - Pattern pattern = null; - try { - pattern = JMeterUtils.getPatternCache().getPattern(regex, Perl5Compiler.READ_ONLY_MASK); - List 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)); - 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 - 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)); - 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(refName, vars.get(refName)); - } 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 processMatches(Pattern pattern, String regex, SampleResult result, int matchNumber, JMeterVariables vars) { - log.debug("Regex = '{}'", regex); - - Perl5Matcher matcher = JMeterUtils.getMatcher(); - List 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 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 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:
- * basename_gn, where n=0...# of groups
- * 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:
- * basename_gn, where n=0...# of groups
- * 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 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 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 0, which is interpreted as meaning - * random. - * - * @param matchNumber The number of the match to be used, or 0 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); - } -} \ No newline at end of file diff --git a/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java b/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java deleted file mode 100644 index 8d0412704a..0000000000 --- a/backend/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java +++ /dev/null @@ -1,283 +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.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: - *
- *
/html/head/title
- *
extracts Title from HTML response
- *
//form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value - *
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'
- *
//head
- *
extracts the XML fragment for head node.
- *
//head/text()
- *
extracts the text content for head node.
- *
- * 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 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 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(refName,vars.get(refName)); - } 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 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 0, 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 0, 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 0, 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 0, 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); - } -} \ No newline at end of file diff --git a/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java b/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java deleted file mode 100644 index b035faadb0..0000000000 --- a/backend/src/main/java/org/apache/jmeter/extractor/json/jsonpath/JSONPostProcessor.java +++ /dev/null @@ -1,305 +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.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 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 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(currentRefName, vars.get(currentRefName)); - } 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 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 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 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 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; - } -} \ No newline at end of file diff --git a/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java deleted file mode 100644 index 359a8615fa..0000000000 --- a/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ /dev/null @@ -1,1625 +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.samplers; - -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -import org.apache.jmeter.assertions.AssertionResult; -import org.apache.jmeter.gui.Searchable; -import org.apache.jmeter.testelement.TestPlan; -import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; -import org.apache.jmeter.threads.JMeterVariables; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.JOrphanUtils; -import org.aspectj.weaver.ast.Var; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -// For unit tests, @see TestSampleResult - -/** - * This is a nice packaging for the various information returned from taking a - * sample of an entry. - */ -public class SampleResult implements Serializable, Cloneable, Searchable { - - private static final long serialVersionUID = 241L; - - // Needs to be accessible from Test code - static Logger log = LoggerFactory.getLogger(SampleResult.class); - - /** - * The default encoding to be used if not overridden. - * The value is ISO-8859-1. - */ - public static final String DEFAULT_HTTP_ENCODING = StandardCharsets.ISO_8859_1.name(); - - private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); - private static final String OK_MSG = "OK"; // $NON-NLS-1$ - private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ - - - // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries - // However the suggested System.getProperty("file.encoding") is Cp1252 on - // Windows - // So use a new property with the original value as default - // needs to be accessible from test code - /** - * The default encoding to be used to decode the responseData byte array. - * The value is defined by the property "sampleresult.default.encoding" - * with a default of DEFAULT_HTTP_ENCODING if that is not defined. - */ - protected static final String DEFAULT_ENCODING - = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ - DEFAULT_HTTP_ENCODING); - - /** - * The default used by {@link #setResponseData(String, String)} - */ - private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); - - /** - * Data type value ({@value}) indicating that the response data is text. - * - * @see #getDataType - * @see #setDataType(java.lang.String) - */ - public static final String TEXT = "text"; // $NON-NLS-1$ - - /** - * Data type value ({@value}) indicating that the response data is binary. - * - * @see #getDataType - * @see #setDataType(java.lang.String) - */ - public static final String BINARY = "bin"; // $NON-NLS-1$ - - private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); - - // List of types that are known to be binary - private static final String[] BINARY_TYPES = { - "image/", //$NON-NLS-1$ - "audio/", //$NON-NLS-1$ - "video/", //$NON-NLS-1$ - }; - - // List of types that are known to be ascii, although they may appear to be binary - private static final String[] NON_BINARY_TYPES = { - "audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest) - "audio/mpegurl", //$NON-NLS-1$ (HLS Media Manifest) - "video/f4m", //$NON-NLS-1$ (Flash Media Manifest) - "image/svg+xml" //$NON-NLS-1$ (SVG is xml) - }; - - - /** - * empty array which can be returned instead of null - */ - private static final byte[] EMPTY_BA = new byte[0]; - - private static final SampleResult[] EMPTY_SR = new SampleResult[0]; - - private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; - - private static final boolean START_TIMESTAMP = - JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ - - /** - * Allow read-only access from test code - */ - private static final boolean USE_NANO_TIME = - JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ - - /** - * How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread - */ - private static final long NANOTHREAD_SLEEP = - JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$ - - private static final String NULL_FILENAME = "NULL"; - - static { - if (START_TIMESTAMP) { - log.info("Note: Sample TimeStamps are START times"); - } else { - log.info("Note: Sample TimeStamps are END times"); - } - log.info("sampleresult.default.encoding is set to {}", DEFAULT_ENCODING); - log.info("sampleresult.useNanoTime={}", USE_NANO_TIME); - log.info("sampleresult.nanoThreadSleep={}", NANOTHREAD_SLEEP); - - if (USE_NANO_TIME && NANOTHREAD_SLEEP > 0) { - // Make sure we start with a reasonable value - NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); - NanoOffset nanoOffset = new NanoOffset(); - nanoOffset.setDaemon(true); - nanoOffset.setName("NanoOffset"); - nanoOffset.start(); - } - } - - private SampleSaveConfiguration saveConfig; - - private SampleResult parent; - - private byte[] responseData = EMPTY_BA; - - private String responseCode = "";// Never return null - - private String label = "";// Never return null - - /** - * Filename used by ResultSaver - */ - private String resultFileName = ""; - - /** - * The data used by the sampler - */ - private String samplerData; - - private String threadName = ""; // Never return null - - private String responseMessage = ""; - - private String responseHeaders = ""; // Never return null - - private String requestHeaders = ""; - - /** - * timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) - * the time stamp - can be start or end - */ - private long timeStamp = 0; - - private long startTime = 0; - - private long endTime = 0; - - private long idleTime = 0;// Allow for non-sample time - - /** - * Start of pause (if any) - */ - private long pauseTime = 0; - - private List assertionResults; - - private List subResults; - - private JMeterVariables vars; - - /** - * The data type of the sample - * - * @see #getDataType() - * @see #setDataType(String) - * @see #TEXT - * @see #BINARY - */ - private String dataType = ""; // Don't return null if not set - - private boolean success; - - /** - * Files that this sample has been saved in. - * In Non GUI mode and when best config is used, size never exceeds 1, - * but as a compromise set it to 2 - */ - private final Set files = ConcurrentHashMap.newKeySet(2); - - // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? - private String dataEncoding;// (is this really the character set?) e.g. - // ISO-8895-1, UTF-8 - - private String contentType = ""; // e.g. text/html; charset=utf-8 - - /** - * elapsed time - */ - private long elapsedTime = 0; - - /** - * time to first response - */ - private long latency = 0; - - /** - * time to end connecting - */ - private long connectTime = 0; - - /** - * Way to signal what to do on Test - */ - private TestLogicalAction testLogicalAction = TestLogicalAction.CONTINUE; - - /** - * Should thread terminate? - */ - private boolean stopThread = false; - - /** - * Should test terminate? - */ - private boolean stopTest = false; - - /** - * Should test terminate abruptly? - */ - private boolean stopTestNow = false; - - private int sampleCount = 1; - - private long bytes = 0; // Allows override of sample size in case sampler does not want to store all the data - - private int headersSize = 0; - - private long bodySize = 0; - - /** - * Currently active threads in this thread group - */ - private volatile int groupThreads = 0; - - /** - * Currently active threads in all thread groups - */ - private volatile int allThreads = 0; - - private final long nanoTimeOffset; - - // Allow testcode access to the settings - final boolean useNanoTime; - - final long nanoThreadSleep; - - private long sentBytes; - - private URL location; - - private transient boolean ignore; - - private transient int subResultIndex; - - /** - * Cache for responseData as string to avoid multiple computations - */ - private transient volatile String responseDataAsString; - - public SampleResult() { - this(USE_NANO_TIME, NANOTHREAD_SLEEP); - } - - // Allow test code to change the default useNanoTime setting - SampleResult(boolean nanoTime) { - this(nanoTime, NANOTHREAD_SLEEP); - } - - // Allow test code to change the default useNanoTime and nanoThreadSleep settings - SampleResult(boolean nanoTime, long nanoThreadSleep) { - this.elapsedTime = 0; - this.useNanoTime = nanoTime; - this.nanoThreadSleep = nanoThreadSleep; - this.nanoTimeOffset = initOffset(); - } - - /** - * Copy constructor. - * - * @param res existing sample result - */ - public SampleResult(SampleResult res) { - this(); - allThreads = res.allThreads;//OK - assertionResults = res.assertionResults; - bytes = res.bytes; - headersSize = res.headersSize; - bodySize = res.bodySize; - contentType = res.contentType;//OK - dataEncoding = res.dataEncoding;//OK - dataType = res.dataType;//OK - endTime = res.endTime;//OK - // files is created automatically, and applies per instance - groupThreads = res.groupThreads;//OK - idleTime = res.idleTime; - label = res.label;//OK - latency = res.latency; - connectTime = res.connectTime; - location = res.location;//OK - parent = res.parent; - pauseTime = res.pauseTime; - requestHeaders = res.requestHeaders;//OK - responseCode = res.responseCode;//OK - responseData = res.responseData;//OK - responseDataAsString = null; - responseHeaders = res.responseHeaders;//OK - responseMessage = res.responseMessage;//OK - - // Don't copy this; it is per instance resultFileName = res.resultFileName; - - sampleCount = res.sampleCount; - samplerData = res.samplerData; - saveConfig = res.saveConfig; - sentBytes = res.sentBytes; - startTime = res.startTime;//OK - stopTest = res.stopTest; - stopTestNow = res.stopTestNow; - stopThread = res.stopThread; - testLogicalAction = res.testLogicalAction; - subResults = res.subResults; - success = res.success;//OK - threadName = res.threadName;//OK - elapsedTime = res.elapsedTime; - timeStamp = res.timeStamp; - } - - /** - * Create a sample with a specific elapsed time but don't allow the times to - * be changed later - *

- * (only used by HTTPSampleResult) - * - * @param elapsed time - * @param atend create the sample finishing now, else starting now - */ - protected SampleResult(long elapsed, boolean atend) { - this(); - long now = currentTimeInMillis(); - if (atend) { - setTimes(now - elapsed, now); - } else { - setTimes(now, now + elapsed); - } - } - - /** - * Allow users to create a sample with specific timestamp and elapsed times - * for cloning purposes, but don't allow the times to be changed later - *

- * Currently used by CSVSaveService and - * StatisticalSampleResult - * - * @param stamp this may be a start time or an end time (both in - * milliseconds) - * @param elapsed time in milliseconds - */ - public SampleResult(long stamp, long elapsed) { - this(); - stampAndTime(stamp, elapsed); - } - - private long initOffset() { - if (useNanoTime) { - return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); - } else { - return Long.MIN_VALUE; - } - } - - /** - * @param propertiesToSave The propertiesToSave to set. - */ - public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { - this.saveConfig = propertiesToSave; - } - - public SampleSaveConfiguration getSaveConfig() { - return saveConfig; - } - - public boolean isStampedAtStart() { - return START_TIMESTAMP; - } - - /** - * Create a sample with specific start and end times for test purposes, but - * don't allow the times to be changed later - *

- * (used by StatVisualizerModel.Test) - * - * @param start start time in milliseconds since unix epoch - * @param end end time in milliseconds since unix epoch - * @return sample with given start and end time - */ - public static SampleResult createTestSample(long start, long end) { - SampleResult res = new SampleResult(); - res.setStartTime(start); - res.setEndTime(end); - return res; - } - - /** - * Create a sample with a specific elapsed time for test purposes, but don't - * allow the times to be changed later - * - * @param elapsed - desired elapsed time in milliseconds - * @return sample that starts 'now' and ends elapsed milliseconds later - */ - public static SampleResult createTestSample(long elapsed) { - long now = System.currentTimeMillis(); - return createTestSample(now, now + elapsed); - } - - private static long sampleNsClockInMs() { - return System.nanoTime() / 1000000; - } - - /** - * Helper method to get 1 ms resolution timing. - * - * @return the current time in milliseconds - * @throws RuntimeException when useNanoTime is true but - * nanoTimeOffset is not set - */ - public long currentTimeInMillis() { - if (useNanoTime) { - if (nanoTimeOffset == Long.MIN_VALUE) { - throw new IllegalStateException("Invalid call; nanoTimeOffset has not been set"); - } - return sampleNsClockInMs() + nanoTimeOffset; - } - return System.currentTimeMillis(); - } - - // Helper method to maintain timestamp relationships - private void stampAndTime(long stamp, long elapsed) { - if (START_TIMESTAMP) { - startTime = stamp; - endTime = stamp + elapsed; - } else { - startTime = stamp - elapsed; - endTime = stamp; - } - timeStamp = stamp; - elapsedTime = elapsed; - } - - /** - * For use by SaveService only. - * - * @param stamp this may be a start time or an end time (both in milliseconds) - * @param elapsed time in milliseconds - * @throws RuntimeException when startTime or endTime has been - * set already - */ - public void setStampAndTime(long stamp, long elapsed) { - if (startTime != 0 || endTime != 0) { - throw new IllegalStateException("Calling setStampAndTime() after start/end times have been set"); - } - stampAndTime(stamp, elapsed); - } - - /** - * Set the "marked" flag to show that the result has been written to the file. - * - * @param filename the name of the file - * @return true if the result was previously marked - */ - public boolean markFile(String filename) { - return !files.add(filename != null ? filename : NULL_FILENAME); - } - - public String getResponseCode() { - return responseCode; - } - - /** - * Set response code to OK, i.e. "200" - */ - public void setResponseCodeOK() { - responseCode = OK_CODE; - } - - public void setResponseCode(String code) { - responseCode = code; - } - - public boolean isResponseCodeOK() { - return responseCode.equals(OK_CODE); - } - - public String getResponseMessage() { - return responseMessage; - } - - public void setResponseMessage(String msg) { - responseMessage = msg; - } - - public void setResponseMessageOK() { - responseMessage = OK_MSG; - } - - /** - * Set result statuses OK - shorthand method to set: - *

    - *
  • ResponseCode
  • - *
  • ResponseMessage
  • - *
  • Successful status
  • - *
- */ - public void setResponseOK() { - setResponseCodeOK(); - setResponseMessageOK(); - setSuccessful(true); - } - - public String getThreadName() { - return threadName; - } - - public void setThreadName(String threadName) { - this.threadName = threadName; - } - - /** - * Get the sample timestamp, which may be either the start time or the end time. - * - * @return timeStamp in milliseconds - * @see #getStartTime() - * @see #getEndTime() - */ - public long getTimeStamp() { - return timeStamp; - } - - public String getSampleLabel() { - return label; - } - - /** - * Get the sample label for use in summary reports etc. - * - * @param includeGroup whether to include the thread group name - * @return the label - */ - public String getSampleLabel(boolean includeGroup) { - if (includeGroup) { - return threadName.substring(0, threadName.lastIndexOf(' ')) + ":" + label; - } - return label; - } - - public void setSampleLabel(String label) { - this.label = label; - } - - public void addAssertionResult(AssertionResult assertResult) { - if (assertionResults == null) { - assertionResults = new ArrayList<>(); - } - assertionResults.add(assertResult); - } - - /** - * Gets the assertion results associated with this sample. - * - * @return an array containing the assertion results for this sample. - * Returns empty array if there are no assertion results. - */ - public AssertionResult[] getAssertionResults() { - if (assertionResults == null) { - return EMPTY_AR; - } - return assertionResults.toArray(new AssertionResult[assertionResults.size()]); - } - - /** - * Add a subresult and adjust the parent byte count and end-time. - * - * @param subResult the {@link SampleResult} to be added - */ - public void addSubResult(SampleResult subResult) { - addSubResult(subResult, isRenameSampleLabel()); - } - - /** - * see https://bz.apache.org/bugzilla/show_bug.cgi?id=63055 - * - * @return true if TestPlan is in functional mode or property subresults.disable_renaming is true - */ - public static boolean isRenameSampleLabel() { - return !(TestPlan.getFunctionalMode() || DISABLE_SUBRESULTS_RENAMING); - } - - /** - * Add a subresult and adjust the parent byte count and end-time. - * - * @param subResult the {@link SampleResult} to be added - * @param renameSubResults boolean do we rename subResults based on position - */ - public void addSubResult(SampleResult subResult, boolean renameSubResults) { - if (subResult == null) { - // see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778 - return; - } - String tn = getThreadName(); - if (tn.length() == 0) { - tn = Thread.currentThread().getName(); - this.setThreadName(tn); - } - subResult.setThreadName(tn); - - // Extend the time to the end of the added sample - setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 - // Include the byte count for the added sample - setBytes(getBytesAsLong() + subResult.getBytesAsLong()); - setSentBytes(getSentBytes() + subResult.getSentBytes()); - setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); - setBodySize(getBodySizeAsLong() + subResult.getBodySizeAsLong()); - addRawSubResult(subResult, renameSubResults); - } - - /** - * Add a subresult to the collection without updating any parent fields. - * - * @param subResult the {@link SampleResult} to be added - */ - public void addRawSubResult(SampleResult subResult) { - storeSubResult(subResult, isRenameSampleLabel()); - } - - /** - * Add a subresult to the collection without updating any parent fields. - * - * @param subResult the {@link SampleResult} to be added - */ - private void addRawSubResult(SampleResult subResult, boolean renameSubResults) { - storeSubResult(subResult, renameSubResults); - } - - /** - * Add a subresult read from a results file. - *

- * As for {@link SampleResult#addSubResult(SampleResult) - * addSubResult(SampleResult)}, except that the fields don't need to be - * accumulated - * - * @param subResult the {@link SampleResult} to be added - */ - public void storeSubResult(SampleResult subResult) { - storeSubResult(subResult, isRenameSampleLabel()); - } - - /** - * Add a subresult read from a results file. - *

- * As for {@link SampleResult#addSubResult(SampleResult) - * addSubResult(SampleResult)}, except that the fields don't need to be - * accumulated - * - * @param subResult the {@link SampleResult} to be added - * @param renameSubResults boolean do we rename subResults based on position - */ - private void storeSubResult(SampleResult subResult, boolean renameSubResults) { - if (subResults == null) { - subResults = new ArrayList<>(); - } - if (renameSubResults) { - subResult.setSampleLabel(getSampleLabel() + "-" + subResultIndex++); - } - subResults.add(subResult); - subResult.setParent(this); - } - - /** - * Gets the subresults associated with this sample. - * - * @return an array containing the subresults for this sample. Returns an - * empty array if there are no subresults. - */ - public SampleResult[] getSubResults() { - if (subResults == null) { - return EMPTY_SR; - } - return subResults.toArray(new SampleResult[subResults.size()]); - } - - /** - * Sets the responseData attribute of the SampleResult object. - *

- * If the parameter is null, then the responseData is set to an empty byte array. - * This ensures that getResponseData() can never be null. - * - * @param response the new responseData value - */ - public void setResponseData(byte[] response) { - responseDataAsString = null; - responseData = response == null ? EMPTY_BA : response; - } - - /** - * Sets the responseData attribute of the SampleResult object. - * Should only be called after setting the dataEncoding (if necessary) - * - * @param response the new responseData value (String) - * @deprecated - only intended for use from BeanShell code - */ - @Deprecated - public void setResponseData(String response) { - responseDataAsString = null; - try { - responseData = response.getBytes(getDataEncodingWithDefault()); - } catch (UnsupportedEncodingException e) { - log.warn("Could not convert string, using default encoding. " + e.getLocalizedMessage()); - responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here - } - } - - /** - * Sets the encoding and responseData attributes of the SampleResult object. - * - * @param response the new responseData value (String) - * @param encoding the encoding to set and then use (if null, use platform default) - */ - public void setResponseData(final String response, final String encoding) { - responseDataAsString = null; - String encodeUsing = encoding != null ? encoding : DEFAULT_CHARSET; - try { - responseData = response.getBytes(encodeUsing); - setDataEncoding(encodeUsing); - } catch (UnsupportedEncodingException e) { - log.warn("Could not convert string using '" + encodeUsing + - "', using default encoding: " + DEFAULT_CHARSET, e); - responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here - setDataEncoding(DEFAULT_CHARSET); - } - } - - /** - * Gets the responseData attribute of the SampleResult object. - *

- * Note that some samplers may not store all the data, in which case - * getResponseData().length will be incorrect. - *

- * Instead, always use {@link #getBytes()} to obtain the sample result byte count. - *

- * - * @return the responseData value (cannot be null) - */ - public byte[] getResponseData() { - return responseData; - } - - /** - * Gets the responseData of the SampleResult object as a String - * - * @return the responseData value as a String, converted according to the encoding - */ - public String getResponseDataAsString() { - try { - if (responseDataAsString == null) { - responseDataAsString = new String(responseData, getDataEncodingWithDefault()); - } - return responseDataAsString; - } catch (UnsupportedEncodingException e) { - log.warn("Using platform default as " + getDataEncodingWithDefault() + " caused " + e); - return new String(responseData, Charset.defaultCharset()); // N.B. default charset is used deliberately here - } - } - - public void setSamplerData(String s) { - samplerData = s; - } - - public String getSamplerData() { - return samplerData; - } - - /** - * Get the time it took this sample to occur. - * - * @return elapsed time in milliseconds - */ - public long getTime() { - return elapsedTime; - } - - public boolean isSuccessful() { - return success; - } - - /** - * Sets the data type of the sample. - * - * @param dataType String containing {@link #BINARY} or {@link #TEXT} - * @see #BINARY - * @see #TEXT - */ - public void setDataType(String dataType) { - this.dataType = dataType; - } - - /** - * Returns the data type of the sample. - * - * @return String containing {@link #BINARY} or {@link #TEXT} or the empty string - * @see #BINARY - * @see #TEXT - */ - public String getDataType() { - return dataType; - } - - /** - * Extract and save the DataEncoding and DataType from the parameter provided. - * Does not save the full content Type. - * - * @param ct - content type (may be null) - * @see #setContentType(String) which should be used to save the full content-type string - */ - public void setEncodingAndType(String ct) { - if (ct != null) { - // Extract charset and store as DataEncoding - // N.B. The meta tag: - // - // is now processed by HTTPSampleResult#getDataEncodingWithDefault - final String charsetPrefix = "charset="; // $NON-NLS-1$ - int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(charsetPrefix); - if (cset >= 0) { - String charSet = ct.substring(cset + charsetPrefix.length()); - // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed - int semiColon = charSet.indexOf(';'); - if (semiColon >= 0) { - charSet = charSet.substring(0, semiColon); - } - // Check for quoted string - if (charSet.startsWith("\"") || charSet.startsWith("\'")) { // $NON-NLS-1$ - setDataEncoding(charSet.substring(1, charSet.length() - 1)); // remove quotes - } else { - setDataEncoding(charSet); - } - } - if (isBinaryType(ct)) { - setDataType(BINARY); - } else { - setDataType(TEXT); - } - } - } - - /* - * Determine if content-type is known to be binary, i.e. not displayable as text. - * - * @param ct content type - * @return true if content-type is of type binary. - */ - public static boolean isBinaryType(String ct) { - for (String entry : NON_BINARY_TYPES) { - if (ct.startsWith(entry)) { - return false; - } - } - for (String binaryType : BINARY_TYPES) { - if (ct.startsWith(binaryType)) { - return true; - } - } - return false; - } - - /** - * Sets the successful attribute of the SampleResult object. - * - * @param success the new successful value - */ - public void setSuccessful(boolean success) { - this.success = success; - } - - /** - * Returns the display name. - * - * @return display name of this sample result - */ - @Override - public String toString() { - return getSampleLabel(); - } - - /** - * Returns the dataEncoding or the default if no dataEncoding was provided. - * - * @return the value of the dataEncoding or DEFAULT_ENCODING - */ - public String getDataEncodingWithDefault() { - return getDataEncodingWithDefault(DEFAULT_ENCODING); - } - - /** - * Returns the dataEncoding or the default if no dataEncoding was provided. - * - * @param defaultEncoding the default to be applied - * @return the value of the dataEncoding or the provided default - */ - protected String getDataEncodingWithDefault(String defaultEncoding) { - if (dataEncoding != null && dataEncoding.length() > 0) { - return dataEncoding; - } - return defaultEncoding; - } - - /** - * Returns the dataEncoding. May be null or the empty String. - * - * @return the value of the dataEncoding - */ - public String getDataEncodingNoDefault() { - return dataEncoding; - } - - /** - * Sets the dataEncoding. - * - * @param dataEncoding the dataEncoding to set, e.g. ISO-8895-1, UTF-8 - */ - public void setDataEncoding(String dataEncoding) { - this.dataEncoding = dataEncoding; - } - - /** - * @return whether to stop the test waiting for current running Sampler to end - */ - public boolean isStopTest() { - return stopTest; - } - - /** - * @return whether to stop the test now interrupting current running samplers - */ - public boolean isStopTestNow() { - return stopTestNow; - } - - /** - * @return whether to stop this thread - */ - public boolean isStopThread() { - return stopThread; - } - - public void setStopTest(boolean b) { - stopTest = b; - } - - public void setStopTestNow(boolean b) { - stopTestNow = b; - } - - public void setStopThread(boolean b) { - stopThread = b; - } - - /** - * @return the request headers - */ - public String getRequestHeaders() { - return requestHeaders; - } - - /** - * @return the response headers - */ - public String getResponseHeaders() { - return responseHeaders; - } - - /** - * @param string - - * request headers - */ - public void setRequestHeaders(String string) { - requestHeaders = string; - } - - /** - * @param string - - * response headers - */ - public void setResponseHeaders(String string) { - responseHeaders = string; - } - - /** - * @return the full content type - e.g. text/html [;charset=utf-8 ] - */ - public String getContentType() { - return contentType; - } - - /** - * Get the media type from the Content Type - * - * @return the media type - e.g. text/html (without charset, if any) - */ - public String getMediaType() { - return JOrphanUtils.trim(contentType, " ;").toLowerCase(java.util.Locale.ENGLISH); - } - - /** - * Stores the content-type string, e.g. text/xml; charset=utf-8 - * - * @param string the content-type to be set - * @see #setEncodingAndType(String) which can be used to extract the charset. - */ - public void setContentType(String string) { - contentType = string; - } - - /** - * @return idleTime - */ - public long getIdleTime() { - return idleTime; - } - - /** - * @return the end time - */ - public long getEndTime() { - return endTime; - } - - /** - * @return the start time - */ - public long getStartTime() { - return startTime; - } - - /* - * Helper methods N.B. setStartTime must be called before setEndTime - * - * setStartTime is used by HTTPSampleResult to clone the parent sampler and - * allow the original start time to be kept - */ - protected final void setStartTime(long start) { - startTime = start; - if (START_TIMESTAMP) { - timeStamp = startTime; - } - } - - public void setEndTime(long end) { - endTime = end; - if (!START_TIMESTAMP) { - timeStamp = endTime; - } - if (startTime == 0) { - log.error("setEndTime must be called after setStartTime", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } else { - elapsedTime = endTime - startTime - idleTime; - } - } - - /** - * Set idle time pause. - * For use by SampleResultConverter/CSVSaveService. - * - * @param idle long - */ - public void setIdleTime(long idle) { - idleTime = idle; - } - - private void setTimes(long start, long end) { - setStartTime(start); - setEndTime(end); - } - - /** - * Record the start time of a sample - */ - public void sampleStart() { - if (startTime == 0) { - setStartTime(currentTimeInMillis()); - } else { - log.error("sampleStart called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - } - - /** - * Record the end time of a sample and calculate the elapsed time - */ - public void sampleEnd() { - if (endTime == 0) { - setEndTime(currentTimeInMillis()); - } else { - log.error("sampleEnd called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - } - - /** - * Pause a sample - */ - public void samplePause() { - if (pauseTime != 0) { - log.error("samplePause called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - pauseTime = currentTimeInMillis(); - } - - /** - * Resume a sample - */ - public void sampleResume() { - if (pauseTime == 0) { - log.error("sampleResume without samplePause", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - idleTime += currentTimeInMillis() - pauseTime; - pauseTime = 0; - } - - /** - * When a Sampler is working as a monitor - * - * @param monitor flag whether this sampler is working as a monitor - * @deprecated since 3.2 NOOP - */ - @Deprecated - public void setMonitor(boolean monitor) { - // NOOP - } - - /** - * If the sampler is a monitor, method will return true. - * - * @return true if the sampler is a monitor - * @deprecated since 3.2 always return false - */ - @Deprecated - public boolean isMonitor() { - return false; - } - - /** - * The statistical sample sender aggregates several samples to save on - * transmission costs. - * - * @param count number of samples represented by this instance - */ - public void setSampleCount(int count) { - sampleCount = count; - } - - /** - * return the sample count. by default, the value is 1. - * - * @return the sample count - */ - public int getSampleCount() { - return sampleCount; - } - - /** - * Returns the count of errors. - * - * @return 0 - or 1 if the sample failed - *

- * TODO do we need allow for nested samples? - */ - public int getErrorCount() { - return success ? 0 : 1; - } - - public void setErrorCount(int i) {// for reading from CSV files - // ignored currently - } - - /* - * TODO: error counting needs to be sorted out. - * - * At present the Statistical Sampler tracks errors separately - * It would make sense to move the error count here, but this would - * mean lots of changes. - * It's also tricky maintaining the count - it can't just be incremented/decremented - * when the success flag is set as this may be done multiple times. - * The work-round for now is to do the work in the StatisticalSampleResult, - * which overrides this method. - * Note that some JMS samplers also create samples with > 1 sample count - * Also the Transaction Controller probably needs to be changed to do - * proper sample and error accounting. - * The purpose of this work-round is to allow at least minimal support for - * errors in remote statistical batch mode. - * - */ - - /** - * In the event the sampler does want to pass back the actual contents, we - * still want to calculate the throughput. The bytes are the bytes of the - * response data. - * - * @param length the number of bytes of the response data for this sample - */ - public void setBytes(long length) { - bytes = length; - } - - /** - * In the event the sampler does want to pass back the actual contents, we - * still want to calculate the throughput. The bytes are the bytes of the - * response data. - * - * @param length the number of bytes of the response data for this sample - * @deprecated use setBytes(long) - */ - @Deprecated - public void setBytes(int length) { - setBytes((long) length); - } - - /** - * @param sentBytesCount long sent bytes - */ - public void setSentBytes(long sentBytesCount) { - sentBytes = sentBytesCount; - } - - /** - * @return the sentBytes - */ - public long getSentBytes() { - return sentBytes; - } - - /** - * return the bytes returned by the response. - * - * @return byte count - * @deprecated use getBytesAsLong - */ - @Deprecated - public int getBytes() { - return (int) getBytesAsLong(); - } - - /** - * return the bytes returned by the response. - * - * @return byte count - */ - public long getBytesAsLong() { - long tmpSum = this.getHeadersSize() + this.getBodySizeAsLong(); - return tmpSum == 0 ? bytes : tmpSum; - } - - /** - * @return Returns the latency. - */ - public long getLatency() { - return latency; - } - - /** - * Set the time to the first response - */ - public void latencyEnd() { - latency = currentTimeInMillis() - startTime - idleTime; - } - - /** - * This is only intended for use by SampleResultConverter! - * - * @param latency The latency to set. - */ - public void setLatency(long latency) { - this.latency = latency; - } - - /** - * @return Returns the connect time. - */ - public long getConnectTime() { - return connectTime; - } - - /** - * Set the time to the end of connecting - */ - public void connectEnd() { - connectTime = currentTimeInMillis() - startTime - idleTime; - } - - /** - * This is only intended for use by SampleResultConverter! - * - * @param time The connect time to set. - */ - public void setConnectTime(long time) { - this.connectTime = time; - } - - /** - * This is only intended for use by SampleResultConverter! - * - * @param timeStamp The timeStamp to set. - */ - public void setTimeStamp(long timeStamp) { - this.timeStamp = timeStamp; - } - - - public void setURL(URL location) { - this.location = location; - } - - public URL getURL() { - return location; - } - - /** - * Get a String representation of the URL (if defined). - * - * @return ExternalForm of URL, or empty string if url is null - */ - public String getUrlAsString() { - return location == null ? "" : location.toExternalForm(); - } - - /** - * @return Returns the parent. - */ - public SampleResult getParent() { - return parent; - } - - /** - * @param parent The parent to set. - */ - public void setParent(SampleResult parent) { - this.parent = parent; - } - - public String getResultFileName() { - return resultFileName; - } - - public void setResultFileName(String resultFileName) { - this.resultFileName = resultFileName; - } - - public int getGroupThreads() { - return groupThreads; - } - - public void setGroupThreads(int n) { - this.groupThreads = n; - } - - public int getAllThreads() { - return allThreads; - } - - public void setAllThreads(int n) { - this.allThreads = n; - } - - // Bug 47394 - - /** - * Allow custom SampleSenders to drop unwanted assertionResults - */ - public void removeAssertionResults() { - this.assertionResults = null; - } - - /** - * Allow custom SampleSenders to drop unwanted subResults - */ - public void removeSubResults() { - this.subResults = null; - } - - /** - * Set the headers size in bytes - * - * @param size the number of bytes of the header - */ - public void setHeadersSize(int size) { - this.headersSize = size; - } - - /** - * Get the headers size in bytes - * - * @return the headers size - */ - public int getHeadersSize() { - return headersSize; - } - - /** - * @return the body size in bytes - * @deprecated replaced by getBodySizeAsLong() - */ - @Deprecated - public int getBodySize() { - return (int) getBodySizeAsLong(); - } - - /** - * @return the body size in bytes - */ - public long getBodySizeAsLong() { - return bodySize == 0 ? responseData.length : bodySize; - } - - /** - * @param bodySize the body size to set - */ - public void setBodySize(long bodySize) { - this.bodySize = bodySize; - } - - /** - * @param bodySize the body size to set - * @deprecated use setBodySize(long) - */ - @Deprecated - public void setBodySize(int bodySize) { - this.bodySize = bodySize; - } - - private static class NanoOffset extends Thread { - - private static volatile long nanoOffset; - - static long getNanoOffset() { - return nanoOffset; - } - - @Override - public void run() { - // Wait longer than a clock pulse (generally 10-15ms) - getOffset(30L); // Catch an early clock pulse to reduce slop. - while (true) { - getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks - } - } - - private static void getOffset(long wait) { - try { - TimeUnit.MILLISECONDS.sleep(wait); - long clock = System.currentTimeMillis(); - long nano = SampleResult.sampleNsClockInMs(); - nanoOffset = clock - nano; - } catch (InterruptedException ignore) { - // ignored - Thread.currentThread().interrupt(); - } - } - } - - /** - * @return the startNextThreadLoop - * @deprecated use {@link SampleResult#getTestLogicalAction()} - */ - @Deprecated - public boolean isStartNextThreadLoop() { - return testLogicalAction == TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; - } - - /** - * @param startNextThreadLoop the startNextLoop to set - * @deprecated use SampleResult#setTestLogicalAction(TestLogicalAction) - */ - @Deprecated - public void setStartNextThreadLoop(boolean startNextThreadLoop) { - if (startNextThreadLoop) { - testLogicalAction = TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; - } else { - testLogicalAction = TestLogicalAction.CONTINUE; - } - } - - /** - * Clean up cached data - */ - public void cleanAfterSample() { - this.responseDataAsString = null; - } - - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - throw new IllegalStateException("This should not happen"); - } - } - - @Override - public List getSearchableTokens() throws Exception { - List datasToSearch = new ArrayList<>(4); - datasToSearch.add(getSampleLabel()); - datasToSearch.add(getResponseDataAsString()); - datasToSearch.add(getRequestHeaders()); - datasToSearch.add(getResponseHeaders()); - return datasToSearch; - } - - /** - * @return boolean true if this SampleResult should not be sent to Listeners - */ - public boolean isIgnore() { - return ignore; - } - - /** - * Call this method to tell JMeter to ignore this SampleResult by Listeners - */ - public void setIgnore() { - this.ignore = true; - } - - /** - * @return String first non null assertion failure message if assertionResults is not null, null otherwise - */ - public String getFirstAssertionFailureMessage() { - String message = null; - AssertionResult[] results = getAssertionResults(); - - if (results != null) { - // Find the first non-null message - for (AssertionResult result : results) { - message = result.getFailureMessage(); - if (message != null) { - break; - } - } - } - return message; - } - - /** - * @return the testLogicalAction - */ - public TestLogicalAction getTestLogicalAction() { - return testLogicalAction; - } - - /** - * @param testLogicalAction the testLogicalAction to set - */ - public void setTestLogicalAction(TestLogicalAction testLogicalAction) { - this.testLogicalAction = testLogicalAction; - } - - public void addVars(String key, String value) { - if (this.vars == null) { - this.vars = new JMeterVariables(); - } - this.vars.put(key, value); - } - - public JMeterVariables getVars() { - return this.vars; - } -} From 5f8c7ef45d9e73ef4c4af79dea1d7d03c333d414 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sun, 27 Sep 2020 19:42:13 +0800 Subject: [PATCH 05/10] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E4=BF=AE=E5=A4=8D=E6=8F=90=E5=8F=96=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/jmeter/JMeterService.java | 1 - .../io/metersphere/api/jmeter/JMeterVars.java | 14 ++++++++++++-- backend/src/main/java/io/metersphere/xpack | 2 +- .../api/test/model/ScenarioModel.js | 19 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 45e177624d..91178520bb 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -42,7 +42,6 @@ public class JMeterService { Object scriptWrapper = SaveService.loadElement(is); HashTree testPlan = getHashTree(scriptWrapper); addBackendListener(testId, debugReportId, testPlan); - LocalRunner runner = new LocalRunner(testPlan); runner.run(); } catch (Exception e) { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java index 380fd0078f..8839acf52d 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -2,14 +2,24 @@ package io.metersphere.api.jmeter; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterVariables; +import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; public class JMeterVars { public static Map variables = new HashMap<>(); - public static void addVars(String testId, JMeterVariables vars) { - variables.put(testId, vars); + public static void addVars(String testId, JMeterVariables vars, String extract) { + JMeterVariables vs = new JMeterVariables(); + if (!StringUtils.isEmpty(extract) && vars != null) { + List extracts = Arrays.asList(extract.split(";")); + extracts.forEach(item -> { + vs.put(item, vars.get(item)); + }); + } + variables.put(testId, vs); } } diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index cf6b065263..c2dacf960c 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit cf6b06526324326a563d933e07118fac014a63b4 +Subproject commit c2dacf960cdb1ed35664bdd3432120b1203b73d8 diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index 4c247d0874..23f25856e9 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -1208,6 +1208,25 @@ class JMXGenerator { sampler.put(new JSR223PreProcessor(name, request.jsr223PreProcessor)); } if (request.jsr223PostProcessor && request.jsr223PostProcessor.script) { + // 增加一段后置脚步,获取 提取变量的内容 + let vars = []; + if (request.extract.regex) { + for (let i = 0; i < request.extract.regex.length; i++) { + vars.push(request.extract.regex[i].variable); + } + } + if (request.extract.json) { + for (let i = 0; i < request.extract.json.length; i++) { + vars.push(request.extract.json[i].variable); + } + } + if (request.extract.xpath) { + for (let i = 0; i < request.extract.xpath.length; i++) { + vars.push(request.extract.xpath[i].variable); + } + } + request.jsr223PostProcessor.script += "\n" + "io.metersphere.api.jmeter.JMeterVars.addVars(org.apache.jmeter.threads.JMeterContextService.getContext().getThread().getThreadName(),vars," + "\"" + vars.join(";") + "\"" + ");" + sampler.put(new JSR223PostProcessor(name, request.jsr223PostProcessor)); } } From 34a0f005f37088ed51362f28738b6e96778d9dd0 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 28 Sep 2020 10:48:05 +0800 Subject: [PATCH 06/10] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E5=AE=8C=E5=96=84=E6=8F=90=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/jmeter/APIBackendListenerClient.java | 6 +++--- .../src/main/java/io/metersphere/api/jmeter/JMeterVars.java | 4 ++-- .../src/business/components/api/test/model/ScenarioModel.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 11082e3c36..fca208b0c5 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -154,10 +154,10 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl responseResult.setResponseTime(result.getTime()); responseResult.setResponseMessage(result.getResponseMessage()); - if (JMeterVars.variables != null && JMeterVars.variables.get(result.getThreadName()) != null) { - JMeterVars.variables.get(result.getThreadName()).remove("TESTSTART.MS"); //去除系统变量 + if (JMeterVars.variables != null && JMeterVars.variables.get(result.hashCode()) != null) { + JMeterVars.variables.get(result.hashCode()).remove("TESTSTART.MS"); //去除系统变量 List vars = new LinkedList<>(); - JMeterVars.variables.get(result.getThreadName()).entrySet().parallelStream().reduce(vars, (first, second) -> { + JMeterVars.variables.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { first.add(second.getKey() + ":" + second.getValue()); return first; }, (first, second) -> { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java index 8839acf52d..36e8d128e5 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -10,9 +10,9 @@ import java.util.List; import java.util.Map; public class JMeterVars { - public static Map variables = new HashMap<>(); + public static Map variables = new HashMap<>(); - public static void addVars(String testId, JMeterVariables vars, String extract) { + public static void addVars(Integer testId, JMeterVariables vars, String extract) { JMeterVariables vs = new JMeterVariables(); if (!StringUtils.isEmpty(extract) && vars != null) { List extracts = Arrays.asList(extract.split(";")); diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index 23f25856e9..fd3cae3dd6 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -1225,7 +1225,7 @@ class JMXGenerator { vars.push(request.extract.xpath[i].variable); } } - request.jsr223PostProcessor.script += "\n" + "io.metersphere.api.jmeter.JMeterVars.addVars(org.apache.jmeter.threads.JMeterContextService.getContext().getThread().getThreadName(),vars," + "\"" + vars.join(";") + "\"" + ");" + request.jsr223PostProcessor.script += "\n" + "io.metersphere.api.jmeter.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + vars.join(";") + "\"" + ");" sampler.put(new JSR223PostProcessor(name, request.jsr223PostProcessor)); } From abcd4b299bbae6d86276d4b2a2c562448999c0d2 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 28 Sep 2020 15:02:12 +0800 Subject: [PATCH 07/10] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E9=87=8D=E5=86=99=E6=8F=90=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 7 +-- .../metersphere/api/jmeter/JMeterService.java | 1 + .../io/metersphere/api/jmeter/JMeterVars.java | 54 ++++++++++++++++--- .../api/test/model/ScenarioModel.js | 19 ------- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index fca208b0c5..abe071ec92 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -39,6 +39,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public String runMode = ApiRunMode.RUN.name(); + private JMeterVars variables; // 测试ID private String testId; @@ -154,10 +155,9 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl responseResult.setResponseTime(result.getTime()); responseResult.setResponseMessage(result.getResponseMessage()); - if (JMeterVars.variables != null && JMeterVars.variables.get(result.hashCode()) != null) { - JMeterVars.variables.get(result.hashCode()).remove("TESTSTART.MS"); //去除系统变量 + if (variables.get(result.hashCode()) != null) { List vars = new LinkedList<>(); - JMeterVars.variables.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { + variables.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { first.add(second.getKey() + ":" + second.getValue()); return first; }, (first, second) -> { @@ -199,6 +199,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl if (StringUtils.isBlank(this.runMode)) { this.runMode = ApiRunMode.RUN.name(); } + variables = new JMeterVars(); } private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 91178520bb..1ceb8ae6bf 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -41,6 +41,7 @@ public class JMeterService { try { Object scriptWrapper = SaveService.loadElement(is); HashTree testPlan = getHashTree(scriptWrapper); + JMeterVars.addJSR223PostProcessor(testPlan); addBackendListener(testId, debugReportId, testPlan); LocalRunner runner = new LocalRunner(testPlan); runner.run(); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java index 36e8d128e5..5e662112e3 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -1,25 +1,65 @@ package io.metersphere.api.jmeter; -import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.extractor.JSR223PostProcessor; +import org.apache.jmeter.extractor.RegexExtractor; +import org.apache.jmeter.extractor.XPath2Extractor; +import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.collections.HashTree; import org.springframework.util.StringUtils; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class JMeterVars { - public static Map variables = new HashMap<>(); + private static Map variables = new HashMap<>(); + + // 线程执行过程调用提取变量值 public static void addVars(Integer testId, JMeterVariables vars, String extract) { JMeterVariables vs = new JMeterVariables(); if (!StringUtils.isEmpty(extract) && vars != null) { List extracts = Arrays.asList(extract.split(";")); - extracts.forEach(item -> { + Optional.ofNullable(extracts).orElse(new ArrayList<>()).forEach(item -> { vs.put(item, vars.get(item)); }); + vs.remove("TESTSTART.MS"); // 标示变量移除 } variables.put(testId, vs); } + + // 递归处理所有请求,对有提取变量的增加后置脚本 + public static void addJSR223PostProcessor(HashTree tree) { + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy) { + StringJoiner extract = new StringJoiner(";"); + for (Object child : node.keySet()) { + if (child instanceof RegexExtractor) { + RegexExtractor regexExtractor = (RegexExtractor) child; + extract.add(regexExtractor.getRefName()); + } else if (child instanceof XPath2Extractor) { + XPath2Extractor regexExtractor = (XPath2Extractor) child; + extract.add(regexExtractor.getRefName()); + } else if (child instanceof JSONPostProcessor) { + JSONPostProcessor regexExtractor = (JSONPostProcessor) child; + extract.add(regexExtractor.getRefNames()); + } + } + if (Optional.ofNullable(extract).orElse(extract).length() > 0) { + JSR223PostProcessor shell = new JSR223PostProcessor(); + shell.setEnabled(true); + shell.setProperty("script", "io.metersphere.api.jmeter.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + extract.toString() + "\"" + ");"); + node.add(shell); + } + } + if (node != null) { + addJSR223PostProcessor(node); + } + } + } + + public JMeterVariables get(Integer key) { + return variables.get(key); + } } diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index fd3cae3dd6..4c247d0874 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -1208,25 +1208,6 @@ class JMXGenerator { sampler.put(new JSR223PreProcessor(name, request.jsr223PreProcessor)); } if (request.jsr223PostProcessor && request.jsr223PostProcessor.script) { - // 增加一段后置脚步,获取 提取变量的内容 - let vars = []; - if (request.extract.regex) { - for (let i = 0; i < request.extract.regex.length; i++) { - vars.push(request.extract.regex[i].variable); - } - } - if (request.extract.json) { - for (let i = 0; i < request.extract.json.length; i++) { - vars.push(request.extract.json[i].variable); - } - } - if (request.extract.xpath) { - for (let i = 0; i < request.extract.xpath.length; i++) { - vars.push(request.extract.xpath[i].variable); - } - } - request.jsr223PostProcessor.script += "\n" + "io.metersphere.api.jmeter.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + vars.join(";") + "\"" + ");" - sampler.put(new JSR223PostProcessor(name, request.jsr223PostProcessor)); } } From bc94f1cedbb42876f1d888aec87a631c4b6bb642 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 28 Sep 2020 15:41:38 +0800 Subject: [PATCH 08/10] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E4=BF=9D=E6=8C=81=E7=BA=BF=E7=A8=8B=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E7=9A=84=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 7 +++---- .../java/io/metersphere/api/jmeter/JMeterVars.java | 10 +++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index abe071ec92..61682d86bd 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -39,7 +39,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public String runMode = ApiRunMode.RUN.name(); - private JMeterVars variables; // 测试ID private String testId; @@ -155,9 +154,9 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl responseResult.setResponseTime(result.getTime()); responseResult.setResponseMessage(result.getResponseMessage()); - if (variables.get(result.hashCode()) != null) { + if (JMeterVars.get(result.hashCode()) != null) { List vars = new LinkedList<>(); - variables.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { + JMeterVars.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { first.add(second.getKey() + ":" + second.getValue()); return first; }, (first, second) -> { @@ -168,6 +167,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl return first; }); responseResult.setVars(StringUtils.join(vars, "\n")); + JMeterVars.remove(result.hashCode()); } for (AssertionResult assertionResult : result.getAssertionResults()) { ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult); @@ -199,7 +199,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl if (StringUtils.isBlank(this.runMode)) { this.runMode = ApiRunMode.RUN.name(); } - variables = new JMeterVars(); } private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java index 5e662112e3..6bcd2150f7 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -13,6 +13,9 @@ import java.util.*; public class JMeterVars { + private JMeterVars() { } + + // 数据和线程变量保持一致 private static Map variables = new HashMap<>(); // 线程执行过程调用提取变量值 @@ -59,7 +62,12 @@ public class JMeterVars { } } - public JMeterVariables get(Integer key) { + public static JMeterVariables get(Integer key) { return variables.get(key); } + + public static void remove(Integer key) { + variables.remove(key); + } + } From 2e70bab36294fcfe268d5a7baf1d1729ccc41d53 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 28 Sep 2020 15:43:41 +0800 Subject: [PATCH 09/10] =?UTF-8?q?style(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E5=8E=BB=E9=99=A4=E5=A4=9A=E4=BD=99import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/jmeter/APIBackendListenerClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 61682d86bd..67173ef791 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -13,7 +13,6 @@ import io.metersphere.notice.service.NoticeService; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleResult; -import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; From 5c83c7136ec66bafa6e79be0fdd8d3b310c16485 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 9 Oct 2020 10:41:05 +0800 Subject: [PATCH 10/10] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E6=89=A7=E8=A1=8C=E6=B2=A1=E6=9C=89=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E6=97=B6=E6=8F=90=E5=8F=96=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E4=BF=9D=E6=8A=A4=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/io/metersphere/api/jmeter/JMeterVars.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java index 6bcd2150f7..91dfb1edac 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -13,7 +13,8 @@ import java.util.*; public class JMeterVars { - private JMeterVars() { } + private JMeterVars() { + } // 数据和线程变量保持一致 private static Map variables = new HashMap<>(); @@ -24,7 +25,7 @@ public class JMeterVars { if (!StringUtils.isEmpty(extract) && vars != null) { List extracts = Arrays.asList(extract.split(";")); Optional.ofNullable(extracts).orElse(new ArrayList<>()).forEach(item -> { - vs.put(item, vars.get(item)); + vs.put(item, vars.get(item) == null ? "" : vars.get(item)); }); vs.remove("TESTSTART.MS"); // 标示变量移除 }