diff --git a/CHANGES.txt b/CHANGES.txt index 67e82fb0a..815e3192d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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) diff --git a/testng-core-api/src/main/java/org/testng/IClass.java b/testng-core-api/src/main/java/org/testng/IClass.java index adf1df903..2c94a9aea 100644 --- a/testng-core-api/src/main/java/org/testng/IClass.java +++ b/testng-core-api/src/main/java/org/testng/IClass.java @@ -27,14 +27,30 @@ public interface IClass { * * @param create flag if a new set of instances must be returned (if set to false) * @return All the instances the methods will be invoked upon. + * @deprecated - As of TestNG v7.10.0 */ + @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 false) + * @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 v7.10.0 + */ + @Deprecated default Object[] getInstances(boolean create, String errorMsgPrefix) { return getInstances(create); } - long[] getInstanceHashCodes(); - + /** + * @param instance - The instance to be added. + * @deprecated - As of TestNG v7.10.0 + */ + @Deprecated void addInstance(Object instance); } diff --git a/testng-core-api/src/main/java/org/testng/ITestNGMethod.java b/testng-core-api/src/main/java/org/testng/ITestNGMethod.java index 0a5ec017f..bff0385de 100644 --- a/testng-core-api/src/main/java/org/testng/ITestNGMethod.java +++ b/testng-core-api/src/main/java/org/testng/ITestNGMethod.java @@ -279,7 +279,7 @@ default CustomAttribute[] getAttributes() { /** * @return - An {@link IDataProviderMethod} for a data provider powered test method and null - * otherwise. + * otherwise. */ default IDataProviderMethod getDataProviderMethod() { return null; diff --git a/testng-core/src/main/java/org/testng/TestClass.java b/testng-core/src/main/java/org/testng/TestClass.java index 4bc150f2e..1ce6add95 100644 --- a/testng-core/src/main/java/org/testng/TestClass.java +++ b/testng-core/src/main/java/org/testng/TestClass.java @@ -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...) @@ -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(); } @@ -129,9 +128,14 @@ 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 @@ -139,11 +143,16 @@ 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, @@ -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( @@ -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)); } diff --git a/testng-core/src/main/java/org/testng/internal/BaseClassFinder.java b/testng-core/src/main/java/org/testng/internal/BaseClassFinder.java index 5fbd2b526..75af20b88 100644 --- a/testng-core/src/main/java/org/testng/internal/BaseClassFinder.java +++ b/testng-core/src/main/java/org/testng/internal/BaseClassFinder.java @@ -32,7 +32,7 @@ protected IClass findOrCreateIClass( ITestContext context, Class cls, XmlClass xmlClass, - Object instance, + IObject.IdentifiableObject instance, IAnnotationFinder annotationFinder, ITestObjectFactory objectFactory) { 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 976f8b855..503e58a99 100644 --- a/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java +++ b/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java @@ -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; @@ -34,7 +35,8 @@ import org.testng.xml.XmlTest; /** Superclass to represent both @Test and @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(" +"); @@ -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 m_testMethodToRetryAnalyzer = Maps.newConcurrentMap(); protected final ITestObjectFactory m_objectFactory; @@ -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; @@ -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); } /** @@ -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; } @@ -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; } diff --git a/testng-core/src/main/java/org/testng/internal/ClassImpl.java b/testng-core/src/main/java/org/testng/internal/ClassImpl.java index 262f40eb6..f893b6008 100644 --- a/testng-core/src/main/java/org/testng/internal/ClassImpl.java +++ b/testng-core/src/main/java/org/testng/internal/ClassImpl.java @@ -1,5 +1,6 @@ package org.testng.internal; +import java.util.Arrays; import java.util.List; import java.util.Map; import org.testng.IClass; @@ -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 m_instances = Lists.newArrayList(); + private final List identifiableObjects = Lists.newArrayList(); private final Map, 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; @@ -38,7 +39,7 @@ public ClassImpl( ITestContext context, Class cls, XmlClass xmlClass, - Object instance, + IObject.IdentifiableObject instance, Map, IClass> classes, IAnnotationFinder annotationFinder, ITestObjectFactory objectFactory) { @@ -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); @@ -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; @@ -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; } @@ -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; } @@ -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) { diff --git a/testng-core/src/main/java/org/testng/internal/ConfigurationMethod.java b/testng-core/src/main/java/org/testng/internal/ConfigurationMethod.java index 9b5169b7b..69702a5f4 100644 --- a/testng-core/src/main/java/org/testng/internal/ConfigurationMethod.java +++ b/testng-core/src/main/java/org/testng/internal/ConfigurationMethod.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import org.testng.ITestNGMethod; import org.testng.ITestObjectFactory; import org.testng.annotations.IAnnotation; @@ -66,19 +67,18 @@ private ConfigurationMethod( String[] beforeGroups, String[] afterGroups, boolean initialize, - Object instance) { - super( - objectFactory, - com.getName(), - com, - annotationFinder, - IParameterInfo.embeddedInstance(instance)); + IObject.IdentifiableObject instance) { + super(objectFactory, com.getName(), com, annotationFinder, instance); if (initialize) { init(); } this.factoryMethodInfo = - (instance instanceof IParameterInfo) ? (IParameterInfo) instance : null; + (IParameterInfo) + Optional.ofNullable(instance) + .map(IObject.IdentifiableObject::getInstance) + .filter(it -> it instanceof IParameterInfo) + .orElse(null); m_isBeforeSuiteConfiguration = isBeforeSuite; m_isAfterSuiteConfiguration = isAfterSuite; @@ -122,7 +122,7 @@ public ConfigurationMethod( String[] beforeGroups, String[] afterGroups, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { this( objectFactory, com, @@ -156,7 +156,7 @@ private static ITestNGMethod[] createMethods( boolean isBeforeMethod, boolean isAfterMethod, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { List result = Lists.newArrayList(); for (ITestNGMethod method : methods) { if (Modifier.isStatic(method.getConstructorOrMethod().getMethod().getModifiers())) { @@ -196,7 +196,7 @@ public static ITestNGMethod[] createSuiteConfigurationMethods( ITestNGMethod[] methods, IAnnotationFinder annotationFinder, boolean isBefore, - Object instance) { + IObject.IdentifiableObject instance) { return createMethods( objectFactory, @@ -220,7 +220,7 @@ public static ITestNGMethod[] createTestConfigurationMethods( IAnnotationFinder annotationFinder, boolean isBefore, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { return createMethods( objectFactory, methods, @@ -243,7 +243,7 @@ public static ITestNGMethod[] createClassConfigurationMethods( IAnnotationFinder annotationFinder, boolean isBefore, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { return createMethods( objectFactory, methods, @@ -265,7 +265,7 @@ public static ITestNGMethod[] createBeforeConfigurationMethods( ITestNGMethod[] methods, IAnnotationFinder annotationFinder, boolean isBefore, - Object instance) { + IObject.IdentifiableObject instance) { ITestNGMethod[] result = new ITestNGMethod[methods.length]; for (int i = 0; i < methods.length; i++) { result[i] = @@ -296,7 +296,7 @@ public static ITestNGMethod[] createAfterConfigurationMethods( ITestNGMethod[] methods, IAnnotationFinder annotationFinder, boolean isBefore, - Object instance) { + IObject.IdentifiableObject instance) { return Arrays.stream(methods) .parallel() .map( @@ -327,7 +327,7 @@ public static ITestNGMethod[] createTestMethodConfigurationMethods( IAnnotationFinder annotationFinder, boolean isBefore, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { return createMethods( objectFactory, methods, @@ -507,7 +507,7 @@ public ConfigurationMethod clone() { getBeforeGroups(), getAfterGroups(), false /* do not call init() */, - getFactoryMethodParamsInfo()); + new IObject.IdentifiableObject(getInstance(), getInstanceId())); clone.m_testClass = getTestClass(); clone.setDate(getDate()); clone.setGroups(getGroups()); diff --git a/testng-core/src/main/java/org/testng/internal/FactoryMethod.java b/testng-core/src/main/java/org/testng/internal/FactoryMethod.java index 6c5b0085b..c5bf4de1f 100644 --- a/testng-core/src/main/java/org/testng/internal/FactoryMethod.java +++ b/testng-core/src/main/java/org/testng/internal/FactoryMethod.java @@ -71,13 +71,14 @@ private void init(Object instance, IAnnotationFinder annotationFinder, Construct // constructor outside of this package. FactoryMethod( ConstructorOrMethod com, - Object instance, + IObject.IdentifiableObject identifiable, IAnnotationFinder annotationFinder, ITestContext testContext, ITestObjectFactory objectFactory, DataProviderHolder holder) { - super(objectFactory, com.getName(), com, annotationFinder, instance); + super(objectFactory, com.getName(), com, annotationFinder, identifiable); this.holder = holder; + Object instance = IObject.IdentifiableObject.unwrap(identifiable); init(instance, annotationFinder, com); Utils.checkInstanceOrStatic(instance, com.getMethod()); Utils.checkReturnType(com.getMethod(), Object[].class, IInstanceInfo[].class); @@ -117,7 +118,7 @@ private void init(Object instance, IAnnotationFinder annotationFinder, Construct factoryAnnotation = annotationFinder.findAnnotation(com, IFactoryAnnotation.class); - m_instance = instance; + m_instance = getInstance(); m_testContext = testContext; NoOpTestClass tc = new NoOpTestClass(); tc.setTestClass(declaringClass); diff --git a/testng-core/src/main/java/org/testng/internal/IInstanceIdentity.java b/testng-core/src/main/java/org/testng/internal/IInstanceIdentity.java new file mode 100644 index 000000000..75f1c4aa1 --- /dev/null +++ b/testng-core/src/main/java/org/testng/internal/IInstanceIdentity.java @@ -0,0 +1,19 @@ +package org.testng.internal; + +import java.util.UUID; + +public interface IInstanceIdentity { + + /** + * @return - A {@link UUID} that represents a unique id which is associated with + * every test class object. + */ + UUID getInstanceId(); + + static Object getInstanceId(Object object) { + if (object instanceof IInstanceIdentity) { + return ((IInstanceIdentity) object).getInstanceId(); + } + return object; + } +} diff --git a/testng-core/src/main/java/org/testng/internal/IObject.java b/testng-core/src/main/java/org/testng/internal/IObject.java new file mode 100644 index 000000000..04c04946f --- /dev/null +++ b/testng-core/src/main/java/org/testng/internal/IObject.java @@ -0,0 +1,116 @@ +package org.testng.internal; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * Represents the associations of a class with one or more instances. Relevant with @Factory + * annotation. + */ +public interface IObject { + + /** + * 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 - true if objects should be created before returning. + * @param errorMsgPrefix - Text that should be prefixed to the error message when there are + * issues. Can be empty. + * @return - An array of {@link IdentifiableObject} objects + */ + IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix); + + /** @return - An array representing the hash codes of the corresponding instances. */ + long[] getInstanceHashCodes(); + + /** @param instance - The instance that should be added to the list of instances. */ + void addObject(IdentifiableObject instance); + + /** + * @param object - The object that should be inspected for its compatibility with {@link IObject}. + * @return - An array representing the hash codes of the corresponding instances. + */ + static long[] instanceHashCodes(Object object) { + return cast(object).map(IObject::getInstanceHashCodes).orElse(new long[] {}); + } + + /** + * @param object - The object that should be inspected for its compatibility with {@link IObject}. + * @param create - true if objects should be created before returning. + * @return - An array (can be empty is instance compatibility fails) of {@link IdentifiableObject} + * objects. + */ + static IdentifiableObject[] objects(Object object, boolean create) { + return objects(object, create, ""); + } + + /** + * @param object - The object that should be inspected for its compatibility with {@link IObject}. + * @param create - true if objects should be created before returning. + * @param errorMsgPrefix - Text that should be prefixed to the error message when there are + * issues. Can be empty. + * @return - An array (can be empty is instance compatibility fails) of {@link IdentifiableObject} + * objects. + */ + static IdentifiableObject[] objects(Object object, boolean create, String errorMsgPrefix) { + return cast(object) + .map(it -> it.getObjects(create, errorMsgPrefix)) + .orElse(new IdentifiableObject[] {}); + } + + /** + * @param object - The object that should be inspected for its compatibility with {@link IObject}. + * @return - If the incoming object is an instance of {@link IObject} then the cast instance is + * wrapped within {@link Optional} else it would be an {@link Optional#empty()} + */ + static Optional cast(Object object) { + if (object instanceof IObject) { + return Optional.of((IObject) object); + } + return Optional.empty(); + } + + /** 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(instanceId, that.instanceId); + } + + @Override + public int hashCode() { + return Objects.hash(instanceId); + } + } +} diff --git a/testng-core/src/main/java/org/testng/internal/NoOpTestClass.java b/testng-core/src/main/java/org/testng/internal/NoOpTestClass.java index 43dae05b6..02b899394 100644 --- a/testng-core/src/main/java/org/testng/internal/NoOpTestClass.java +++ b/testng-core/src/main/java/org/testng/internal/NoOpTestClass.java @@ -5,7 +5,7 @@ import org.testng.xml.XmlClass; import org.testng.xml.XmlTest; -public class NoOpTestClass implements ITestClass { +public class NoOpTestClass implements ITestClass, IObject { protected Class m_testClass = null; @@ -22,7 +22,7 @@ public class NoOpTestClass implements ITestClass { protected ITestNGMethod[] m_beforeGroupsMethods = new ITestNGMethod[0]; protected ITestNGMethod[] m_afterGroupsMethods = new ITestNGMethod[0]; - private final Object[] m_instances; + private final IdentifiableObject[] m_instances; private final long[] m_instanceHashes; private final XmlTest m_xmlTest; @@ -47,8 +47,8 @@ public NoOpTestClass(ITestClass testClass) { m_afterGroupsMethods = testClass.getAfterGroupsMethods(); m_afterClassMethods = testClass.getAfterClassMethods(); m_afterTestMethods = testClass.getAfterTestMethods(); - m_instances = testClass.getInstances(true); - m_instanceHashes = testClass.getInstanceHashCodes(); + m_instances = IObject.objects(testClass, true); + m_instanceHashes = IObject.instanceHashCodes(testClass); m_xmlTest = testClass.getXmlTest(); m_xmlClass = testClass.getXmlClass(); } @@ -123,13 +123,12 @@ public ITestNGMethod[] getAfterGroupsMethods() { return m_afterGroupsMethods; } - /** @see org.testng.ITestClass#getInstanceHashCodes() */ + /** @see org.testng.internal.IObject#getInstanceHashCodes() */ @Override public long[] getInstanceHashCodes() { return m_instanceHashes; } - /** @see org.testng.ITestClass#getInstances(boolean) */ @Override public Object[] getInstances(boolean reuse) { return m_instances; @@ -145,10 +144,17 @@ public Class getRealClass() { return m_testClass; } - /** @see org.testng.IClass#addInstance(java.lang.Object) */ @Override public void addInstance(Object instance) {} + @Override + public void addObject(IdentifiableObject instance) {} + + @Override + public IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) { + return m_instances; + } + public void setTestClass(Class declaringClass) { m_testClass = declaringClass; } diff --git a/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java b/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java index 80145ac32..d372924d9 100644 --- a/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java +++ b/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java @@ -177,8 +177,9 @@ public static ITestNGListenerFactory createListenerFactory( try { if (finder != null) { IClass ic = finder.getIClass(factoryClass); - if (ic != null) { - listenerFactory = (ITestNGListenerFactory) ic.getInstances(false)[0]; + IObject.IdentifiableObject[] created = IObject.objects(ic, false); + if (created.length != 0) { + listenerFactory = (ITestNGListenerFactory) created[0].getInstance(); } } if (listenerFactory == null) { diff --git a/testng-core/src/main/java/org/testng/internal/TestNGClassFinder.java b/testng-core/src/main/java/org/testng/internal/TestNGClassFinder.java index bf461c5d6..b8d00d888 100644 --- a/testng-core/src/main/java/org/testng/internal/TestNGClassFinder.java +++ b/testng-core/src/main/java/org/testng/internal/TestNGClassFinder.java @@ -34,7 +34,7 @@ public class TestNGClassFinder extends BaseClassFinder { private static final String PREFIX = "[TestNGClassFinder]"; private final ITestContext m_testContext; - private final Map, List> m_instanceMap = Maps.newHashMap(); + private final Map, List> m_instanceMap = Maps.newHashMap(); private final DataProviderHolder holder; private final ITestObjectFactory objectFactory; private final IAnnotationFinder annotationFinder; @@ -47,7 +47,7 @@ public String getFactoryCreationFailedMessage() { public TestNGClassFinder( ClassInfoMap cim, - Map, List> instanceMap, + Map, List> instanceMap, IConfiguration configuration, ITestContext testContext, DataProviderHolder holder) { @@ -71,20 +71,18 @@ public TestNGClassFinder( // // Add all the instances we found to their respective IClasses // - for (Map.Entry, List> entry : m_instanceMap.entrySet()) { + for (Map.Entry, List> entry : m_instanceMap.entrySet()) { Class clazz = entry.getKey(); - for (Object instance : entry.getValue()) { + for (IObject.IdentifiableObject instance : entry.getValue()) { IClass ic = getIClass(clazz); - if (null != ic) { - ic.addInstance(instance); - } + IObject.cast(ic).ifPresent(it -> it.addObject(instance)); } } } private void processClass( ClassInfoMap cim, - Map, List> instanceMap, + Map, List> instanceMap, IConfiguration configuration, Class cls) { if (null == cls) { @@ -96,8 +94,8 @@ private void processClass( Utils.log(PREFIX, 3, "SKIPPING CLASS " + cls + " no TestNG annotations found"); return; } - List allInstances = instanceMap.get(cls); - Object thisInstance = + List allInstances = instanceMap.get(cls); + IObject.IdentifiableObject thisInstance = (allInstances != null && !allInstances.isEmpty()) ? allInstances.get(0) : null; // If annotation class and instances are abstract, skip them @@ -165,9 +163,9 @@ private static boolean excludeFactory(FactoryMethod fm, ITestContext ctx) { } private ClassInfoMap processFactory(IClass ic, ConstructorOrMethod factoryMethod) { - Object[] theseInstances = ic.getInstances(false); + IObject.IdentifiableObject[] theseInstances = IObject.objects(ic, false); - Object instance = theseInstances.length != 0 ? theseInstances[0] : null; + IObject.IdentifiableObject instance = theseInstances.length != 0 ? theseInstances[0] : null; FactoryMethod fm = new FactoryMethod( factoryMethod, instance, annotationFinder, m_testContext, objectFactory, holder); @@ -180,19 +178,19 @@ private ClassInfoMap processFactory(IClass ic, ConstructorOrMethod factoryMethod // If the factory returned IInstanceInfo, get the class from it, // otherwise, just call getClass() on the returned instances int i = 0; - for (Object o : fm.invoke()) { + for (IParameterInfo o : fm.invoke()) { if (o == null) { throw new TestNGException( "The factory " + fm + " returned a null instance" + "at index " + i); } Class oneMoreClass; - Object objToInspect = IParameterInfo.embeddedInstance(o); + Object objToInspect = o.getInstance(); if (IInstanceInfo.class.isAssignableFrom(objToInspect.getClass())) { - IInstanceInfo ii = (IInstanceInfo) objToInspect; + IInstanceInfo ii = (IInstanceInfo) objToInspect; addInstance(ii); oneMoreClass = ii.getInstanceClass(); } else { - addInstance(o); + addInstance(new IObject.IdentifiableObject(o)); oneMoreClass = objToInspect.getClass(); } if (!classExists(oneMoreClass)) { @@ -326,17 +324,22 @@ private static boolean isTestNGClass(Class c, IAnnotationFinder annotationFin // IInstanceInfo should be replaced by IInstanceInfo but eclipse complains against it. // See: https://github.com/cbeust/testng/issues/1070 private void addInstance(IInstanceInfo ii) { - addInstance(ii.getInstanceClass(), ii.getInstance()); + addInstance(ii.getInstanceClass(), new IObject.IdentifiableObject(ii.getInstance())); } - private void addInstance(Object o) { - addInstance(IParameterInfo.embeddedInstance(o).getClass(), o); + private void addInstance(IObject.IdentifiableObject o) { + Class key = o.getInstance().getClass(); + if (o.getInstance() instanceof IParameterInfo) { + key = ((IParameterInfo) o.getInstance()).getInstance().getClass(); + } + addInstance(key, o); } // Class should be replaced by Class but java doesn't fail as expected // See: https://github.com/cbeust/testng/issues/1070 - private void addInstance(Class clazz, T instance) { - List instances = m_instanceMap.computeIfAbsent(clazz, key -> Lists.newArrayList()); + private void addInstance(Class clazz, IObject.IdentifiableObject instance) { + List instances = + m_instanceMap.computeIfAbsent(clazz, key -> Lists.newArrayList()); instances.add(instance); } } diff --git a/testng-core/src/main/java/org/testng/internal/TestNGMethod.java b/testng-core/src/main/java/org/testng/internal/TestNGMethod.java index f68564611..eef4e16a6 100644 --- a/testng-core/src/main/java/org/testng/internal/TestNGMethod.java +++ b/testng-core/src/main/java/org/testng/internal/TestNGMethod.java @@ -33,7 +33,7 @@ public TestNGMethod( Method method, IAnnotationFinder finder, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { this(objectFactory, method, finder, true, xmlTest, instance); } @@ -43,7 +43,7 @@ private TestNGMethod( IAnnotationFinder finder, boolean initialize, XmlTest xmlTest, - Object instance) { + IObject.IdentifiableObject instance) { super(objectFactory, method.getName(), new ConstructorOrMethod(method), finder, instance); setXmlTest(xmlTest); @@ -171,7 +171,7 @@ public BaseTestMethod clone() { getAnnotationFinder(), false, getXmlTest(), - getInstance()); + new IObject.IdentifiableObject(getInstance(), getInstanceId())); ITestClass tc = getTestClass(); NoOpTestClass testClass = new NoOpTestClass(tc); testClass.setBeforeTestMethods(clone(tc.getBeforeTestMethods())); diff --git a/testng-core/src/main/java/org/testng/internal/WrappedTestNGMethod.java b/testng-core/src/main/java/org/testng/internal/WrappedTestNGMethod.java index 688c96dc3..7700d33fb 100644 --- a/testng-core/src/main/java/org/testng/internal/WrappedTestNGMethod.java +++ b/testng-core/src/main/java/org/testng/internal/WrappedTestNGMethod.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.concurrent.Callable; import org.testng.IClass; import org.testng.IRetryAnalyzer; @@ -16,12 +17,18 @@ * generates a unique hashcode that is different from the original {@link ITestNGMethod} instance * that it wraps. */ -public class WrappedTestNGMethod implements ITestNGMethod { +public class WrappedTestNGMethod implements ITestNGMethod, IInstanceIdentity { private final ITestNGMethod testNGMethod; private final int multiplicationFactor = new Random().nextInt(); + private final UUID uuid; + public WrappedTestNGMethod(ITestNGMethod testNGMethod) { this.testNGMethod = testNGMethod; + uuid = + (testNGMethod instanceof BaseTestMethod) + ? ((BaseTestMethod) testNGMethod).getInstanceId() + : UUID.randomUUID(); } @Override @@ -364,6 +371,11 @@ public String getQualifiedName() { return testNGMethod.getQualifiedName(); } + @Override + public UUID getInstanceId() { + return uuid; + } + @Override public boolean equals(Object o) { return o == this || (o instanceof ITestNGMethod && testNGMethod.equals(o)); diff --git a/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java b/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java index 308005e7f..24fe11d16 100644 --- a/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java +++ b/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java @@ -47,6 +47,7 @@ import org.testng.collections.Maps; import org.testng.collections.Sets; import org.testng.internal.*; +import org.testng.internal.IObject; import org.testng.internal.invokers.GroupConfigMethodArguments.Builder; import org.testng.internal.invokers.InvokeMethodRunnable.TestNGRuntimeException; import org.testng.internal.thread.ThreadExecutionException; @@ -381,14 +382,14 @@ private List runWorkers( // Invoke @BeforeGroups on the original method (reduce thread contention, // and also solve thread confinement) ITestClass testClass = testMethod.getTestClass(); - Object[] instances = testClass.getInstances(true); - for (Object instance : instances) { + IObject.IdentifiableObject[] instances = IObject.objects(testClass, true); + for (IObject.IdentifiableObject instance : instances) { GroupConfigMethodArguments arguments = new GroupConfigMethodArguments.Builder() .forTestMethod(testMethod) .withGroupConfigMethods(groupMethods) .withParameters(parameters) - .forInstance(instance) + .forInstance(instance.getInstance()) .build(); invoker.invokeBeforeGroupsConfigurations(arguments); } diff --git a/testng-core/src/main/java/org/testng/internal/invokers/TestMethodWorker.java b/testng-core/src/main/java/org/testng/internal/invokers/TestMethodWorker.java index 248022fd3..758791b3f 100644 --- a/testng-core/src/main/java/org/testng/internal/invokers/TestMethodWorker.java +++ b/testng-core/src/main/java/org/testng/internal/invokers/TestMethodWorker.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.annotation.Nonnull; import org.testng.ClassMethodMap; @@ -47,6 +48,8 @@ public class TestMethodWorker implements IWorker { private final ITestInvoker m_testInvoker; private final IConfigInvoker m_configInvoker; + private static final KeyAwareAutoCloseableLock lock = new KeyAwareAutoCloseableLock(); + public TestMethodWorker( ITestInvoker testInvoker, IConfigInvoker configInvoker, @@ -119,8 +122,9 @@ && doesTaskHavePreRequisites() for (IMethodInstance testMethodInstance : m_methodInstances) { ITestNGMethod testMethod = testMethodInstance.getMethod(); + Object key = Objects.requireNonNull(IInstanceIdentity.getInstanceId(testMethod)); if (canInvokeBeforeClassMethods()) { - synchronized (testMethod.getInstance()) { + try (KeyAwareAutoCloseableLock.AutoReleasable ignored = lock.lockForObject(key)) { invokeBeforeClassMethods(testMethod.getTestClass(), testMethodInstance); } } @@ -129,7 +133,7 @@ && doesTaskHavePreRequisites() try { invokeTestMethods(testMethod, testMethodInstance.getInstance()); } finally { - synchronized (testMethod.getInstance()) { + try (KeyAwareAutoCloseableLock.AutoReleasable ignored = lock.lockForObject(key)) { invokeAfterClassMethods(testMethod.getTestClass(), testMethodInstance); } } diff --git a/testng-core/src/main/java/org/testng/internal/objects/SimpleObjectDispenser.java b/testng-core/src/main/java/org/testng/internal/objects/SimpleObjectDispenser.java index 7be8886bf..19faf6dc2 100644 --- a/testng-core/src/main/java/org/testng/internal/objects/SimpleObjectDispenser.java +++ b/testng-core/src/main/java/org/testng/internal/objects/SimpleObjectDispenser.java @@ -8,6 +8,7 @@ import org.testng.TestNGException; import org.testng.annotations.IFactoryAnnotation; import org.testng.annotations.IParametersAnnotation; +import org.testng.internal.IObject; import org.testng.internal.Parameters; import org.testng.internal.annotations.IAnnotationFinder; import org.testng.internal.objects.pojo.BasicAttributes; @@ -187,11 +188,11 @@ private static Object computeParameters( if (enclosingIClass == null) { return factory.newInstance(ec); } - Object[] enclosingInstances = enclosingIClass.getInstances(false); - if (enclosingInstances == null || enclosingInstances.length == 0) { + IObject.IdentifiableObject[] enclosingInstances = IObject.objects(enclosingIClass, false); + if (enclosingInstances.length == 0) { return factory.newInstance(ec.getConstructor(ec)); } - return enclosingInstances[0]; + return enclosingInstances[0].getInstance(); } /** Find the best constructor given the parameters found on the annotation */ diff --git a/testng-core/src/test/java/org/testng/internal/DynamicGraphHelperTest.java b/testng-core/src/test/java/org/testng/internal/DynamicGraphHelperTest.java index 6923ec210..5b94ea28a 100644 --- a/testng-core/src/test/java/org/testng/internal/DynamicGraphHelperTest.java +++ b/testng-core/src/test/java/org/testng/internal/DynamicGraphHelperTest.java @@ -233,7 +233,7 @@ private static List associateInstanceToMethods( each.getConstructorOrMethod().getMethod(), finder, xmlTest, - object); + new IObject.IdentifiableObject(object)); fixedMethods.add(m); } } diff --git a/testng-core/src/test/java/org/testng/internal/MethodHelperTest.java b/testng-core/src/test/java/org/testng/internal/MethodHelperTest.java index 49ef7f0f9..87f46d093 100644 --- a/testng-core/src/test/java/org/testng/internal/MethodHelperTest.java +++ b/testng-core/src/test/java/org/testng/internal/MethodHelperTest.java @@ -36,7 +36,7 @@ public void findDependedUponMethods() throws NoSuchMethodException { new String[0], new String[0], Reporter.getCurrentTestResult().getTestContext().getCurrentXmlTest(), - testClass); + new IObject.IdentifiableObject(testClass)); method.addMethodDependedUpon("dummyDependsOnMethod"); ITestNGMethod[] methods = new ITestNGMethod[0]; diff --git a/testng-core/src/test/java/org/testng/internal/MethodInstanceTest.java b/testng-core/src/test/java/org/testng/internal/MethodInstanceTest.java index 24a069d07..bcd0840e2 100644 --- a/testng-core/src/test/java/org/testng/internal/MethodInstanceTest.java +++ b/testng-core/src/test/java/org/testng/internal/MethodInstanceTest.java @@ -126,7 +126,7 @@ public String getName() { } } - public static class TestClassStub implements ITestClass { + public static class TestClassStub implements ITestClass, IObject { private XmlTest xmlTest; private XmlClass xmlClass; @@ -164,11 +164,21 @@ public Class getRealClass() { @Override public void addInstance(Object instance) {} + @Override + public void addObject(IObject.IdentifiableObject instance) { + // Intentionally left blank + } + @Override public Object[] getInstances(boolean reuse) { return null; } + @Override + public IObject.IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) { + return new IObject.IdentifiableObject[0]; + } + @Override public long[] getInstanceHashCodes() { return null; diff --git a/testng-core/src/test/java/org/testng/internal/dynamicgraph/FakeTestClass.java b/testng-core/src/test/java/org/testng/internal/dynamicgraph/FakeTestClass.java index 6f657fc12..6ceef4e23 100644 --- a/testng-core/src/test/java/org/testng/internal/dynamicgraph/FakeTestClass.java +++ b/testng-core/src/test/java/org/testng/internal/dynamicgraph/FakeTestClass.java @@ -2,10 +2,11 @@ import org.testng.ITestClass; import org.testng.ITestNGMethod; +import org.testng.internal.IObject; import org.testng.xml.XmlClass; import org.testng.xml.XmlTest; -public class FakeTestClass implements ITestClass { +public class FakeTestClass implements ITestClass, IObject { private final Class clazz; public FakeTestClass(Class clazz) { @@ -97,6 +98,11 @@ public Object[] getInstances(boolean create) { return new Object[0]; } + @Override + public IObject.IdentifiableObject[] getObjects(boolean create, String errorMsgPrefix) { + return new IObject.IdentifiableObject[0]; + } + @Override public long[] getInstanceHashCodes() { return new long[0]; @@ -104,4 +110,7 @@ public long[] getInstanceHashCodes() { @Override public void addInstance(Object instance) {} + + @Override + public void addObject(IObject.IdentifiableObject instance) {} } diff --git a/testng-core/src/test/java/org/testng/internal/objects/GuiceHelperTest.java b/testng-core/src/test/java/org/testng/internal/objects/GuiceHelperTest.java index 52823f4c5..eaf22649d 100644 --- a/testng-core/src/test/java/org/testng/internal/objects/GuiceHelperTest.java +++ b/testng-core/src/test/java/org/testng/internal/objects/GuiceHelperTest.java @@ -61,7 +61,7 @@ public MockClass() { new FakeTestContext(), GuiceHelperTest.class, null, - (ITest) () -> "GITHUB-2273", + new IdentifiableObject((ITest) () -> "GITHUB-2273"), null, null, new ITestObjectFactory() {}); diff --git a/testng-core/src/test/java/test/factory/ObjectIdTest.java b/testng-core/src/test/java/test/factory/ObjectIdTest.java new file mode 100644 index 000000000..e530e292e --- /dev/null +++ b/testng-core/src/test/java/test/factory/ObjectIdTest.java @@ -0,0 +1,41 @@ +package test.factory; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.assertj.core.api.SoftAssertions; +import org.testng.TestNG; +import org.testng.annotations.Test; +import org.testng.xml.XmlSuite; +import test.SimpleBaseTest; +import test.factory.issue3079.FactoryTestCase; +import test.factory.issue3079.SampleTestCase; + +public class ObjectIdTest extends SimpleBaseTest { + + @Test(description = "GITHUB-3079") + public void ensureOnlyOneObjectIdExistsForNormalTestClass() { + TestNG testng = create(SampleTestCase.class); + testng.setParallel(XmlSuite.ParallelMode.METHODS); + testng.run(); + Map> objectMap = SampleTestCase.objectMap; + assertThat(objectMap.keySet()).hasSize(1); + SoftAssertions assertions = new SoftAssertions(); + objectMap.forEach((key, value) -> assertions.assertThat(value).hasSize(1)); + assertions.assertAll(); + } + + @Test(description = "GITHUB-3079") + public void ensureOnlyOneObjectIdExistsForFactoryPoweredTestClass() { + TestNG testng = create(FactoryTestCase.class); + testng.setParallel(XmlSuite.ParallelMode.INSTANCES); + testng.run(); + Map> objectMap = FactoryTestCase.objectMap; + assertThat(objectMap.keySet()).hasSize(100); + SoftAssertions assertions = new SoftAssertions(); + objectMap.forEach((key, value) -> assertions.assertThat(value).hasSize(1)); + assertions.assertAll(); + } +} diff --git a/testng-core/src/test/java/test/factory/issue3079/FactoryTestCase.java b/testng-core/src/test/java/test/factory/issue3079/FactoryTestCase.java new file mode 100644 index 000000000..7996b9627 --- /dev/null +++ b/testng-core/src/test/java/test/factory/issue3079/FactoryTestCase.java @@ -0,0 +1,68 @@ +package test.factory.issue3079; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.IntStream; +import org.testng.ITestNGMethod; +import org.testng.ITestResult; +import org.testng.Reporter; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Factory; +import org.testng.annotations.Test; +import org.testng.internal.IInstanceIdentity; + +public class FactoryTestCase { + + public static Map> objectMap = new ConcurrentHashMap<>(); + + @Factory(dataProvider = "dp") + public FactoryTestCase(int ignored) {} + + @DataProvider(parallel = true) + public static Object[][] dp() { + return IntStream.rangeClosed(1, 100) + .boxed() + .map(it -> new Object[] {it}) + .toArray(size -> new Object[size][1]); + } + + @BeforeClass + public void beforeClass() { + record(); + } + + @BeforeMethod + public void beforeMethod() { + record(); + } + + @Test(dataProvider = "dp") + public void t1(int ignored) { + record(); + } + + @AfterMethod + public void afterMethod() { + record(); + } + + @AfterClass + public void afterClass() { + record(); + } + + private static void record() { + ITestResult itr = Reporter.getCurrentTestResult(); + ITestNGMethod itm = itr.getMethod(); + objectMap + .computeIfAbsent( + (UUID) IInstanceIdentity.getInstanceId(itm), k -> ConcurrentHashMap.newKeySet()) + .add(itm.getInstance()); + } +} diff --git a/testng-core/src/test/java/test/factory/issue3079/SampleTestCase.java b/testng-core/src/test/java/test/factory/issue3079/SampleTestCase.java new file mode 100644 index 000000000..05ab5a6ad --- /dev/null +++ b/testng-core/src/test/java/test/factory/issue3079/SampleTestCase.java @@ -0,0 +1,69 @@ +package test.factory.issue3079; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.testng.ITestNGMethod; +import org.testng.ITestResult; +import org.testng.Reporter; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.testng.internal.IInstanceIdentity; + +public class SampleTestCase { + public static Map> objectMap = new ConcurrentHashMap<>(); + + @BeforeClass + public void beforeClass() { + record(); + } + + @BeforeMethod + public void beforeMethod() { + record(); + } + + @Test + public void test1() { + record(); + } + + @Test + public void test2() { + record(); + } + + @Test(dataProvider = "dp") + public void test3(int i) { + record(); + } + + @DataProvider(name = "dp", parallel = true) + public Object[][] getData() { + return new Object[][] {{1}, {2}}; + } + + @AfterMethod + public void afterMethod() { + record(); + } + + @AfterClass + public void afterClass() { + record(); + } + + private static void record() { + ITestResult itr = Reporter.getCurrentTestResult(); + ITestNGMethod itm = itr.getMethod(); + objectMap + .computeIfAbsent( + (UUID) IInstanceIdentity.getInstanceId(itm), k -> ConcurrentHashMap.newKeySet()) + .add(itm.getInstance()); + } +} diff --git a/testng-core/src/test/resources/testng.xml b/testng-core/src/test/resources/testng.xml index 0aba6f1bf..24ae270c9 100644 --- a/testng-core/src/test/resources/testng.xml +++ b/testng-core/src/test/resources/testng.xml @@ -412,6 +412,7 @@ +