Skip to content

Commit 28fedc0

Browse files
committed
Add Unique Id for all test class instances
Closes #3079
1 parent 438674c commit 28fedc0

27 files changed

+505
-109
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Current (7.10.0)
22
Fixed: GITHUB:3084: Document project's PGP artifact signing keys (Krishnan Mahadevan)
3+
Fixed: GITHUB-3079: Associate a unique id with every test class object instantiated by TestNG (Krishnan Mahadevan)
34
Fixed: GITHUB:3040: replace the usages of synchronized with ReentrantLock (Krishnan Mahadevan)
45
Fixed: GITHUB-3041: TestNG 7.x DataProvider works in opposite to TestNG 6.x when retrying tests. (Krishnan Mahadevan)
56
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: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,30 @@ 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+
/**
36+
* Returns all the instances the methods will be invoked upon. This will typically be an array of
37+
* one object in the absence of a @Factory annotation.
38+
*
39+
* @param create flag if a new set of instances must be returned (if set to <code>false</code>)
40+
* @param errorMsgPrefix - Text that should be prefixed to the error message when there are
41+
* issues. Can be empty.
42+
* @return All the instances the methods will be invoked upon.
43+
* @deprecated - As of TestNG <code>v7.10.0</code>
44+
*/
45+
@Deprecated
3346
default Object[] getInstances(boolean create, String errorMsgPrefix) {
3447
return getInstances(create);
3548
}
3649

