Skip to content

Commit

Permalink
Add Unique Id for all test class instances
Browse files Browse the repository at this point in the history
Closes #3079
  • Loading branch information
krmahadevan committed Mar 5, 2024
1 parent eb80671 commit 8fdd106
Show file tree
Hide file tree
Showing 27 changed files with 456 additions and 97 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Current (7.10.0)
Fixed: GITHUB-3079: Associate a unique id with every test class object instantiated by TestNG (Krishnan Mahadevan)
Fixed: GITHUB:3040: replace the usages of synchronized with ReentrantLock (Krishnan Mahadevan)
Fixed: GITHUB-3041: TestNG 7.x DataProvider works in opposite to TestNG 6.x when retrying tests. (Krishnan Mahadevan)
Fixed: GITHUB-3066: How to dynamically adjust the number of TestNG threads after IExecutorFactory is deprecated? (Krishnan Mahadevan)
Expand Down
8 changes: 5 additions & 3 deletions testng-core-api/src/main/java/org/testng/IClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.testng.xml.XmlTest;

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

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

@Deprecated
default Object[] getInstances(boolean create, String errorMsgPrefix) {
return getInstances(create);
}

long[] getInstanceHashCodes();

@Deprecated
void addInstance(Object instance);
}
65 changes: 65 additions & 0 deletions testng-core-api/src/main/java/org/testng/IObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.testng;

import java.util.Objects;
import java.util.UUID;

/** Represents the associations of a class with one or more instances. */
public interface IObject {

default IdentifiableObject[] getObjects(boolean create) {
return getObjects(create, "");
}

IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix);

long[] getInstanceHashCodes();

void addObject(IdentifiableObject instance);

