Skip to content

Commit 8fdd106

Browse files
committed
Add Unique Id for all test class instances
Closes #3079
1 parent eb80671 commit 8fdd106

27 files changed

+456
-97
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Current (7.10.0)
2+
Fixed: GITHUB-3079: Associate a unique id with every test class object instantiated by TestNG (Krishnan Mahadevan)
23
Fixed: GITHUB:3040: replace the usages of synchronized with ReentrantLock (Krishnan Mahadevan)
34
Fixed: GITHUB-3041: TestNG 7.x DataProvider works in opposite to TestNG 6.x when retrying tests. (Krishnan Mahadevan)
45
Fixed: GITHUB-3066: How to dynamically adjust the number of TestNG threads after IExecutorFactory is deprecated? (Krishnan Mahadevan)

testng-core-api/src/main/java/org/testng/IClass.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import org.testng.xml.XmlTest;
55

66
/** <code>IClass</code> represents a test class and a collection of its instances. */
7-
public interface IClass {
7+
public interface IClass extends IObject {
88

99
/** @return this test class name. This is the name of the corresponding Java class. */
1010
String getName();
@@ -27,14 +27,16 @@ public interface IClass {
2727
*
2828
* @param create flag if a new set of instances must be returned (if set to <code>false</code>)
2929
* @return All the instances the methods will be invoked upon.
30+
* @deprecated - As of TestNG <code>v7.10.0</code>
3031
*/
32+
@Deprecated
3133
Object[] getInstances(boolean create);
3234

35+
@Deprecated
3336
default Object[] getInstances(boolean create, String errorMsgPrefix) {
3437
return getInstances(create);
3538
}
3639

37-
long[] getInstanceHashCodes();
38-
40+
@Deprecated
3941
void addInstance(Object instance);
4042
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.testng;
2+
3+
import java.util.Objects;
4+
import java.util.UUID;
5+
6+
/** Represents the associations of a class with one or more instances. */
7+
public interface IObject {
8+
9+
default IdentifiableObject[] getObjects(boolean create) {
10+
return getObjects(create, "");
11+
}
12+
13+
IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix);
14+
15+
long[] getInstanceHashCodes();
16+
17+
void addObject(IdentifiableObject instance);
18+
19+
/** A wrapper object that associates a unique id to every unique test class object. */
20+
class IdentifiableObject {
21+
private final Object instance;
22+
private final UUID instanceId;
23+
24+
public IdentifiableObject(Object instance) {
25+
this(instance, UUID.randomUUID());
26+
}
27+
28+
public IdentifiableObject(Object instance, UUID instanceId) {
29+
this.instance = instance;
30+
this.instanceId = instanceId;
31+
}
32+
33+
public static Object unwrap(IdentifiableObject object) {
34+
if (object == null) {
35+
return null;
36+
}
37+
return object.getInstance();
38+
}
39+
40+
public UUID getInstanceId() {
41+
return instanceId;
42+
}
43+
44+
public Object getInstance() {
45+
return instance;
46+
}
47+
48+
@Override
49+
public boolean equals(Object object) {
50+
if (this == object) {
51+
return true;
52+
}
53+
if (object == null || getClass() != object.getClass()) {
54+
return false;
55+
}
56+
IdentifiableObject that = (IdentifiableObject) object;
57+
return Objects.equals(instance, that.instance);
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return Objects.hash(instance);
63+
}
64+
}
65+
}

testng-core-api/src/main/java/org/testng/ITestNGMethod.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.List;
44
import java.util.Map;
55
import java.util.Set;
6+
import java.util.UUID;
67
import java.util.concurrent.Callable;
78
import org.testng.annotations.CustomAttribute;
89
import org.testng.internal.ConstructorOrMethod;
@@ -279,7 +280,7 @@ default CustomAttribute[] getAttributes() {
279280

280281
/**
281282
* @return - An {@link IDataProviderMethod} for a data provider powered test method and <code>null
282-
* </code> otherwise.
283+
* </code> otherwise.
283284
*/
284285
default IDataProviderMethod getDataProviderMethod() {
285286
return null;
@@ -296,4 +297,12 @@ default Class<?>[] getParameterTypes() {
296297
default boolean isIgnoreFailure() {
297298
return false;
298299
}
300+
301+
/**
302+
* @return - A <code>{@link UUID}</code> that represents a unique id which is associated with
303+
* every test class object.
304+
*/
305+
default UUID getInstanceId() {
306+
return null;
307+
}
299308
}

testng-core/src/main/java/org/testng/TestClass.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,13 @@ private void initTestClassesAndInstances() {
106106
//
107107
// TestClasses and instances
108108
//
109-
Object[] instances = getInstances(true, this.m_errorMsgPrefix);
110-
for (Object instance : instances) {
111-
instance = IParameterInfo.embeddedInstance(instance);
112-
if (instance instanceof ITest) {
113-
testName = ((ITest) instance).getTestName();
114-
break;
115-
}
116-
}
109+
IdentifiableObject[] instances = getObjects(true, this.m_errorMsgPrefix);
110+
Arrays.stream(instances)
111+
.map(IdentifiableObject::getInstance)
112+
.map(IParameterInfo::embeddedInstance)
113+
.filter(it -> it instanceof ITest)
114+
.findFirst()
115+
.ifPresent(it -> testName = ((ITest) it).getTestName());
117116
if (testName == null) {
118117
testName = iClass.getTestName();
119118
}
@@ -124,11 +123,21 @@ public Object[] getInstances(boolean create) {
124123
return iClass.getInstances(create);
125124
}
126125

126+
@Override
127+
public IdentifiableObject[] getObjects(boolean create) {
128+
return iClass.getObjects(create);
129+
}
130+
127131
@Override
128132
public Object[] getInstances(boolean create, String errorMsgPrefix) {
129133
return iClass.getInstances(create, this.m_errorMsgPrefix);
130134
}
131135

136+
@Override
137+
public IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) {
138+
return iClass.getObjects(create, this.m_errorMsgPrefix);
139+
}
140+
132141
@Override
133142
public long[] getInstanceHashCodes() {
134143
return iClass.getInstanceHashCodes();
@@ -139,11 +148,16 @@ public void addInstance(Object instance) {
139148
iClass.addInstance(instance);
140149
}
141150

151+
@Override
152+
public void addObject(IdentifiableObject instance) {
153+
iClass.addObject(instance);
154+
}
155+
142156
private void initMethods() {
143157
ITestNGMethod[] methods = testMethodFinder.getTestMethods(m_testClass, xmlTest);
144158
m_testMethods = createTestMethods(methods);
145159

146-
for (Object eachInstance : iClass.getInstances(false)) {
160+
for (IdentifiableObject eachInstance : iClass.getObjects(false)) {
147161
m_beforeSuiteMethods =
148162
ConfigurationMethod.createSuiteConfigurationMethods(
149163
objectFactory,
@@ -182,7 +196,7 @@ private void initMethods() {
182196
true,
183197
xmlTest,
184198
eachInstance);
185-
Object instance = IParameterInfo.embeddedInstance(eachInstance);
199+
Object instance = IParameterInfo.embeddedInstance(eachInstance.getInstance());
186200
beforeClassConfig.put(instance, Arrays.asList(m_beforeClassMethods));
187201
m_afterClassMethods =
188202
ConfigurationMethod.createClassConfigurationMethods(
@@ -234,7 +248,7 @@ private ITestNGMethod[] createTestMethods(ITestNGMethod[] methods) {
234248
for (ITestNGMethod tm : methods) {
235249
ConstructorOrMethod m = tm.getConstructorOrMethod();
236250
if (m.getDeclaringClass().isAssignableFrom(m_testClass)) {
237-
for (Object o : iClass.getInstances(false)) {
251+
for (IdentifiableObject o : iClass.getObjects(false)) {
238252
log(4, "Adding method " + tm + " on TestClass " + m_testClass);
239253
vResult.add(new TestNGMethod(objectFactory, m.getMethod(), annotationFinder, xmlTest, o));
240254
}

testng-core/src/main/java/org/testng/internal/BaseClassFinder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Map;
44
import org.testng.IClass;
5+
import org.testng.IObject;
56
import org.testng.ITestClassFinder;
67
import org.testng.ITestContext;
78
import org.testng.ITestObjectFactory;
@@ -32,7 +33,7 @@ protected IClass findOrCreateIClass(
3233
ITestContext context,
3334
Class<?> cls,
3435
XmlClass xmlClass,
35-
Object instance,
36+
IObject.IdentifiableObject instance,
3637
IAnnotationFinder annotationFinder,
3738
ITestObjectFactory objectFactory) {
3839

testng-core/src/main/java/org/testng/internal/BaseTestMethod.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
import java.util.Objects;
1010
import java.util.Optional;
1111
import java.util.Set;
12+
import java.util.UUID;
1213
import java.util.concurrent.Callable;
1314
import java.util.concurrent.ConcurrentLinkedQueue;
1415
import java.util.concurrent.atomic.AtomicInteger;
1516
import java.util.regex.Pattern;
1617
import java.util.stream.Collectors;
1718
import org.testng.IClass;
19+
import org.testng.IObject;
1820
import org.testng.IRetryAnalyzer;
1921
import org.testng.ITestClass;
2022
import org.testng.ITestNGMethod;
@@ -81,7 +83,7 @@ public abstract class BaseTestMethod implements ITestNGMethod, IInvocationStatus
8183
private int m_interceptedPriority;
8284

8385
private XmlTest m_xmlTest;
84-
private final Object m_instance;
86+
private final IObject.IdentifiableObject m_instance;
8587

8688
private final Map<String, IRetryAnalyzer> m_testMethodToRetryAnalyzer = Maps.newConcurrentMap();
8789
protected final ITestObjectFactory m_objectFactory;
@@ -91,7 +93,7 @@ public BaseTestMethod(
9193
String methodName,
9294
ConstructorOrMethod com,
9395
IAnnotationFinder annotationFinder,
94-
Object instance) {
96+
IObject.IdentifiableObject instance) {
9597
m_objectFactory = objectFactory;
9698
m_methodClass = com.getDeclaringClass();
9799
m_method = com;
@@ -148,7 +150,15 @@ public String getMethodName() {
148150

149151
@Override
150152
public Object getInstance() {
151-
return IParameterInfo.embeddedInstance(m_instance);
153+
return Optional.ofNullable(m_instance)
154+
.map(IObject.IdentifiableObject::getInstance)
155+
.map(IParameterInfo::embeddedInstance)
156+
.orElse(null);
157+
}
158+
159+
@Override
160+
public UUID getInstanceId() {
161+
return m_instance.getInstanceId();
152162
}
153163

154164
/** {@inheritDoc} */
@@ -379,8 +389,8 @@ public boolean equals(Object obj) {
379389
@Override
380390
public int hashCode() {
381391
int hash = m_method.hashCode();
382-
if (m_instance != null) {
383-
hash = hash * 31 + System.identityHashCode(m_instance);
392+
if (getInstance() != null) {
393+
hash = hash * 31 + System.identityHashCode(getInstance());
384394
}
385395
return hash;
386396
}
@@ -790,8 +800,11 @@ public String getQualifiedName() {
790800

791801
@Override
792802
public IParameterInfo getFactoryMethodParamsInfo() {
793-
if (m_instance instanceof IParameterInfo) {
794-
return (IParameterInfo) m_instance;
803+
if (m_instance == null) {
804+
return null;
805+
}
806+
if (m_instance.getInstance() instanceof IParameterInfo) {
807+
return (IParameterInfo) m_instance.getInstance();
795808
}
796809
return null;
797810
}

0 commit comments

Comments
 (0)