37-
long[] getInstanceHashCodes();
38-
50+
/**
51+
* @param instance - The instance to be added.
52+
* @deprecated - As of TestNG <code>v7.10.0</code>
53+
*/
54+
@Deprecated
3955
void addInstance(Object instance);
4056
}

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
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.testng.internal;
2+
3+
import java.util.Objects;
4+
import java.util.Optional;
5+
import java.util.UUID;
6+
7+
/**
8+
* Represents the associations of a class with one or more instances. Relevant with <code>@Factory
9+
* </code> annotation.
10+
*/
11+
public interface IObject {
12+
13+
/**
14+
* Returns all the instances the methods will be invoked upon. This will typically be an array of
15+
* one object in the absence of a @Factory annotation.
16+
*
17+
* @param create - <code>true</code> if objects should be created before returning.
18+
* @param errorMsgPrefix - Text that should be prefixed to the error message when there are
19+
* issues. Can be empty.
20+
* @return - An array of {@link IdentifiableObject} objects
21+
*/
22+
IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix);
23+
24+
/** @return - An array representing the hash codes of the corresponding instances. */
25+
long[] getInstanceHashCodes();
26+
27+
/** @param instance - The instance that should be added to the list of instances. */
28+
void addObject(IdentifiableObject instance);
29+
30+
/**
31+
* @param object - The object that should be inspected for its compatibility with {@link IObject}.
32+
* @return - An array representing the hash codes of the corresponding instances.
33+
*/
34+
static long[] instanceHashCodes(Object object) {
35+
return cast(object).map(IObject::getInstanceHashCodes).orElse(new long[] {});
36+
}
37+
38+
/**
39+
* @param object - The object that should be inspected for its compatibility with {@link IObject}.
40+
* @param create - <code>true</code> if objects should be created before returning.
41+
* @return - An array (can be empty is instance compatibility fails) of {@link IdentifiableObject}
42+
* objects.
43+
*/
44+
static IdentifiableObject[] objects(Object object, boolean create) {
45+
return objects(object, create, "");
46+
}
47+
48+
/**
49+
* @param object - The object that should be inspected for its compatibility with {@link IObject}.
50+
* @param create - <code>true</code> if objects should be created before returning.
51+
* @param errorMsgPrefix - Text that should be prefixed to the error message when there are
52+
* issues. Can be empty.
53+
* @return - An array (can be empty is instance compatibility fails) of {@link IdentifiableObject}
54+
* objects.
55+
*/
56+
static IdentifiableObject[] objects(Object object, boolean create, String errorMsgPrefix) {
57+
return cast(object)
58+
.map(it -> it.getObjects(create, errorMsgPrefix))
59+
.orElse(new IdentifiableObject[] {});
60+
}
61+
62+
/**
63+
* @param object - The object that should be inspected for its compatibility with {@link IObject}.
64+
* @return - If the incoming object is an instance of {@link IObject} then the cast instance is
65+
* wrapped within {@link Optional} else it would be an {@link Optional#empty()}
66+
*/
67+
static Optional<IObject> cast(Object object) {
68+
if (object instanceof IObject) {
69+
return Optional.of((IObject) object);
70+
}
71+
return Optional.empty();
72+
}
73+
74+
/** A wrapper object that associates a unique id to every unique test class object. */
75+
class IdentifiableObject {
76+
private final Object instance;
77+
private final UUID instanceId;
78+
79+
public IdentifiableObject(Object instance) {
80+
this(instance, UUID.randomUUID());
81+
}
82+
83+
public IdentifiableObject(Object instance, UUID instanceId) {
84+
this.instance = instance;
85+
this.instanceId = instanceId;
86+
}
87+
88+
public static Object unwrap(IdentifiableObject object) {
89+
if (object == null) {
90+
return null;
91+
}
92+
return object.getInstance();
93+
}
94+
95+
public UUID getInstanceId() {
96+
return instanceId;
97+
}
98+
99+
public Object getInstance() {
100+
return instance;
101+
}
102+
103+
@Override
104+
public boolean equals(Object object) {
105+
if (this == object) return true;
106+
if (object == null || getClass() != object.getClass()) return false;
107+
IdentifiableObject that = (IdentifiableObject) object;
108+
return Objects.equals(instanceId, that.instanceId);
109+
}
110+
111+
@Override
112+
public int hashCode() {
113+
return Objects.hash(instanceId);
114+
}
115+
}
116+
}

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* This class represents a test class: - The test methods - The configuration methods (test and
1717
* method) - The class file
1818
*/
19-
class TestClass extends NoOpTestClass implements ITestClass, ITestClassConfigInfo {
19+
class TestClass extends NoOpTestClass implements ITestClass, ITestClassConfigInfo, IObject {
2020

2121
private IAnnotationFinder annotationFinder = null;
2222
// The Strategy used to locate test methods (TestNG, JUnit, etc...)
@@ -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+
IObject.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
}
@@ -129,21 +128,31 @@ public Object[] getInstances(boolean create, String errorMsgPrefix) {
129128
return iClass.getInstances(create, this.m_errorMsgPrefix);
130129
}
131130

131+
@Override
132+
public IObject.IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) {
133+
return IObject.objects(iClass, create, errorMsgPrefix);
134+
}
135+
132136
@Override
133137
public long[] getInstanceHashCodes() {
134-
return iClass.getInstanceHashCodes();
138+
return IObject.instanceHashCodes(iClass);
135139
}
136140

137141
@Override
138142
public void addInstance(Object instance) {
139143
iClass.addInstance(instance);
140144
}
141145

