From 3505e4b232f00969cf8baccece550cff85785602 Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Sat, 16 Mar 2024 21:45:02 +0530 Subject: [PATCH] Test case failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixing failure for the test test.retryAnalyzer.RetryAnalyzerTest.ensureRetryDoesntRunEndlesslyForDataDrivenTests Github actions failure on `master` branch: https://github.com/testng-team/testng/actions/runs/8307524777/job/22736819681 Gradle scan results link: https://scans.gradle.com/s/wap5a5bvq3qgq/tests/task/:testng-core:test/details/test.retryAnalyzer.RetryAnalyzerTest/ensureRetryDoesntRunEndlesslyForDataDrivenTests?top-execution=1 Prerequisites for failure: The below JVM arguments should be used: * `-Duser.timezone="America/New_York”` * `-Duser.country=RU` * `-Duser.language=ru` * `-XX:+UnlockExperimentalVMOptions` * `-XX:hashCode=2` Root cause: We are using hashCode() to create Key that is used to determine if a RetryAnalyzer instance should be created or not. When we run the tests using `-XX:hashCode=2` it causes the JVM to generate the same hashcode (value 1) And this messes up the `RetryAnalyzer` object creation. Fix: Addressed this by wrapping the parameters into an object which will contain a unique id that can be used to represent the same set of parameters. --- .../org/testng/internal/BaseTestMethod.java | 15 ++++++--- .../java/org/testng/internal/IObject.java | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java b/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java index 503e58a99..c0ad3379f 100644 --- a/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java +++ b/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java @@ -6,13 +6,14 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.testng.IClass; @@ -832,7 +833,7 @@ private IRetryAnalyzer getRetryAnalyzerConsideringMethodParameters(ITestResult t return this.m_retryAnalyzer; } - final String keyAsString = getSimpleName() + "#" + hashParameters(tr); + final String keyAsString = getSimpleName() + "#" + parameterId(tr); return m_testMethodToRetryAnalyzer.computeIfAbsent( keyAsString, key -> { @@ -842,9 +843,13 @@ private IRetryAnalyzer getRetryAnalyzerConsideringMethodParameters(ITestResult t }); } - private int hashParameters(ITestResult itr) { - Object[] parameters = itr.getParameters(); - return Objects.hash(parameters); + private final Map parameters = + new ConcurrentHashMap<>(); + + private String parameterId(ITestResult itr) { + IObject.IdentifiableArrayObject parameter = + new IObject.IdentifiableArrayObject(itr.getParameters()); + return parameters.computeIfAbsent(parameter, Function.identity()).getInstanceId(); } private static boolean isNotParameterisedTest(ITestResult tr) { diff --git a/testng-core/src/main/java/org/testng/internal/IObject.java b/testng-core/src/main/java/org/testng/internal/IObject.java index 04c04946f..4389b6ec0 100644 --- a/testng-core/src/main/java/org/testng/internal/IObject.java +++ b/testng-core/src/main/java/org/testng/internal/IObject.java @@ -1,5 +1,6 @@ package org.testng.internal; +import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -113,4 +114,36 @@ public int hashCode() { return Objects.hash(instanceId); } } + + /** + * A wrapper class that wraps around an array and associates a unique Id that can be used as a key + * for the array. + */ + class IdentifiableArrayObject { + + private final String instanceId = UUID.randomUUID().toString(); + + private final Object[] parameters; + + public IdentifiableArrayObject(Object[] parameters) { + this.parameters = parameters; + } + + public String getInstanceId() { + return instanceId; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + IdentifiableArrayObject that = (IdentifiableArrayObject) object; + return Arrays.equals(parameters, that.parameters); + } + + @Override + public int hashCode() { + return Arrays.hashCode(parameters); + } + } }