/** A wrapper object that associates a unique id to every unique test class object. */
class IdentifiableObject {
private final Object instance;
private final UUID instanceId;

public IdentifiableObject(Object instance) {
this(instance, UUID.randomUUID());
}

public IdentifiableObject(Object instance, UUID instanceId) {
this.instance = instance;
this.instanceId = instanceId;
}

public static Object unwrap(IdentifiableObject object) {
if (object == null) {
return null;
}
return object.getInstance();
}

public UUID getInstanceId() {
return instanceId;
}

public Object getInstance() {
return instance;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
IdentifiableObject that = (IdentifiableObject) object;
return Objects.equals(instance, that.instance);
}

@Override
public int hashCode() {
return Objects.hash(instance);
}
}
}
11 changes: 10 additions & 1 deletion testng-core-api/src/main/java/org/testng/ITestNGMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import org.testng.annotations.CustomAttribute;
import org.testng.internal.ConstructorOrMethod;
Expand Down Expand Up @@ -279,7 +280,7 @@ default CustomAttribute[] getAttributes() {

/**
* @return - An {@link IDataProviderMethod} for a data provider powered test method and <code>null
* </code> otherwise.
* </code> otherwise.
*/
default IDataProviderMethod getDataProviderMethod() {
return null;
Expand All @@ -296,4 +297,12 @@ default Class<?>[] getParameterTypes() {
default boolean isIgnoreFailure() {
return false;
}

/**
* @return - A <code>{@link UUID}</code> that represents a unique id which is associated with
* every test class object.
*/
default UUID getInstanceId() {
return null;
}
}
36 changes: 25 additions & 11 deletions testng-core/src/main/java/org/testng/TestClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,13 @@ private void initTestClassesAndInstances() {
//
// TestClasses and instances
//
Object[] instances = getInstances(true, this.m_errorMsgPrefix);
for (Object instance : instances) {
instance = IParameterInfo.embeddedInstance(instance);
if (instance instanceof ITest) {
testName = ((ITest) instance).getTestName();
break;
}
}
IdentifiableObject[] instances = getObjects(true, this.m_errorMsgPrefix);
Arrays.stream(instances)
.map(IdentifiableObject::getInstance)
.map(IParameterInfo::embeddedInstance)
.filter(it -> it instanceof ITest)
.findFirst()
.ifPresent(it -> testName = ((ITest) it).getTestName());
if (testName == null) {
testName = iClass.getTestName();
}
Expand All @@ -124,11 +123,21 @@ public Object[] getInstances(boolean create) {
return iClass.getInstances(create);
}

@Override
public IdentifiableObject[] getObjects(boolean create) {
return iClass.getObjects(create);
}

@Override
public Object[] getInstances(boolean create, String errorMsgPrefix) {
return iClass.getInstances(create, this.m_errorMsgPrefix);
}

@Override
public IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) {
return iClass.getObjects(create, this.m_errorMsgPrefix);
}

@Override
public long[] getInstanceHashCodes() {
return iClass.getInstanceHashCodes();
Expand All @@ -139,11 +148,16 @@ public void addInstance(Object instance) {
iClass.addInstance(instance);
}

@Override
public void addObject(IdentifiableObject instance) {
iClass.addObject(instance);
}

private void initMethods() {
ITestNGMethod[] methods = testMethodFinder.getTestMethods(m_testClass, xmlTest);
m_testMethods = createTestMethods(methods);

for (Object eachInstance : iClass.getInstances(false)) {
for (IdentifiableObject eachInstance : iClass.getObjects(false)) {
m_beforeSuiteMethods =
ConfigurationMethod.createSuiteConfigurationMethods(
objectFactory,
Expand Down Expand Up @@ -182,7 +196,7 @@ private void initMethods() {
true,
xmlTest,
eachInstance);
Object instance = IParameterInfo.embeddedInstance(eachInstance);
Object instance = IParameterInfo.embeddedInstance(eachInstance.getInstance());
beforeClassConfig.put(instance, Arrays.asList(m_beforeClassMethods));
m_afterClassMethods =
ConfigurationMethod.createClassConfigurationMethods(
Expand Down Expand Up @@ -234,7 +248,7 @@ private ITestNGMethod[] createTestMethods(ITestNGMethod[] methods) {
for (ITestNGMethod tm : methods) {
ConstructorOrMethod m = tm.getConstructorOrMethod();
if (m.getDeclaringClass().isAssignableFrom(m_testClass)) {
for (Object o : iClass.getInstances(false)) {
for (IdentifiableObject o : iClass.getObjects(false)) {
log(4, "Adding method " + tm + " on TestClass " + m_testClass);
vResult.add(new TestNGMethod(objectFactory, m.getMethod(), annotationFinder, xmlTest, o));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Map;
import org.testng.IClass;
import org.testng.IObject;
import org.testng.ITestClassFinder;
import org.testng.ITestContext;
import org.testng.ITestObjectFactory;
Expand Down Expand Up @@ -32,7 +33,7 @@ protected IClass findOrCreateIClass(
ITestContext context,
Class<?> cls,
XmlClass xmlClass,
Object instance,
IObject.IdentifiableObject instance,
IAnnotationFinder annotationFinder,
ITestObjectFactory objectFactory) {

Expand Down
27 changes: 20 additions & 7 deletions testng-core/src/main/java/org/testng/internal/BaseTestMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
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.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.testng.IClass;
import org.testng.IObject;
import org.testng.IRetryAnalyzer;
import org.testng.ITestClass;
import org.testng.ITestNGMethod;
Expand Down Expand Up @@ -81,7 +83,7 @@ public abstract class BaseTestMethod implements ITestNGMethod, IInvocationStatus
private int m_interceptedPriority;

private XmlTest m_xmlTest;
private final Object m_instance;
private final IObject.IdentifiableObject m_instance;

private final Map<String, IRetryAnalyzer> m_testMethodToRetryAnalyzer = Maps.newConcurrentMap();
protected final ITestObjectFactory m_objectFactory;
Expand All @@ -91,7 +93,7 @@ public BaseTestMethod(
String methodName,
ConstructorOrMethod com,
IAnnotationFinder annotationFinder,
Object instance) {
IObject.IdentifiableObject instance) {
m_objectFactory = objectFactory;
m_methodClass = com.getDeclaringClass();
m_method = com;
Expand Down Expand Up @@ -148,7 +150,15 @@ public String getMethodName() {

@Override
public Object getInstance() {
return IParameterInfo.embeddedInstance(m_instance);
return Optional.ofNullable(m_instance)
.map(IObject.IdentifiableObject::getInstance)
.map(IParameterInfo::embeddedInstance)
.orElse(null);
}

@Override
public UUID getInstanceId() {
return m_instance.getInstanceId();
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -379,8 +389,8 @@ public boolean equals(Object obj) {
@Override
public int hashCode() {
int hash = m_method.hashCode();
if (m_instance != null) {
hash = hash * 31 + System.identityHashCode(m_instance);
if (getInstance() != null) {
hash = hash * 31 + System.identityHashCode(getInstance());
}
return hash;
}
Expand Down Expand Up @@ -790,8 +800,11 @@ public String getQualifiedName() {

@Override
public IParameterInfo getFactoryMethodParamsInfo() {
if (m_instance instanceof IParameterInfo) {
return (IParameterInfo) m_instance;
if (m_instance == null) {
return null;
}
if (m_instance.getInstance() instanceof IParameterInfo) {
return (IParameterInfo) m_instance.getInstance();
}
return null;
}
Expand Down

0 comments on commit 8fdd106

Please sign in to comment.