146+
@Override
147+
public void addObject(IObject.IdentifiableObject instance) {
148+
IObject.cast(iClass).ifPresent(it -> it.addObject(instance));
149+
}
150+
142151
private void initMethods() {
143152
ITestNGMethod[] methods = testMethodFinder.getTestMethods(m_testClass, xmlTest);
144153
m_testMethods = createTestMethods(methods);
145154

146-
for (Object eachInstance : iClass.getInstances(false)) {
155+
for (IdentifiableObject eachInstance : IObject.objects(iClass, false)) {
147156
m_beforeSuiteMethods =
148157
ConfigurationMethod.createSuiteConfigurationMethods(
149158
objectFactory,
@@ -182,7 +191,7 @@ private void initMethods() {
182191
true,
183192
xmlTest,
184193
eachInstance);
185-
Object instance = IParameterInfo.embeddedInstance(eachInstance);
194+
Object instance = IParameterInfo.embeddedInstance(eachInstance.getInstance());
186195
beforeClassConfig.put(instance, Arrays.asList(m_beforeClassMethods));
187196
m_afterClassMethods =
188197
ConfigurationMethod.createClassConfigurationMethods(
@@ -234,7 +243,7 @@ private ITestNGMethod[] createTestMethods(ITestNGMethod[] methods) {
234243
for (ITestNGMethod tm : methods) {
235244
ConstructorOrMethod m = tm.getConstructorOrMethod();
236245
if (m.getDeclaringClass().isAssignableFrom(m_testClass)) {
237-
for (Object o : iClass.getInstances(false)) {
246+
for (IdentifiableObject o : IObject.objects(iClass, false)) {
238247
log(4, "Adding method " + tm + " on TestClass " + m_testClass);
239248
vResult.add(new TestNGMethod(objectFactory, m.getMethod(), annotationFinder, xmlTest, o));
240249
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected IClass findOrCreateIClass(
3232
ITestContext context,
3333
Class<?> cls,
3434
XmlClass xmlClass,
35-
Object instance,
35+
IObject.IdentifiableObject instance,
3636
IAnnotationFinder annotationFinder,
3737
ITestObjectFactory objectFactory) {
3838

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
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;
@@ -81,7 +82,7 @@ public abstract class BaseTestMethod implements ITestNGMethod, IInvocationStatus
8182
private int m_interceptedPriority;
8283

8384
private XmlTest m_xmlTest;
84-
private final Object m_instance;
85+
private final IObject.IdentifiableObject m_instance;
8586

8687
private final Map<String, IRetryAnalyzer> m_testMethodToRetryAnalyzer = Maps.newConcurrentMap();
8788
protected final ITestObjectFactory m_objectFactory;
@@ -91,7 +92,7 @@ public BaseTestMethod(
9192
String methodName,
9293
ConstructorOrMethod com,
9394
IAnnotationFinder annotationFinder,
94-
Object instance) {
95+
IObject.IdentifiableObject instance) {
9596
m_objectFactory = objectFactory;
9697
m_methodClass = com.getDeclaringClass();
9798
m_method = com;
@@ -148,13 +149,21 @@ public String getMethodName() {
148149

149150
@Override
150151
public Object getInstance() {
151-
return IParameterInfo.embeddedInstance(m_instance);
152+
return Optional.ofNullable(m_instance)
153+
.map(IObject.IdentifiableObject::getInstance)
154+
.map(IParameterInfo::embeddedInstance)
155+
.orElse(null);
156+
}
157+
158+
@Override
159+
public UUID getInstanceId() {
160+
return m_instance.getInstanceId();
152161
}
153162

154163
/** {@inheritDoc} */
155164
@Override
156165
public long[] getInstanceHashCodes() {
157-
return m_testClass.getInstanceHashCodes();
166+
return IObject.instanceHashCodes(m_testClass);
158167
}
159168

160169
/**
@@ -379,8 +388,8 @@ public boolean equals(Object obj) {
379388
@Override
380389
public int hashCode() {
381390
int hash = m_method.hashCode();
382-
if (m_instance != null) {
383-
hash = hash * 31 + System.identityHashCode(m_instance);
391+
if (getInstance() != null) {
392+
hash = hash * 31 + System.identityHashCode(getInstance());
384393
}
385394
return hash;
386395
}
@@ -790,8 +799,11 @@ public String getQualifiedName() {
790799

791800
@Override
792801
public IParameterInfo getFactoryMethodParamsInfo() {
793-
if (m_instance instanceof IParameterInfo) {
794-
return (IParameterInfo) m_instance;
802+
if (m_instance == null) {
803+
return null;
804+
}
805+
if (m_instance.getInstance() instanceof IParameterInfo) {
806+
return (IParameterInfo) m_instance.getInstance();
795807
}
796808
return null;
797809
}

0 commit comments

Comments
 (0)