From 18400576414f7fee2900b942c33b27b227bd0ab2 Mon Sep 17 00:00:00 2001 From: ruthes00 Date: Thu, 24 Sep 2026 04:13:27 -0400 Subject: [PATCH 1/2] Fixed issue where the __Match No.__ field in extractor elements (Regex Extractor, Boundary Extractor, etc.) had no documentation explaining that leaving it blank is equivalent to `0` (random match), causing user confusion. --- .../extractor/TestBoundaryExtractor.java | 63 +++++++++++++++++++ .../jmeter/extractor/TestRegexExtractor.java | 15 +++++ xdocs/usermanual/component_reference.xml | 2 + 3 files changed, 80 insertions(+) diff --git a/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java b/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java index 934078c8d6e..3af5e5f437c 100644 --- a/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java +++ b/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java @@ -18,7 +18,9 @@ package org.apache.jmeter.extractor; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterContext; @@ -108,4 +110,65 @@ public void testOnlyRightBoundary() { assertEquals("on", vars.get("varname_1"), "First match is incorrect"); assertEquals("1", vars.get("varname_matchNr"), "MatchNumber is incorrect"); } + + /** + * matchNumber=0 means random: when there is exactly one match the result + * must equal that match (no ambiguity about which one is chosen). + */ + @Test + public void testMatchNumberZeroRandomSingleMatch() { + vars.put("content", "left-VALUE-right"); + extractor.setLeftBoundary("left-"); + extractor.setRightBoundary("-right"); + extractor.setMatchNumber(0); + extractor.setRefName("varname"); + extractor.setScopeVariable("content"); + extractor.setThreadContext(jmctx); + extractor.process(); + assertEquals("VALUE", vars.get("varname"), + "matchNumber=0 (random) with a single match should return that match"); + assertNull(vars.get("varname_matchNr"), + "matchNr variable should not be set for matchNumber=0"); + } + + /** + * matchNumber=0 means random: when there are multiple matches the result + * must be one of the available matches. + */ + @Test + public void testMatchNumberZeroRandomMultipleMatches() { + vars.put("content", "left-A-right left-B-right left-C-right"); + extractor.setLeftBoundary("left-"); + extractor.setRightBoundary("-right"); + extractor.setMatchNumber(0); + extractor.setRefName("varname"); + extractor.setScopeVariable("content"); + extractor.setThreadContext(jmctx); + extractor.process(); + String found = vars.get("varname"); + assertNotNull(found, "matchNumber=0 (random) should return a non-null result when matches exist"); + assertTrue("A".equals(found) || "B".equals(found) || "C".equals(found), + "matchNumber=0 (random) result '" + found + "' should be one of the available matches"); + assertNull(vars.get("varname_matchNr"), + "matchNr variable should not be set for matchNumber=0"); + } + + /** + * An empty Match No. field is stored as "" which resolves to 0 via + * getIntValue(), so it must behave identically to matchNumber=0 (random). + */ + @Test + public void testEmptyMatchNumberFieldBehavesLikeZero() { + vars.put("content", "left-ONLY-right"); + extractor.setLeftBoundary("left-"); + extractor.setRightBoundary("-right"); + // Simulate the GUI leaving the field blank: store an empty string property + extractor.setMatchNumber(""); + extractor.setRefName("varname"); + extractor.setScopeVariable("content"); + extractor.setThreadContext(jmctx); + extractor.process(); + assertEquals("ONLY", vars.get("varname"), + "Empty Match No. field (defaults to 0/random) should return the single available match"); + } } diff --git a/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java b/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java index d04385f1a8e..2abc4b9df51 100644 --- a/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java +++ b/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java @@ -125,6 +125,21 @@ public void testVariableExtraction0() { assertEquals("value", vars.get("regVal")); } + /** + * An empty Match No. field must behave identically to matchNumber=0 (random). + * When there is exactly one match the result must equal that match. + */ + @Test + public void testEmptyMatchNumberFieldBehavesLikeZero() { + extractor.setRegex("<(value) field=\""); + extractor.setTemplate("$1$"); + // Simulate the GUI leaving the field blank + extractor.setMatchNumber(""); + extractor.process(); + assertEquals("value", vars.get("regVal"), + "Empty Match No. field (defaults to 0/random) should return the single available match"); + } + @Test public void testVariableExtraction() { extractor.setRegex("(\\d+)"); diff --git a/xdocs/usermanual/component_reference.xml b/xdocs/usermanual/component_reference.xml index 783051d8bb1..82a54950e83 100644 --- a/xdocs/usermanual/component_reference.xml +++ b/xdocs/usermanual/component_reference.xml @@ -5744,6 +5744,7 @@ generate the template string, and store the result into the given variable name.
  • Use a value of zero to indicate JMeter should choose a match at random.
  • A positive number N means to select the nth match.
  • Negative numbers are used in conjunction with the - see below.
  • +
  • If the field is left empty, it defaults to 0 (random).
  • @@ -6312,6 +6313,7 @@ generate the template string, and store the result into the given variable name.
  • Use a value of zero to indicate JMeter should choose a match at random.
  • A positive number N means to select the nth match.
  • Negative numbers are used in conjunction with the - see below.
  • +
  • If the field is left empty, it defaults to 0 (random).
  • From b2f264a6f146f526802f01c4f8877233526c5af6 Mon Sep 17 00:00:00 2001 From: ruthes00 Date: Sat, 26 Sep 2026 04:12:44 -0400 Subject: [PATCH 2/2] All changes requested by vlsi in the inline comments have been implemented: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **`TestBoundaryExtractor.java`:** - Dropped `testMatchNumberZeroRandomSingleMatch` (already covered by `BoundaryExtractorTest.kt`'s `ExtractCase(1..1, 0, ...)` and the existing `extract random from variable` Kotlin test checks `varname_matchNr` is null) - Renamed `testMatchNumberZeroRandomMultipleMatches` → `testMatchNumberZeroMultipleMatches` (removed "Random" since the test doesn't verify randomness), removed the redundant `assertNotNull`, and replaced the `a || b || c` assertTrue with `Set.of("A","B","C").contains(found)` - Replaced `testEmptyMatchNumberFieldBehavesLikeZero` with `testEmptyMatchNumber` which: (1) deterministically checks `assertEquals(0, extractor.getMatchNumber())` after `setMatchNumber("")`, and (2) behaviorally verifies that empty field acts like 0 (not -1) by asserting `varname` is set but `varname_1` and `varname_matchNr` are not - Fixed the comment to say "An empty Match No. is treated as 0." instead of describing the implementation **`TestRegexExtractor.java`:** - Replaced `testEmptyMatchNumberFieldBehavesLikeZero` with `testEmptyMatchNumber` which simply calls `extractor.setMatchNumber("")` and asserts `assertEquals(0, extractor.getMatchNumber())` — a check that can actually fail if the behavior changes The `component_reference.xml` was already correct (all three sections — Regex, CSS/JQuery, Boundary — already had `required="No"` and the `
  • An empty field is treated as 0.
  • ` line). Build and style checks passed cleanly. --- .../extractor/TestBoundaryExtractor.java | 53 +++++++------------ .../jmeter/extractor/TestRegexExtractor.java | 12 ++--- xdocs/usermanual/component_reference.xml | 11 ++-- 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java b/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java index 3af5e5f437c..ba2cf6526d9 100644 --- a/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java +++ b/src/components/src/test/java/org/apache/jmeter/extractor/TestBoundaryExtractor.java @@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Set; + import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContextService; @@ -112,31 +114,10 @@ public void testOnlyRightBoundary() { } /** - * matchNumber=0 means random: when there is exactly one match the result - * must equal that match (no ambiguity about which one is chosen). + * matchNumber=0 means the result must be one of the available matches. */ @Test - public void testMatchNumberZeroRandomSingleMatch() { - vars.put("content", "left-VALUE-right"); - extractor.setLeftBoundary("left-"); - extractor.setRightBoundary("-right"); - extractor.setMatchNumber(0); - extractor.setRefName("varname"); - extractor.setScopeVariable("content"); - extractor.setThreadContext(jmctx); - extractor.process(); - assertEquals("VALUE", vars.get("varname"), - "matchNumber=0 (random) with a single match should return that match"); - assertNull(vars.get("varname_matchNr"), - "matchNr variable should not be set for matchNumber=0"); - } - - /** - * matchNumber=0 means random: when there are multiple matches the result - * must be one of the available matches. - */ - @Test - public void testMatchNumberZeroRandomMultipleMatches() { + public void testMatchNumberZeroMultipleMatches() { vars.put("content", "left-A-right left-B-right left-C-right"); extractor.setLeftBoundary("left-"); extractor.setRightBoundary("-right"); @@ -146,29 +127,33 @@ public void testMatchNumberZeroRandomMultipleMatches() { extractor.setThreadContext(jmctx); extractor.process(); String found = vars.get("varname"); - assertNotNull(found, "matchNumber=0 (random) should return a non-null result when matches exist"); - assertTrue("A".equals(found) || "B".equals(found) || "C".equals(found), - "matchNumber=0 (random) result '" + found + "' should be one of the available matches"); + assertTrue(Set.of("A", "B", "C").contains(found), + "matchNumber=0 result '" + found + "' should be one of the available matches"); assertNull(vars.get("varname_matchNr"), "matchNr variable should not be set for matchNumber=0"); } /** - * An empty Match No. field is stored as "" which resolves to 0 via - * getIntValue(), so it must behave identically to matchNumber=0 (random). + * An empty Match No. is treated as 0. */ @Test - public void testEmptyMatchNumberFieldBehavesLikeZero() { - vars.put("content", "left-ONLY-right"); + public void testEmptyMatchNumber() { + // Deterministic check: getMatchNumber() must return 0 for an empty field. + extractor.setMatchNumber(""); + assertEquals(0, extractor.getMatchNumber(), "getMatchNumber() for an empty Match No."); + + // Behavioural check: empty field behaves like 0, not like -1. + // With matchNumber=0 the non-indexed variable is set and the indexed + // variables (_1, _matchNr) are not. + vars.put("content", "left-A-right left-B-right left-C-right"); extractor.setLeftBoundary("left-"); extractor.setRightBoundary("-right"); - // Simulate the GUI leaving the field blank: store an empty string property - extractor.setMatchNumber(""); extractor.setRefName("varname"); extractor.setScopeVariable("content"); extractor.setThreadContext(jmctx); extractor.process(); - assertEquals("ONLY", vars.get("varname"), - "Empty Match No. field (defaults to 0/random) should return the single available match"); + assertNotNull(vars.get("varname"), "varname should be set"); + assertNull(vars.get("varname_1"), "varname_1 should not be set for matchNumber=0"); + assertNull(vars.get("varname_matchNr"), "varname_matchNr should not be set for matchNumber=0"); } } diff --git a/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java b/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java index 2abc4b9df51..e730fe0e4e7 100644 --- a/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java +++ b/src/components/src/test/java/org/apache/jmeter/extractor/TestRegexExtractor.java @@ -126,18 +126,12 @@ public void testVariableExtraction0() { } /** - * An empty Match No. field must behave identically to matchNumber=0 (random). - * When there is exactly one match the result must equal that match. + * An empty Match No. is treated as 0. */ @Test - public void testEmptyMatchNumberFieldBehavesLikeZero() { - extractor.setRegex("<(value) field=\""); - extractor.setTemplate("$1$"); - // Simulate the GUI leaving the field blank + public void testEmptyMatchNumber() { extractor.setMatchNumber(""); - extractor.process(); - assertEquals("value", vars.get("regVal"), - "Empty Match No. field (defaults to 0/random) should return the single available match"); + assertEquals(0, extractor.getMatchNumber(), "getMatchNumber() for an empty Match No."); } @Test diff --git a/xdocs/usermanual/component_reference.xml b/xdocs/usermanual/component_reference.xml index 82a54950e83..504c964fc1a 100644 --- a/xdocs/usermanual/component_reference.xml +++ b/xdocs/usermanual/component_reference.xml @@ -5739,12 +5739,12 @@ generate the template string, and store the result into the given variable name. The template used to create a string from the matches found. This is an arbitrary string with special elements to refer to groups within the regular expression. The syntax to refer to a group is: '$1$' to refer to group 1, '$2$' to refer to group 2, etc. $0$ refers to whatever the entire expression matches. - Indicates which match to use. The regular expression may match multiple times. + Indicates which match to use. The regular expression may match multiple times.
    • Use a value of zero to indicate JMeter should choose a match at random.
    • A positive number N means to select the nth match.
    • Negative numbers are used in conjunction with the - see below.
    • -
    • If the field is left empty, it defaults to 0 (random).
    • +
    • An empty field is treated as 0.
    @@ -5854,11 +5854,12 @@ extracting the node as text or attribute value and store the result into the giv If empty this is the equivalent of Element#text() function for JSoup if not value is set for attribute.
    CSS Extractor with no attribute set
    - Indicates which match to use. The CSS/JQuery selector may match multiple times. + Indicates which match to use. The CSS/JQuery selector may match multiple times.
    • Use a value of zero to indicate JMeter should choose a match at random.
    • A positive number N means to select the nth match.
    • Negative numbers are used in conjunction with the - see below.
    • +
    • An empty field is treated as 0.
    @@ -6308,12 +6309,12 @@ generate the template string, and store the result into the given variable name. The name of the JMeter variable in which to store the result. Also note that each group is stored as [refname]_g#, where [refname] is the string you entered as the reference name, and # is the group number, where group 0 is the entire match, group 1 is the match from the first set of parentheses, etc. Left boundary of value to find Right boundary of value to find - Indicates which match to use. The boundaries may match multiple times. + Indicates which match to use. The boundaries may match multiple times.
    • Use a value of zero to indicate JMeter should choose a match at random.
    • A positive number N means to select the nth match.
    • Negative numbers are used in conjunction with the - see below.
    • -
    • If the field is left empty, it defaults to 0 (random).
    • +
    • An empty field is treated as 0.