You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2017/10/18 20:11:10 UTC
svn commit: r1812568 - in /jmeter/trunk: bin/
src/components/org/apache/jmeter/extractor/
src/components/org/apache/jmeter/extractor/gui/
test/src/org/apache/jmeter/extractor/ xdocs/ xdocs/usermanual/
Author: pmouawad
Date: Wed Oct 18 20:11:09 2017
New Revision: 1812568
URL: http://svn.apache.org/viewvc?rev=1812568&view=rev
Log:
Bug 60213 - Boundary based extractor
Bugzilla Id: 60213
Added:
jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java (with props)
jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java (with props)
jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java (with props)
Modified:
jmeter/trunk/bin/saveservice.properties
jmeter/trunk/xdocs/changes.xml
jmeter/trunk/xdocs/usermanual/component_reference.xml
Modified: jmeter/trunk/bin/saveservice.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/saveservice.properties?rev=1812568&r1=1812567&r2=1812568&view=diff
==============================================================================
--- jmeter/trunk/bin/saveservice.properties (original)
+++ jmeter/trunk/bin/saveservice.properties Wed Oct 18 20:11:09 2017
@@ -64,7 +64,8 @@
# 2.9 = 2.14
# 3.1 = 3.1
# 3.2 = 3.2
-_version=3.2
+# 3.4 = 3.4
+_version=3.4
#
#
# Character set encoding used to read and write JMeter XML files and CSV results
@@ -99,6 +100,8 @@ BeanShellPreProcessor=org.apache.jmeter.
BeanShellSampler=org.apache.jmeter.protocol.java.sampler.BeanShellSampler
BeanShellSamplerGui=org.apache.jmeter.protocol.java.control.gui.BeanShellSamplerGui
BeanShellTimer=org.apache.jmeter.timers.BeanShellTimer
+BoundaryExtractor=org.apache.jmeter.extractor.BoundaryExtractor
+BoundaryExtractorGui=org.apache.jmeter.extractor.gui.BoundaryExtractorGui
BSFAssertion=org.apache.jmeter.assertions.BSFAssertion
BSFListener=org.apache.jmeter.visualizers.BSFListener
BSFPreProcessor=org.apache.jmeter.modifiers.BSFPreProcessor
Added: jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java?rev=1812568&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java Wed Oct 18 20:11:09 2017
@@ -0,0 +1,393 @@
+/*
+ * 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.lang3.StringEscapeUtils;
+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.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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+public class BoundaryExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable {
+
+ private static final Logger log = LoggerFactory.getLogger(BoundaryExtractor.class);
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String REFNAME = "BoundaryExtractor.refname"; // $NON-NLS-1$
+
+ private static final String MATCH_NUMBER = "BoundaryExtractor.match_number"; // $NON-NLS-1$
+
+ private static final String L_BOUNDARY = "BoundaryExtractor.lboundary"; // $NON-NLS-1$
+
+ private static final String R_BOUNDARY = "BoundaryExtractor.rboundary"; // $NON-NLS-1$
+
+ private static final String DEFAULT_EMPTY_VALUE = "BoundaryExtractor.default_empty_value"; // $NON-NLS-1$
+
+ private static final String DEFAULT = "BoundaryExtractor.default"; // $NON-NLS-1$
+
+ private static final String REF_MATCH_NR = "_matchNr"; // $NON-NLS-1$
+
+ private static final String UNDERSCORE = "_"; // $NON-NLS-1$
+
+ // What to match against. N.B. do not change the string value or test plans will break!
+ private static final String MATCH_AGAINST = "BoundaryExtractor.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$
+
+ /**
+ * Parses the response data using Boundaries and saving the results
+ * into variables for use later in the test.
+ *
+ * @see org.apache.jmeter.processor.PostProcessor#process()
+ */
+ @Override
+ public void process() {
+ JMeterContext context = getThreadContext();
+ SampleResult previousResult = context.getPreviousResult();
+ if (previousResult == null) {
+ return;
+ }
+ if(log.isDebugEnabled()) {
+ log.debug("Boundary Extractor {}: processing result", getName());
+ }
+ if(StringUtils.isEmpty(getLeftBoundary()) ||
+ StringUtils.isEmpty(getRightBoundary()) ||
+ StringUtils.isEmpty(getRefName())
+ ) {
+ throw new IllegalArgumentException("One of the mandatory properties is missing in Boundary Extractor:"+
+ getName());
+ }
+ // 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);
+ }
+
+ try {
+ List<String> matches =
+ extractMatchingStrings(vars, getLeftBoundary(), getRightBoundary(), matchNumber, previousResult);
+ 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) {
+ if (log.isWarnEnabled()) {
+ log.warn("{}: Could not parse number: '{}'.", getName(), prevString);
+ }
+ }
+ }
+ int matchCount=0;// Number of refName_n variable sets to keep
+ String match;
+ if (matchNumber >= 0) {// Original match behaviour
+ match = getCorrectMatch(matches, matchNumber);
+ if (match != null) {
+ vars.put(refName, match);
+ }
+ } else // < 0 means we save all the 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 refNameN = new StringBuilder(refName).append(UNDERSCORE).append(i).toString();
+ vars.put(refNameN, match);
+ }
+ }
+ }
+ // Remove any left-over variables
+ for (int i = matchCount + 1; i <= prevCount; i++) {
+ final String refNameN = new StringBuilder(refName).append(UNDERSCORE).append(i).toString();
+ vars.remove(refNameN);
+ }
+ } catch (RuntimeException e) {
+ if (log.isWarnEnabled()) {
+ log.warn("{}: Error while generating result. {}", getName(), e.toString());
+ }
+ }
+ }
+
+ 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;
+ }
+ /**
+ * Grab the appropriate result from the list.
+ *
+ * @param matches
+ * list of matches
+ * @param entry
+ * the entry number in the list
+ * @return MatchResult
+ */
+ private String getCorrectMatch(List<String> 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);
+ }
+
+ private List<String> extractMatchingStrings(JMeterVariables vars,
+ String leftBoundary, String rightBoundary, int matchNumber,
+ SampleResult previousResult) {
+ int found = 0;
+ List<String> result = new ArrayList<>();
+ if (isScopeVariable()){
+ String inputString=vars.get(getVariableName());
+ if(!StringUtils.isEmpty(inputString)) {
+ extract(leftBoundary, rightBoundary, matchNumber, inputString, result, found);
+ } else {
+ if(inputString==null) {
+ if (log.isWarnEnabled()) {
+ log.warn("No variable '{}' found to process by Boundary Extractor '{}', skipping processing",
+ getVariableName(), getName());
+ }
+ }
+ return Collections.emptyList();
+ }
+ } else {
+ List<SampleResult> sampleList = getSampleList(previousResult);
+ for (SampleResult sr : sampleList) {
+ String inputString = getInputString(sr);
+ found = extract(leftBoundary, rightBoundary, matchNumber, inputString, result, found);
+ if (matchNumber > 0 && found == matchNumber){// no need to process further
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ *
+ * @param leftBoundary
+ * @param rightBoundary
+ * @param matchNumber
+ * @param inputString
+ * @param result
+ * @param found
+ * @return int found updated
+ */
+ private int extract(String leftBoundary, String rightBoundary, int matchNumber, String inputString,
+ List<String> result, int found) {
+ int startIndex = -1;
+ int endIndex = -1;
+ List<String> matches = new ArrayList<>();
+ while(true) {
+ startIndex = inputString.indexOf(leftBoundary, startIndex+1);
+ if(startIndex >= 0) {
+ endIndex = inputString.indexOf(rightBoundary, startIndex+leftBoundary.length());
+ if(endIndex >= 0) {
+ result.add(inputString.substring(startIndex+leftBoundary.length(), endIndex));
+ } else {
+ result.add(inputString.substring(startIndex+leftBoundary.length()));
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ for (String element : matches) {
+ if (matchNumber <= 0 || found != matchNumber) {
+ result.add(element);
+ found++;
+ } else {
+ break;
+ }
+ }
+ return found;
+ }
+
+ public void setRefName(String refName) {
+ setProperty(REFNAME, refName);
+ }
+
+ public String getRefName() {
+ return getPropertyAsString(REFNAME);
+ }
+
+ /**
+ * Set which Match to use. This can be any positive number, indicating the
+ * exact match to use, or <code>0</code>, which is interpreted as meaning random.
+ *
+ * @param matchNumber The number of the match to be used
+ */
+ public void setMatchNumber(int matchNumber) {
+ setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber));
+ }
+
+ public void setMatchNumber(String matchNumber) {
+ setProperty(MATCH_NUMBER, matchNumber);
+ }
+
+ public int getMatchNumber() {
+ return getPropertyAsInt(MATCH_NUMBER);
+ }
+
+ public String getMatchNumberAsString() {
+ return getPropertyAsString(MATCH_NUMBER);
+ }
+
+ public void setLeftBoundary(String leftBoundary) {
+ setProperty(L_BOUNDARY, leftBoundary);
+ }
+
+ public String getLeftBoundary() {
+ return getPropertyAsString(L_BOUNDARY);
+ }
+
+ public void setRightBoundary(String rightBoundary) {
+ setProperty(R_BOUNDARY, rightBoundary);
+ }
+
+ public String getRightBoundary() {
+ return getPropertyAsString(R_BOUNDARY);
+ }
+
+ /**
+ * 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, defaultValue);
+ }
+
+ /**
+ * @param defaultEmptyValue boolean set value to "" if not found
+ */
+ public void setDefaultEmptyValue(boolean defaultEmptyValue) {
+ setProperty(DEFAULT_EMPTY_VALUE, defaultEmptyValue);
+ }
+
+ /**
+ * Get the default value for the variable if no matches are found
+ * @return The default value for the variable
+ */
+ public String getDefaultValue() {
+ return getPropertyAsString(DEFAULT);
+ }
+
+ /**
+ * @return boolean set value to "" if not found
+ */
+ public boolean isEmptyDefaultValue() {
+ return getPropertyAsBoolean(DEFAULT_EMPTY_VALUE);
+ }
+
+
+ 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);
+ }
+}
Propchange: jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/src/components/org/apache/jmeter/extractor/BoundaryExtractor.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java?rev=1812568&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java Wed Oct 18 20:11:09 2017
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.extractor.gui;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import org.apache.jmeter.extractor.BoundaryExtractor;
+import org.apache.jmeter.extractor.RegexExtractor;
+import org.apache.jmeter.processor.gui.AbstractPostProcessorGui;
+import org.apache.jmeter.testelement.AbstractScopedTestElement;
+import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.gui.JLabeledTextField;
+
+/**
+ * Boundary Extractor Post-Processor GUI
+ * @since 4.0
+ */
+public class BoundaryExtractorGui extends AbstractPostProcessorGui {
+ private static final long serialVersionUID = 240L;
+
+ private JLabeledTextField leftBoundaryField;
+
+ private JLabeledTextField rightBoundaryField;
+
+ private JLabeledTextField defaultField;
+
+ private JLabeledTextField matchNumberField;
+
+ private JLabeledTextField refNameField;
+
+ private JCheckBox emptyDefaultValue;
+
+ private JRadioButton useBody;
+
+ private JRadioButton useUnescapedBody;
+
+ private JRadioButton useBodyAsDocument;
+
+ private JRadioButton useHeaders;
+
+ private JRadioButton useRequestHeaders;
+
+ private JRadioButton useURL;
+
+ private JRadioButton useCode;
+
+ private JRadioButton useMessage;
+
+ private ButtonGroup group;
+
+ public BoundaryExtractorGui() {
+ super();
+ init();
+ }
+
+ @Override
+ public String getLabelResource() {
+ return "boundaryextractor_title"; //$NON-NLS-1$
+ }
+
+ @Override
+ public void configure(TestElement el) {
+ super.configure(el);
+ if (el instanceof BoundaryExtractor){
+ BoundaryExtractor boundary = (BoundaryExtractor) el;
+ showScopeSettings(boundary, true);
+ useHeaders.setSelected(boundary.useHeaders());
+ useRequestHeaders.setSelected(boundary.useRequestHeaders());
+ useBody.setSelected(boundary.useBody());
+ useUnescapedBody.setSelected(boundary.useUnescapedBody());
+ useBodyAsDocument.setSelected(boundary.useBodyAsDocument());
+ useURL.setSelected(boundary.useUrl());
+ useCode.setSelected(boundary.useCode());
+ useMessage.setSelected(boundary.useMessage());
+ leftBoundaryField.setText(boundary.getLeftBoundary());
+ rightBoundaryField.setText(boundary.getRightBoundary());
+ defaultField.setText(boundary.getDefaultValue());
+ emptyDefaultValue.setSelected(boundary.isEmptyDefaultValue());
+ matchNumberField.setText(boundary.getMatchNumberAsString());
+ refNameField.setText(boundary.getRefName());
+ }
+ }
+
+ /**
+ * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()
+ */
+ @Override
+ public TestElement createTestElement() {
+ AbstractScopedTestElement extractor = new BoundaryExtractor();
+ modifyTestElement(extractor);
+ return extractor;
+ }
+
+ /**
+ * Modifies a given TestElement to mirror the data in the gui components.
+ *
+ * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement)
+ */
+ @Override
+ public void modifyTestElement(TestElement extractor) {
+ super.configureTestElement(extractor);
+ if (extractor instanceof BoundaryExtractor) {
+ BoundaryExtractor boundary = (BoundaryExtractor) extractor;
+ saveScopeSettings(boundary);
+ boundary.setUseField(group.getSelection().getActionCommand());
+ boundary.setRefName(refNameField.getText());
+ boundary.setLeftBoundary(leftBoundaryField.getText());
+ boundary.setRightBoundary(rightBoundaryField.getText());
+ boundary.setDefaultValue(defaultField.getText());
+ boundary.setDefaultEmptyValue(emptyDefaultValue.isSelected());
+ boundary.setMatchNumber(matchNumberField.getText());
+ }
+ }
+
+ /**
+ * Implements JMeterGUIComponent.clearGui
+ */
+ @Override
+ public void clearGui() {
+ super.clearGui();
+ leftBoundaryField.setText(""); //$NON-NLS-1$
+ rightBoundaryField.setText(""); //$NON-NLS-1$
+ defaultField.setText(""); //$NON-NLS-1$
+ refNameField.setText(""); //$NON-NLS-1$
+ emptyDefaultValue.setSelected(false);
+ matchNumberField.setText(""); //$NON-NLS-1$
+ }
+
+ private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
+ setLayout(new BorderLayout());
+ setBorder(makeBorder());
+
+ Box box = Box.createVerticalBox();
+ box.add(makeTitlePanel());
+ box.add(createScopePanel(true));
+ box.add(makeSourcePanel());
+ add(box, BorderLayout.NORTH);
+ add(makeParameterPanel(), BorderLayout.CENTER);
+ }
+
+ private JPanel makeSourcePanel() {
+ JPanel panel = new JPanel();
+ panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("regex_source"))); //$NON-NLS-1$
+
+ useBody = new JRadioButton(JMeterUtils.getResString("regex_src_body")); //$NON-NLS-1$
+ useUnescapedBody = new JRadioButton(JMeterUtils.getResString("regex_src_body_unescaped")); //$NON-NLS-1$
+ useBodyAsDocument = new JRadioButton(JMeterUtils.getResString("regex_src_body_as_document")); //$NON-NLS-1$
+ useHeaders = new JRadioButton(JMeterUtils.getResString("regex_src_hdrs")); //$NON-NLS-1$
+ useRequestHeaders = new JRadioButton(JMeterUtils.getResString("regex_src_hdrs_req")); //$NON-NLS-1$
+ useURL = new JRadioButton(JMeterUtils.getResString("regex_src_url")); //$NON-NLS-1$
+ useCode = new JRadioButton(JMeterUtils.getResString("assertion_code_resp")); //$NON-NLS-1$
+ useMessage = new JRadioButton(JMeterUtils.getResString("assertion_message_resp")); //$NON-NLS-1$
+
+ group = new ButtonGroup();
+ group.add(useBody);
+ group.add(useUnescapedBody);
+ group.add(useBodyAsDocument);
+ group.add(useHeaders);
+ group.add(useRequestHeaders);
+ group.add(useURL);
+ group.add(useCode);
+ group.add(useMessage);
+
+ panel.add(useBody);
+ panel.add(useUnescapedBody);
+ panel.add(useBodyAsDocument);
+ panel.add(useHeaders);
+ panel.add(useRequestHeaders);
+ panel.add(useURL);
+ panel.add(useCode);
+ panel.add(useMessage);
+
+ useBody.setSelected(true);
+
+ // So we know which button is selected
+ useBody.setActionCommand(RegexExtractor.USE_BODY);
+ useUnescapedBody.setActionCommand(RegexExtractor.USE_BODY_UNESCAPED);
+ useBodyAsDocument.setActionCommand(RegexExtractor.USE_BODY_AS_DOCUMENT);
+ useHeaders.setActionCommand(RegexExtractor.USE_HDRS);
+ useRequestHeaders.setActionCommand(RegexExtractor.USE_REQUEST_HDRS);
+ useURL.setActionCommand(RegexExtractor.USE_URL);
+ useCode.setActionCommand(RegexExtractor.USE_CODE);
+ useMessage.setActionCommand(RegexExtractor.USE_MESSAGE);
+
+ return panel;
+ }
+
+ private JPanel makeParameterPanel() {
+ leftBoundaryField = new JLabeledTextField(JMeterUtils.getResString("boundaryextractor_leftboundary_field")); //$NON-NLS-1$
+ rightBoundaryField = new JLabeledTextField(JMeterUtils.getResString("boundaryextractor_rightboundary_field")); //$NON-NLS-1$
+ refNameField = new JLabeledTextField(JMeterUtils.getResString("ref_name_field")); //$NON-NLS-1$
+ matchNumberField = new JLabeledTextField(JMeterUtils.getResString("match_num_field")); //$NON-NLS-1$
+
+ JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ initConstraints(gbc);
+ addField(panel, refNameField, gbc);
+ resetContraints(gbc);
+ addField(panel, leftBoundaryField, gbc);
+ resetContraints(gbc);
+ addField(panel, rightBoundaryField, gbc);
+ resetContraints(gbc);
+ addField(panel, matchNumberField, gbc);
+ resetContraints(gbc);
+ gbc.weighty = 1;
+
+ defaultField = new JLabeledTextField(JMeterUtils.getResString("default_value_field")); //$NON-NLS-1$
+ List<JComponent> item = defaultField.getComponentList();
+ panel.add(item.get(0), gbc.clone());
+ JPanel p = new JPanel(new BorderLayout());
+ p.add(item.get(1), BorderLayout.WEST);
+ emptyDefaultValue = new JCheckBox(JMeterUtils.getResString("boundaryextractor_empty_default_value"));
+ emptyDefaultValue.addItemListener(evt -> {
+ if(emptyDefaultValue.isSelected()) {
+ defaultField.setText("");
+ }
+ defaultField.setEnabled(!emptyDefaultValue.isSelected());
+ });
+ p.add(emptyDefaultValue, BorderLayout.CENTER);
+ gbc.gridx++;
+ gbc.weightx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ panel.add(p, gbc.clone());
+
+ return panel;
+ }
+
+ private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) {
+ List<JComponent> item = field.getComponentList();
+ panel.add(item.get(0), gbc.clone());
+ gbc.gridx++;
+ gbc.weightx = 1;
+ gbc.fill=GridBagConstraints.HORIZONTAL;
+ panel.add(item.get(1), gbc.clone());
+ }
+
+ // Next line
+ private void resetContraints(GridBagConstraints gbc) {
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.weightx = 0;
+ gbc.fill=GridBagConstraints.NONE;
+ }
+
+ private void initConstraints(GridBagConstraints gbc) {
+ gbc.anchor = GridBagConstraints.NORTHWEST;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 1;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.weightx = 0;
+ gbc.weighty = 0;
+ }
+}
Propchange: jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/BoundaryExtractorGui.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java?rev=1812568&view=auto
==============================================================================
--- jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java (added)
+++ jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java Wed Oct 18 20:11:09 2017
@@ -0,0 +1,168 @@
+/*
+ * 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 static org.junit.Assert.assertThat;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jmeter.threads.JMeterContext;
+import org.apache.jmeter.threads.JMeterContextService;
+import org.apache.jmeter.threads.JMeterVariables;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestBoundaryExtractor {
+
+ private static final String VAR_NAME = "varName";
+ private BoundaryExtractor extractor;
+
+ private SampleResult result;
+
+ private JMeterVariables vars;
+
+ private JMeterContext context;
+
+ @Before
+ public void setUp() {
+ context = JMeterContextService.getContext();
+ extractor = new BoundaryExtractor();
+ extractor.setThreadContext(context);// This would be done by the run
+ // command
+ extractor.setRefName("regVal");
+ result = new SampleResult();
+ String data = "zazzd azd azd azd <t>value</t>azdazd azd azd";
+ result.setResponseData(data, null);
+ result.setResponseHeaders("Header1: Value1\nHeader2: Value2");
+ result.setResponseCode("abcd");
+ result.setResponseMessage("The quick brown fox");
+ vars = new JMeterVariables();
+ context.setVariables(vars);
+ context.setPreviousResult(result);
+ }
+
+ @Test
+ public void testProcessAllElementsOneMatch() {
+ BoundaryExtractor processor = setupProcessor(context, "-1");
+ JMeterVariables vars = new JMeterVariables();
+ processor.setDefaultValue("NONE");
+ processor.setLeftBoundary("<t>");
+ processor.setRightBoundary("</t>");
+ processor.setRefName("varname");
+ processor.setScopeVariable("contentvar");
+ context.setVariables(vars);
+ vars.put("contentvar", "zazzd azd azd azd <t>one</t>azdazd azd azd");
+ processor.process();
+ assertThat(vars.get("varname"), CoreMatchers.is("NONE"));
+ assertThat(vars.get("varname_1"), CoreMatchers.is("one"));
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is("1"));
+ }
+
+ @Test
+ public void testProcessAllElementsMultipleMatches() {
+ BoundaryExtractor processor = setupProcessor(context, "-1");
+ JMeterVariables vars = new JMeterVariables();
+ processor.setDefaultValue("NONE");
+ processor.setLeftBoundary("<t>");
+ processor.setRightBoundary("</t>");
+ processor.setRefName("varname");
+ processor.setScopeVariable("contentvar");
+ context.setVariables(vars);
+ vars.put("contentvar", "zazzd azd azd azd \r\n<t>one</t>azdazd \r\nazd <t>two</t>azd");
+ processor.process();
+ assertThat(vars.get("varname_1"), CoreMatchers.is("one"));
+ assertThat(vars.get("varname_2"), CoreMatchers.is("two"));
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is("2"));
+ }
+
+ @Test
+ public void testProcessRandomElementMultipleMatches() {
+ BoundaryExtractor processor = setupProcessor(context, "0");
+ JMeterVariables vars = new JMeterVariables();
+ processor.setDefaultValue("NONE");
+ processor.setLeftBoundary("<t>");
+ processor.setRightBoundary("</t>");
+ processor.setRefName("varname");
+ processor.setScopeVariable("contentvar");
+ context.setVariables(vars);
+ vars.put("contentvar", "zazzd azd azd azd \r\n<t>one</t>azdazd \r\nazd <t>two</t>azd");
+ processor.process();
+ assertThat(vars.get("varname"),
+ CoreMatchers.is(CoreMatchers.anyOf(CoreMatchers.is("one"), CoreMatchers.is("two"))));
+ assertThat(vars.get("varname_1"), CoreMatchers.is(CoreMatchers.nullValue()));
+ assertThat(vars.get("varname_2"), CoreMatchers.is(CoreMatchers.nullValue()));
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is(CoreMatchers.nullValue()));
+ }
+
+ @Test
+ public void testCaseEmptyResponse() {
+ BoundaryExtractor processor = setupProcessor(context, "-1");
+ JMeterVariables vars = new JMeterVariables();
+ processor.setDefaultValue("NONE");
+ processor.setLeftBoundary("<t>");
+ processor.setRightBoundary("</t>");
+ processor.setRefName("varname");
+ processor.setScopeVariable("contentvar");
+ context.setVariables(vars);
+ vars.put("contentvar", "zazzd azd azd azd \r\n<t>one</t>azdazd \r\nazd <t>two</t>azd");
+ processor.process();
+ assertThat(vars.get("varname_1"), CoreMatchers.is("one"));
+ assertThat(vars.get("varname_2"), CoreMatchers.is("two"));
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is("2"));
+ vars.put("contentvar", "");
+ processor.process();
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is("0"));
+ assertThat(vars.get("varname_1"), CoreMatchers.is(CoreMatchers.nullValue()));
+ assertThat(vars.get("varname_2"), CoreMatchers.is(CoreMatchers.nullValue()));
+ }
+
+ @Test
+ public void testCaseMatchOneWithZero() {
+ BoundaryExtractor processor = setupProcessor(context, "-1");
+ JMeterVariables vars = new JMeterVariables();
+ processor.setDefaultValue("NONE");
+ processor.setLeftBoundary("<t>");
+ processor.setRightBoundary("</t>");
+ processor.setRefName("varname");
+ processor.setScopeVariable("contentvar");
+ context.setVariables(vars);
+ vars.put("contentvar", "zazzd azd azd azd \r\n<t>one</t>azdazd \r\nazd <t>two</t>azd");
+ processor.process();
+ assertThat(vars.get("varname_1"), CoreMatchers.is("one"));
+ assertThat(vars.get("varname_2"), CoreMatchers.is("two"));
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is("2"));
+ vars.put("contentvar", "zaddazddad bvefvdv azd azvddfvfvd \r\n<t>A</t>azdazd \r\nfvdfv <t>B</t>azd");
+ processor.setMatchNumber("0");
+ processor.process();
+ assertThat(vars.get("varname"), CoreMatchers.is(CoreMatchers.anyOf(CoreMatchers.is("A"), CoreMatchers.is("B"))));
+ assertThat(vars.get("varname_matchNr"), CoreMatchers.is(CoreMatchers.nullValue()));
+ assertThat(vars.get("varname_1"), CoreMatchers.is(CoreMatchers.nullValue()));
+ assertThat(vars.get("varname_2"), CoreMatchers.is(CoreMatchers.nullValue()));
+ }
+
+ private BoundaryExtractor setupProcessor(JMeterContext context,
+ String matchNumber) {
+ BoundaryExtractor processor = new BoundaryExtractor();
+ processor.setThreadContext(context);
+ processor.setRefName(VAR_NAME);
+ processor.setMatchNumber(matchNumber);
+ return processor;
+ }
+
+}
Propchange: jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/test/src/org/apache/jmeter/extractor/TestBoundaryExtractor.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1812568&r1=1812567&r2=1812568&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Wed Oct 18 20:11:09 2017
@@ -97,6 +97,7 @@ Summary
<h3>Timers, Assertions, Config, Pre- & Post-Processors</h3>
<ul>
+ <li><bug>60213</bug>Boundary based extractor</li>
</ul>
<h3>Functions</h3>
Modified: jmeter/trunk/xdocs/usermanual/component_reference.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/usermanual/component_reference.xml?rev=1812568&r1=1812567&r2=1812568&view=diff
==============================================================================
--- jmeter/trunk/xdocs/usermanual/component_reference.xml (original)
+++ jmeter/trunk/xdocs/usermanual/component_reference.xml Wed Oct 18 20:11:09 2017
@@ -6153,6 +6153,95 @@ It will allow you to extract in a very e
<figure width="855" height="276" image="json-post-processor.png">JSON PostProcessor</figure>
</component>
+<component name="Boundary Extractor" index="§-num;.8.10" width="1127" height="277" screenshot="boundary_extractor.png">
+<description><p>Allows the user to extract values from a server response using left and right boundaries. As a post-processor,
+this element will execute after each Sample request in its scope, testing the boundaries, extracting the requested values,
+generate the template string, and store the result into the given variable name.</p></description>
+<properties>
+ <property name="Name" required="">Descriptive name for this element that is shown in the tree.</property>
+ <property name="Apply to:" required="Yes">
+ This is for use with samplers that can generate sub-samples,
+ e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller.
+ <ul>
+ <li><code>Main sample only</code> - only applies to the main sample</li>
+ <li><code>Sub-samples only</code> - only applies to the sub-samples</li>
+ <li><code>Main sample and sub-samples</code> - applies to both.</li>
+ <li><code>JMeter Variable</code> - assertion is to be applied to the contents of the named variable</li>
+ </ul>
+ Matching is applied to all qualifying samples in turn.
+ For example if there is a main sample and 3 sub-samples, each of which contains a single match test,
+ (i.e. 4 matches in total).
+ For match number = <code>3</code>, Sub-samples only, the extractor will match the 3<sup>rd</sup> sub-sample.
+ For match number = <code>3</code>, Main sample and sub-samples, the extractor will match the 2<sup>nd</sup> sub-sample (1<sup>st</sup> match is main sample).
+ For match number = <code>0</code> or negative, all qualifying samples will be processed.
+ For match number > <code>0</code>, matching will stop as soon as enough matches have been found.
+ </property>
+ <property name="Field to check" required="Yes">
+ The following fields can be checked:
+ <ul>
+ <li><code>Body</code> - the body of the response, e.g. the content of a web-page (excluding headers)</li>
+ <li><code>Body (unescaped)</code> - the body of the response, with all Html escape codes replaced.
+ Note that Html escapes are processed without regard to context, so some incorrect substitutions
+ may be made.
+ <note>Note that this option highly impacts performances, so use it only when absolutely necessary and be aware of its impacts</note>
+ </li>
+ <li><code>Body as a Document</code> - the extract text from various type of documents via Apache Tika (see <complink name="View Results Tree"/> Document view section).
+ <note>Note that the Body as a Document option can impact performances, so ensure it is OK for your test</note>
+ </li>
+ <li><code>Request Headers</code> - may not be present for non-HTTP samples</li>
+ <li><code>Response Headers</code> - may not be present for non-HTTP samples</li>
+ <li><code>URL</code></li>
+ <li><code>Response Code</code> - e.g. <code>200</code></li>
+ <li><code>Response Message</code> - e.g. <code>OK</code></li>
+ </ul>
+ Headers can be useful for HTTP samples; it may not be present for other sample types.
+ </property>
+ <property name="Reference Name" required="Yes">The name of the JMeter variable in which to store the result. Also note that each group is stored as <code>[refname]_g#</code>, where <code>[refname]</code> is the string you entered as the reference name, and <code>#</code> is the group number, where group <code>0</code> is the entire match, group <code>1</code> is the match from the first set of parentheses, etc.</property>
+ <property name="Left Boundary" required="Yes">Left boundary of value to find</property>
+ <property name="Right Boundary" required="Yes">Left boundary of value to find</property>
+ <property name="Match No. (0 for Random)" required="Yes">Indicates which match to use. The boundaries may match multiple times.
+ <ul>
+ <li>Use a value of zero to indicate JMeter should choose a match at random.</li>
+ <li>A positive number N means to select the n<sup>th</sup> match.</li>
+ <li> Negative numbers are used in conjunction with the <complink name="ForEach Controller"/> - see below.</li>
+ </ul>
+ </property>
+ <property name="Default Value" required="No, but recommended">
+ If the boundaries do not match, then the reference variable will be set to the default value.
+ This is particularly useful for debugging tests. If no default is provided, then it is difficult to tell
+ whether the boundaries did not match, or maybe the wrong variable
+ is being used.
+ <p>
+ However, if you have several test elements that set the same variable,
+ you may wish to leave the variable unchanged if the expression does not match.
+ In this case, remove the default value once debugging is complete.
+ </p>
+ </property>
+</properties>
+<p>
+ If the match number is set to a non-negative number, and a match occurs, the variables are set as follows:
+</p>
+ <ul>
+ <li><code>refName</code> - the value of the extraction</li>
+ </ul>
+<p>
+ If no match occurs, then the <code>refName</code> variable is set to the default (unless this is absent).
+</p>
+<p>
+ If the match number is set to a negative number, then all the possible matches in the sampler data are processed.
+ The variables are set as follows:
+</p>
+ <ul>
+ <li><code>refName_matchNr</code> - the number of matches found; could be <code>0</code></li>
+ <li><code>refName_<em>n</em></code>, where <code>n</code> = <code>1</code>, <code>2</code>, <code>3</code> etc. - the strings as generated by the template</li>
+ <li><code>refName_<em>n</em>_g<em>m</em></code>, where <code>m</code>=<code>0</code>, <code>1</code>, <code>2</code> - the groups for match <code>n</code></li>
+ <li><code>refName</code> - always set to the default value</li>
+ </ul>
+<p>
+ Note that the <code>refName</code> variable is always set to the default value in this case,
+ and the associated group variables are not set.
+</p>
+</component>
</section>
<section name="§-num;.9 Miscellaneous Features" anchor="Miscellaneous_Features">