Skip to content

Commit

Permalink
Add Unique Id for all test class instances (#3083)
Browse files Browse the repository at this point in the history
* Add Unique Id for all test class instances

Closes #3079
  • Loading branch information
krmahadevan committed Mar 14, 2024
1 parent 438674c commit 3308b3c
Show file tree
Hide file tree
Showing 28 changed files with 529 additions and 111 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Current (7.10.0)
Fixed: GITHUB:3084: Document project's PGP artifact signing keys (Krishnan Mahadevan)
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
20 changes: 18 additions & 2 deletions testng-core-api/src/main/java/org/testng/IClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,30 @@ 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);

/**
* Returns all the instances the methods will be invoked upon. This will typically be an array of
* one object in the absence of a @Factory annotation.
*
* @param create flag if a new set of instances must be returned (if set to <code>false</code>)
* @param errorMsgPrefix - Text that should be prefixed to the error message when there are
* issues. Can be empty.
* @return All the instances the methods will be invoked upon.
* @deprecated - As of TestNG <code>v7.10.0</code>
*/
@Deprecated
default Object[] getInstances(boolean create, String errorMsgPrefix) {
return getInstances(create);
}

long[] getInstanceHashCodes();

/**
* @param instance - The instance to be added.
* @deprecated - As of TestNG <code>v7.10.0</code>
*/
@Deprecated
void addInstance(Object instance);
}
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,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 Down
35 changes: 22 additions & 13 deletions testng-core/src/main/java/org/testng/TestClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* This class represents a test class: - The test methods - The configuration methods (test and
* method) - The class file
*/
class TestClass extends NoOpTestClass implements ITestClass, ITestClassConfigInfo {
class TestClass extends NoOpTestClass implements ITestClass, ITestClassConfigInfo, IObject {

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

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

@Override
public long[] getInstanceHashCodes() {
return iClass.getInstanceHashCodes();
return IObject.instanceHashCodes(iClass);
}

@Override
public void addInstance(Object instance) {
iClass.addInstance(instance);
}

@Override
public void addObject(IObject.IdentifiableObject instance) {
IObject.cast(iClass).ifPresent(it -> it.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 : IObject.objects(iClass, false)) {
m_beforeSuiteMethods =
ConfigurationMethod.createSuiteConfigurationMethods(
objectFactory,
Expand Down Expand Up @@ -182,7 +191,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 +243,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 : IObject.objects(iClass, 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 @@ -32,7 +32,7 @@ protected IClass findOrCreateIClass(
ITestContext context,
Class<?> cls,
XmlClass xmlClass,
Object instance,
IObject.IdentifiableObject instance,
IAnnotationFinder annotationFinder,
ITestObjectFactory objectFactory) {

Expand Down
33 changes: 24 additions & 9 deletions testng-core/src/main/java/org/testng/internal/BaseTestMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;
Expand All @@ -34,7 +35,8 @@
import org.testng.xml.XmlTest;

/** Superclass to represent both &#64;Test and &#64;Configuration methods. */
public abstract class BaseTestMethod implements ITestNGMethod, IInvocationStatus {
public abstract class BaseTestMethod
implements ITestNGMethod, IInvocationStatus, IInstanceIdentity {

private static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" +");

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,13 +150,23 @@ 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 Optional.ofNullable(m_instance)
.map(IObject.IdentifiableObject::getInstanceId)
.orElse(null);
}

/** {@inheritDoc} */
@Override
public long[] getInstanceHashCodes() {
return m_testClass.getInstanceHashCodes();
return IObject.instanceHashCodes(m_testClass);
}

/**
Expand Down Expand Up @@ -379,8 +391,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 +802,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
51 changes: 33 additions & 18 deletions testng-core/src/main/java/org/testng/internal/ClassImpl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.testng.internal;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.testng.IClass;
Expand All @@ -20,15 +21,15 @@
import org.testng.xml.XmlTest;

/** Implementation of an IClass. */
public class ClassImpl implements IClass {
public class ClassImpl implements IClass, IObject {

private final Class<?> m_class;
private Object m_defaultInstance = null;
private IObject.IdentifiableObject m_defaultInstance = null;
private final IAnnotationFinder m_annotationFinder;
private final List<Object> m_instances = Lists.newArrayList();
private final List<IObject.IdentifiableObject> identifiableObjects = Lists.newArrayList();
private final Map<Class<?>, IClass> m_classes;
private long[] m_instanceHashCodes;
private final Object m_instance;
private final IObject.IdentifiableObject m_instance;
private final ITestObjectFactory m_objectFactory;
private String m_testName = null;
private final XmlClass m_xmlClass;
Expand All @@ -38,7 +39,7 @@ public ClassImpl(
ITestContext context,
Class<?> cls,
XmlClass xmlClass,
Object instance,
IObject.IdentifiableObject instance,
Map<Class<?>, IClass> classes,
IAnnotationFinder annotationFinder,
ITestObjectFactory objectFactory) {
Expand All @@ -49,8 +50,8 @@ public ClassImpl(
m_annotationFinder = annotationFinder;
m_instance = instance;
m_objectFactory = objectFactory;
if (instance instanceof ITest) {
m_testName = ((ITest) instance).getTestName();
if (IObject.IdentifiableObject.unwrap(instance) instanceof ITest) {
m_testName = ((ITest) instance.getInstance()).getTestName();
}
if (m_testName == null) {
ITestAnnotation annotation = m_annotationFinder.findAnnotation(cls, ITestAnnotation.class);
Expand Down Expand Up @@ -90,7 +91,7 @@ public XmlClass getXmlClass() {
return m_xmlClass;
}

private Object getDefaultInstance(boolean create, String errMsgPrefix) {
private IObject.IdentifiableObject getDefaultInstance(boolean create, String errMsgPrefix) {
if (m_defaultInstance == null) {
if (m_instance != null) {
m_defaultInstance = m_instance;
Expand All @@ -103,10 +104,12 @@ private Object getDefaultInstance(boolean create, String errMsgPrefix) {
BasicAttributes basic = new BasicAttributes(this, null);
DetailedAttributes detailed = newDetailedAttributes(create, errMsgPrefix);
CreationAttributes attributes = new CreationAttributes(m_testContext, basic, detailed);
m_defaultInstance = dispenser.dispense(attributes);
Object raw = dispenser.dispense(attributes);
if (raw != null) {
m_defaultInstance = new IObject.IdentifiableObject(raw);
}
}
}

return m_defaultInstance;
}

Expand All @@ -117,21 +120,33 @@ public Object[] getInstances(boolean create) {

@Override
public Object[] getInstances(boolean create, String errorMsgPrefix) {
Object[] result = {};
return Arrays.stream(getObjects(create, errorMsgPrefix))
.map(IdentifiableObject::getInstance)
.toArray(Object[]::new);
}

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

@Override
public IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) {
IdentifiableObject[] result = {};

if (!m_instances.isEmpty()) {
result = m_instances.toArray(new Object[0]);
if (!identifiableObjects.isEmpty()) {
result = identifiableObjects.toArray(new IdentifiableObject[0]);
} else {
Object defaultInstance = getDefaultInstance(create, errorMsgPrefix);
IdentifiableObject defaultInstance = getDefaultInstance(create, errorMsgPrefix);
if (defaultInstance != null) {
result = new Object[] {defaultInstance};
result = new IdentifiableObject[] {defaultInstance};
}
}

int m_instanceCount = m_instances.size();
int m_instanceCount = identifiableObjects.size();
m_instanceHashCodes = new long[m_instanceCount];
for (int i = 0; i < m_instanceCount; i++) {
m_instanceHashCodes[i] = computeHashCode(m_instances.get(i));
m_instanceHashCodes[i] = computeHashCode(identifiableObjects.get(i).getInstance());
}
return result;
}
Expand All @@ -143,7 +158,7 @@ public String toString() {

@Override
public void addInstance(Object instance) {
m_instances.add(instance);
addObject(new IdentifiableObject(instance));
}

private static int computeHashCode(Object instance) {
Expand Down

0 comments on commit 3308b3c

Please sign in to comment.