vlsi opened a new issue, #6728:
URL: https://github.com/apache/jmeter/issues/6728
## Summary
`TestBean` is a marker interface that means two unrelated things at once.
Elements can't opt into one without the other, which is what turned a simple
bug report (#6451) into an open-ended design question in #6726. This issue
proposes splitting it, and asks for a decision on how existing elements opt out
of the storage half.
## Background
Implementing `TestBean` currently buys an element two separate things:
1. **A generated GUI.** Write a `BeanInfo` and `TestBeanGUI` builds the
editor by introspection, with no Swing code in the element.
2. **Field-backed storage with a sync step.** `TestBeanHelper.prepare()`
copies the property map into the element's setters, so the element's plain Java
fields hold the values during a run.
The second is the source of a recurring surprise: the fields are only
populated after `prepare()`, so every TestBean getter returns `null` (or a
field initializer) on a tree that was just loaded from JMX. #6451 hit this with
`CSVDataSet.getFilename()`. It's working as designed, but the design is a trap,
and it applies to all 32 elements that implement `TestBean` — JSR223 elements,
`ConstantThroughputTimer`, `DataSourceElement`, and the rest.
The two jobs have nothing to do with each other. An element that stores its
configuration in the property map still wants a generated GUI; it just doesn't
want the field sync.
## The split is already there in the code
Every place the framework tests for `TestBean` uses it for exactly one of
the two jobs, and no call site needs both:
**GUI generation (5)**
- `TestBeanGUI:159` — guard that the class is a TestBean
- `GuiPackage:368` — instantiate `TestBeanGUI` for the element
- `MenuFactory:138`, `MenuFactory:209` — menu registration
- `JMeterTreeNode:145` — icon and label resolution
**Field-backed storage (3)**
- `TestBeanHelper:129` — entry guard for `prepare()`
- `TestBeanHelper:180` — recursion into nested `TestElementProperty`
- `StandardJMeterEngine:243` — prepare test listeners
## Proposal
Split the marker in two and have `TestBean` extend both. Names are open to
bikeshedding:
```java
public interface BeanInfoGui {} // "generate my GUI from my
BeanInfo"
public interface FieldBackedProperties {} // "copy my properties into my
setters before use"
public interface TestBean extends BeanInfoGui, FieldBackedProperties {}
```
Then narrow the eight call sites above to whichever half they actually mean.
## This is a no-op for existing code
Adding superinterfaces to an existing interface is binary compatible, and
because `TestBean` extends both, **every `instanceof` result stays identical**:
- `el instanceof TestBean` at `TestBeanHelper:129` becomes `el instanceof
FieldBackedProperties`. Any element implementing `TestBean` inherits it, so the
result is unchanged.
- `TestBean.class.isAssignableFrom(c)` at `GuiPackage:368` becomes
`BeanInfoGui.class.isAssignableFrom(c)`. Same.
All 32 in-tree elements and every third-party plugin keep working with no
source change. Both interfaces must stay method-free markers for this to hold.
So the split can land on its own merits, independently of anything else
discussed here.
## What it enables
New elements implement only `BeanInfoGui`: they get the generated GUI, store
their configuration in the property map, and their getters are valid at any
point in the lifecycle. No marker, no opt-out, no `prepare()` on the sampling
path.
That also unblocks the newer property editors (`JBooleanPropertyEditor` and
friends) and the modified-value gutter for these elements, since those bind to
schema property descriptors rather than to bean fields.
## The unresolved part: existing elements can't un-implement
An element like `CSVDataSet` has to keep implementing `TestBean` for
compatibility — user code may cast to it or test for it. Since `TestBean
extends FieldBackedProperties`, it inherits the storage half and Java gives us
no way to drop it. So migrating an existing element to property-backed storage
still needs an explicit opt-out. Options:
**A. A negative marker.** `interface NotFieldBacked {}`, checked alongside
the positive one in `TestBeanHelper.prepare()`. Visible in the element's
declaration, and its javadoc can scope it honestly as a legacy-compatibility
wart that new elements should never need. Cost: a double negative in the type
hierarchy.
**B. A BeanInfo attribute.** `beanDescriptor.setValue("propertyBacked",
TRUE)`, read once inside the existing `GOOD_PROPS` `ClassValue` in
`TestBeanHelper`, so zero per-call cost. The "cannot un-implement" problem
disappears because this is metadata rather than a type, and the decision sits
with the element's other bean metadata. Cost: stringly-typed and less visible
when reading the element class.
**C. Neither** — accept that existing elements stay field-backed forever,
and property-backed storage is only for new elements. Cheapest, but leaves the
#6451 trap in place for all 32.
I lean toward B, precisely because "cannot un-implement an interface" is a
permanent Java constraint and metadata sidesteps it entirely. I don't feel
strongly.
## Decisions requested
These are independent; the first can land without settling the second.
1. Do we split `TestBean` as described?
2. If we do, which opt-out mechanism for existing elements — A, B, or C?
And if the answer to 2 is A or B, two semantics questions need settling
once, project-wide, rather than being rediscovered per element:
- Should a getter return `null` or the schema default for an unset property?
Property-backed getters return the default, which differs from today's field
behavior before `prepare()`.
- Function properties: today `prepare()` freezes `${...}` into a field at
compile time; property-backed access re-evaluates per read, using
`FunctionProperty`'s per-iteration cache. The latter looks more correct, but it
is an observable change.
## Origin
Came out of #6451 (reported as a `CSVDataSet.getFilename()` bug, closed as
works-as-designed) and #6726, where @e345ee implemented property-backed storage
for `CSVDataSet` with a `PropertyBackedTestBean` marker. That PR is a working
reference implementation of the migration; it's on hold pending this decision.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]