Fix CSVDataSet filename getter after loading test plan#6726
Conversation
milamberspace
left a comment
There was a problem hiding this comment.
Nice, well-scoped fix — thanks @e345ee. The diagnosis is correct: CSVDataSet's TestBean transient fields aren't populated until TestBeanHelper.prepare(), so after SaveService.loadTree() the filename field is still null while the property already holds the value; falling back to getPropertyOrNull("filename") is the right minimal fix and doesn't affect the runtime path (at lines 187/197 the field is already populated). The regression test that loads a real JMX via SaveService.loadTree() is exactly what this needs. 👍
The code looks good to me — one thing to do before merge, which is why I'm leaving this as a comment rather than an approval:
- Add a
xdocs/changes.xmlentry under Bug fixes, and add yourself under Thanks. Something like:(The PR checklist ticks "I have updated the documentation", but the diff doesn't include a changelog entry.)<li><pr>6726</pr><issue>6451</issue>Fix CSVDataSet#getFilename() returning null right after loading a test plan via SaveService.loadTree(). Contributed by Sadovoi Grigorii (github.com/e345ee)</li>
Non-blocking follow-up in the inline comment: the sibling transient getters share the same latent behaviour — fine to keep this PR scoped to filename.
This review was drafted by an AI-assisted tool (Apache Magpie) and may contain mistakes. An Apache JMeter maintainer has reviewed and confirmed this submission. See CONTRIBUTING.md.
| if (filename != null) { | ||
| return filename; | ||
| } | ||
| JMeterProperty property = getPropertyOrNull("filename"); |
There was a problem hiding this comment.
Minor / non-blocking (scope note): the sibling getters on this TestBean — getFileEncoding(), getVariableNames(), getDelimiter(), getShareMode(), and the boolean getters — all return their transient field directly and would exhibit the same null-after-loadTree() behaviour this PR fixes for filename. Keeping this PR scoped to filename (the subject of #6451) is perfectly reasonable; just flagging it as a possible follow-up so the pattern is fixed consistently later.
(Tiny aside, not a request: after an explicit setFilename(null) on a loaded element, getFilename() will now return the property value rather than null — an unlikely usage with no real-world impact.)
There was a problem hiding this comment.
Thanks for the review.
I added the required xdocs/changes.xml bug-fix entry and added myself under Thanks.
I also addressed the non-blocking scope note in this PR: the sibling CSVDataSet configuration getters now use the same property-map fallback pattern after SaveService.loadTree(). Explicit setters still take precedence over fallback values, including null for string properties and false for boolean properties.
Tested with:
./gradlew --no-daemon --max-workers=2 :src:components:test --tests org.apache.jmeter.config.TestCVSDataSet./gradlew --no-daemon --max-workers=2 :src:components:checkstyleMain :src:components:checkstyleTestgit diff --check
vlsi
left a comment
There was a problem hiding this comment.
Thanks for digging into this — the diagnosis is right, but I'd like to push back on the direction before this lands.
The bug is that CSVDataSet keeps its state in two places (the property map and the transient fields) and the getters read the wrong one. This PR doesn't remove the duplication; it adds a third copy (nine *Set flags) plus a precedence heuristic between the copies. A few consequences:
-
Getters and setters now disagree. The getters read from either source, but
setFilename()still writes only to the field. So on a loaded plan,setFilename(x)followed bySaveService.saveTree()silently drops the change — which is the same "inspect and edit a loaded plan" use case the PR is motivated by. The new test encodes that divergence as expected behavior. -
The fallback ignores the BeanInfo defaults.
CSVDataSetBeanInfodeclaresNOT_UNDEFINED+DEFAULT(delimiter=",",filename="",shareMode=shareMode.all), andTestBeanHelper.unwrapProperty()applies them for aNullProperty.getStringProperty()returnsnullinstead. For a JMX without adelimiterproperty,getDelimiter()now returnsnullanddelim.isEmpty()initerationStart()NPEs. That's a second set of defaults diverging from the first. -
fieldSet || fieldValue != defaultValue— the second condition only fires if a field differs from its default without a setter call, which thetransientmodifier rules out for every property exceptignoreFirstLine. Either it's dead code or it needs a comment naming the case. -
The constants are a third copy —
FILENAME,DELIMITER,SHAREMODEand friends already exist verbatim inCSVDataSetBeanInfo.
The transient fields exist to cache the evaluated property value, but FunctionProperty already does exactly that, with better semantics: it returns the raw string when !isRunningVersion() and evaluates with per-iteration caching once sampling starts. So the fields are a redundant layer, and the fix is to delete them rather than to arbitrate between them:
- add a
CSVDataSetSchema(the infrastructure is inorg.apache.jmeter.testelement.schema) carrying the property names and the defaults; - back the getters/setters with the property map —
Argument.javais the precedent:return get(getSchema().getArgumentName()); - drop the transient config fields, the
*Setflags, andreadResolve()(it only exists to restore therecycledefault); - have
CSVDataSetBeanInforeadDEFAULTfrom the schema descriptors, so the defaults live in one place.
One thing that needs care: with property-backed setters, TestBeanHelper.prepare() would write the evaluated value back and collapse a FunctionProperty into a literal. For CSVDataSet it happens once at compile time (TestCompiler#trackIterationListeners), so behavior wouldn't change, but relying on that is the same implicit invariant that caused this bug. A marker interface that makes prepare() skip property-backed beans — in the spirit of NoConfigMerge / NoThreadClone — keeps it honest and lets the rest of the TestBeans migrate one at a time.
That's roughly the same diff size as this PR, but the state shrinks to zero instead of growing to 27 fields, and #6451 closes together with the symmetric write-side problem.
Worth adding to the test: a save/reload round-trip after setFilename(), assertEquals(",", new CSVDataSet().getDelimiter()), and getFilename() on a ${__P(csv)} value.
|
Understand, I will rework, I need some time |
Description
This PR fixes
CSVDataSet.getFilename()when a test plan is loaded throughSaveService.loadTree().CSVDataSetkeeps TestBean values in transient fields during normal execution, while loaded JMX values are restored into the element property map. Because of that,getFilename()could returnnullright after loading a test plan, even though thefilenameproperty was already present.The change keeps the existing runtime behavior, but makes
getFilename()fall back to the storedfilenameproperty when the transient field has not been populated yet.Motivation and Context
Fixes #6451.
I ran into this while working with JMeter test plans programmatically. After loading a JMX file,
CSVDataSetalready has thefilenamevalue in its property map, butgetFilename()still returnsnulluntil TestBean preparation runs.That makes it harder to inspect or validate loaded test plans, so this PR makes the getter return the already-loaded value in that case.
How Has This Been Tested?
Tested locally with:
./gradlew :src:components:test --tests org.apache.jmeter.config.TestCVSDataSet.testFilenameGetterAfterLoadingTestPlan -Djava.awt.headless=true./gradlew :src:components:test --tests org.apache.jmeter.config.TestCVSDataSet -Djava.awt.headless=true./gradlew :src:components:checkstyleMain :src:components:checkstyleTestgit diff --checkThe new regression test loads a minimal JMX file with
SaveService.loadTree(), finds theCSVDataSet, and verifies that both the rawfilenameproperty andgetFilename()return the expected value.Screenshots (if appropriate):
Not applicable.
Types of changes
Checklist: