Describe the bug
Description
The non-deterministic bug below was found with NonDex, which explores non-determinism in tests. The order of arguments to constructor can be mixed-up due to assumption of fixed-order in getDeclaredFields.
I have identified the root cause below and will soon submit a PR which mentions and fixes this issue.
Root Cause
The bug is at equalsverifier/internal/instantiation/InstanceCreator.java:
private T createRecordInstance(Map<Field, Object> values) {
var params = new ArrayList<Object>();
traverseFields(values, (p, v) -> params.add(v));
var recordProbe = new RecordProbe<T>(type);
return recordProbe.callRecordConstructor(params);
}
private void traverseFields(Map<Field, Object> values, BiConsumer<FieldProbe, Object> setValue) {
for (FieldProbe p : fields(type)) {
Object value = values.get(p.getField());
if (value == null) {
value = PrimitiveMappers.DEFAULT_VALUE_MAPPER.get(p.getType());
}
setValue.accept(p, value);
}
}
When traverseFields iterates over fields(type), the order is non-deterministic due to getDeclaredFields in:
private List<FieldProbe> addFieldsFor(Class<?> c) {
var fields = new ArrayList<FieldProbe>();
var statics = new ArrayList<FieldProbe>();
for (Field field : c.getDeclaredFields()) {
...
Therefore, the param collected in createRecordInstance may not follow the order of declaration (e.g. under differnt JVM), then it passes values to wrong fields. For example, in the Failure Reproduction below, the arguments one and null is swapped.
Steps to reproduce
openjdk 21.0.9 2025-10-21
OpenJDK Runtime Environment (build 21.0.9+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 21.0.9+10-Ubuntu-124.04, mixed mode, sharing)
mvn edu.illinois:nondex-maven-plugin:2.2.1:nondex -pl core \
-Djacoco.skip -Drat.skip -Dpmd.skip -Denforcer.skip \
-Dtest=com.ericsson.bss.cassandra.ecchronos.core.TestLockCache#testEqualsContract
- Since I already identified the root cause, I did not try to write a simpler reproduction test
Error message and version number
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.133 s <<< FAILURE! -- in com.ericsson.bss.cassandra.ecchronos.core.TestLockCache
[ERROR] com.ericsson.bss.cassandra.ecchronos.core.TestLockCache.testEqualsContract -- Time elapsed: 0.525 s <<< FAILURE!
java.lang.AssertionError:
EqualsVerifier found a problem in class com.ericsson.bss.cassandra.ecchronos.core.LockCache$LockKey.
-> Record: failed to run constructor for record type com.ericsson.bss.cassandra.ecchronos.core.LockCache$LockKey
These were the values passed to the constructor: [one, null]
If the record does not accept null values for its constructor parameters, consider suppressing Warning.NULL_FIELDS.
Code: EqualsVerifier invocation
...
@Test
public void testEqualsContract()
{
EqualsVerifier.forClass(LockCache.LockKey.class).usingGetClass().verify();
}
Code: class under test
Code under test:
public final class LockCache
{
private static final Logger LOG = LoggerFactory.getLogger(LockCache.class);
private final Cache<LockKey, LockException> myFailureCache;
private final LockSupplier myLockSupplier;
public LockCache(final LockSupplier lockSupplier, final long expireTimeInSeconds)
{
this(lockSupplier, expireTimeInSeconds, TimeUnit.SECONDS);
}
...
record LockKey(String dataCenter, @NotNull String resource)
{
LockKey
{
checkNotNull(resource);
}
}
}
Additional context
No response
Describe the bug
Description
The non-deterministic bug below was found with NonDex, which explores non-determinism in tests. The order of arguments to constructor can be mixed-up due to assumption of fixed-order in
getDeclaredFields.I have identified the root cause below and will soon submit a PR which mentions and fixes this issue.
Root Cause
The bug is at
equalsverifier/internal/instantiation/InstanceCreator.java:When
traverseFieldsiterates overfields(type), the order is non-deterministic due togetDeclaredFieldsin:Therefore, the
paramcollected increateRecordInstancemay not follow the order of declaration (e.g. under differnt JVM), then it passes values to wrong fields. For example, in the Failure Reproduction below, the argumentsoneand null is swapped.Steps to reproduce
OS version:
Ubuntu 24.04.3 LTSBuild the latest commit of https://github.com/Ericsson/ecchronos
Run test
TestLockCachewith NonDex, for example:Error message and version number
Code: EqualsVerifier invocation
... @Test public void testEqualsContract() { EqualsVerifier.forClass(LockCache.LockKey.class).usingGetClass().verify(); }Code: class under test
Code under test:
Additional context
No response