Skip to content

Commit

Permalink
Allow users to access factory method params info
Browse files Browse the repository at this point in the history
Closes testng-team#3111

TestNG now exposes the IParameterInfo interface 
Via which you can extract the following details
Pertaining to a factory powered test.

* the index - which would match with what was 
Specified in the “indices” attribute of the “Factory” 
Annotation. If nothing was specified, then this
Would be equal to a running count on the total instances.

* current index - which represents a running count
On the total instances.

* The parameters of the factory method

* The instance that was produced.
  • Loading branch information
krmahadevan committed Apr 23, 2024
1 parent 249ea08 commit b64403e
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
@@ -1,4 +1,5 @@
Current (7.11.0)
Fixed: GITHUB-3111: Replacement API for IClass.getInstanceHashCodes() and IClass.getInstances(boolean) (Krishnan Mahadevan)

7.10.1
Fixed: GITHUB-3110: Update from testng 7.9.0 to 7.10.0 break maven build with junit5 (Krishnan Mahadevan)
Expand Down
36 changes: 36 additions & 0 deletions testng-core-api/src/main/java/org/testng/IParameterInfo.java
@@ -0,0 +1,36 @@
package org.testng;

/** Represents the ability to retrieve the parameters associated with a factory method. */
public interface IParameterInfo {

/** @return - The actual instance associated with a factory method */
Object getInstance();

/**
* @return - The actual index of instance associated with a factory method. This index has a 1:1
* correspondence with what were specified via the <code>indices</code> attribute of the
* <code>@Factory</code> annotation. For e.g., lets say you specified the indices to the "1"
* and your factory returned 4 instances, then the instance on which this method is invoked
* would have the value as "1".
*/
int getIndex();

/**
* @return - returns an index which indicates the running position in the array of test class
* instances that were produced by a <code>@Factory</code> annotated constructor or static
* method. For e.g., lets say your <code>@Factory</code> method returned 4 instances, then
* each of the invocations to this method would return a value from <code>0</code> to <code>3
* </code>
*/
int currentIndex();

/** @return - The parameters associated with the factory method as an array. */
Object[] getParameters();

static Object embeddedInstance(Object original) {
if (original instanceof IParameterInfo) {
return ((IParameterInfo) original).getInstance();
}
return original;
}
}
@@ -1,21 +1,9 @@
package org.testng.internal;

/** Represents the ability to retrieve the parameters associated with a factory method. */
public interface IParameterInfo {

/** @return - The actual instance associated with a factory method */
Object getInstance();

/** @return - The actual index of instance associated with a factory method */
int getIndex();

/** @return - The parameters associated with the factory method as an array. */
Object[] getParameters();

static Object embeddedInstance(Object original) {
if (original instanceof IParameterInfo) {
return ((IParameterInfo) original).getInstance();
}
return original;
}
}
/**
* Represents the ability to retrieve the parameters associated with a factory method.
*
* @deprecated - This interface stands deprecated as of TestNG <code>7.11.0</code>.
*/
@Deprecated
public interface IParameterInfo extends org.testng.IParameterInfo {}
Expand Up @@ -153,7 +153,7 @@ public String getMethodName() {
public Object getInstance() {
return Optional.ofNullable(m_instance)
.map(IObject.IdentifiableObject::getInstance)
.map(IParameterInfo::embeddedInstance)
.map(org.testng.IParameterInfo::embeddedInstance)
.orElse(null);
}

Expand Down
Expand Up @@ -162,7 +162,7 @@ public void addInstance(Object instance) {
}

private static int computeHashCode(Object instance) {
return IParameterInfo.embeddedInstance(instance).hashCode();
return org.testng.IParameterInfo.embeddedInstance(instance).hashCode();
}

private DetailedAttributes newDetailedAttributes(boolean create, String errMsgPrefix) {
Expand Down
18 changes: 13 additions & 5 deletions testng-core/src/main/java/org/testng/internal/FactoryMethod.java
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.testng.DataProviderHolder;
import org.testng.IDataProviderInterceptor;
Expand Down Expand Up @@ -144,8 +145,8 @@ private static String[] getAllGroups(
return groups.toArray(new String[0]);
}

public IParameterInfo[] invoke() {
List<IParameterInfo> result = Lists.newArrayList();
public org.testng.IParameterInfo[] invoke() {
List<org.testng.IParameterInfo> result = Lists.newArrayList();

Map<String, String> allParameterNames = Maps.newHashMap();
Parameters.MethodParameters methodParameters =
Expand Down Expand Up @@ -174,6 +175,7 @@ public IParameterInfo[] invoke() {
try {
List<Integer> indices = factoryAnnotation.getIndices();
int position = 0;
AtomicInteger counter = new AtomicInteger(0);
while (parameterIterator.hasNext()) {
Object[] parameters = parameterIterator.next();
if (parameters == null) {
Expand All @@ -196,21 +198,27 @@ public IParameterInfo[] invoke() {
final int instancePosition = position;
result.addAll(
Arrays.stream(testInstances)
.map(instance -> new ParameterInfo(instance, instancePosition, parameters))
.map(
instance ->
new ParameterInfo(
instance, instancePosition, parameters, counter.getAndIncrement()))
.collect(Collectors.toList()));
} else {
for (Integer index : indices) {
int i = index - position;
if (i >= 0 && i < testInstances.length) {
result.add(new ParameterInfo(testInstances[i], position, parameters));
result.add(
new ParameterInfo(
testInstances[i], position, parameters, counter.getAndIncrement()));
}
}
}
position += testInstances.length;
} else {
if (indices == null || indices.isEmpty() || indices.contains(position)) {
Object instance = m_objectFactory.newInstance(com.getConstructor(), parameters);
result.add(new ParameterInfo(instance, position, parameters));
result.add(
new ParameterInfo(instance, position, parameters, counter.getAndIncrement()));
}
position++;
}
Expand Down
13 changes: 10 additions & 3 deletions testng-core/src/main/java/org/testng/internal/ParameterInfo.java
@@ -1,14 +1,16 @@
package org.testng.internal;

public class ParameterInfo implements IParameterInfo {
private Object instance;
private final Object instance;
private final int index;
private Object[] parameters;
private final Object[] parameters;
private final int currentIndex;

public ParameterInfo(Object instance, int index, Object[] parameters) {
public ParameterInfo(Object instance, int index, Object[] parameters, int currentIndex) {
this.instance = instance;
this.index = index;
this.parameters = parameters;
this.currentIndex = currentIndex;
}

@Override
Expand All @@ -21,6 +23,11 @@ public int getIndex() {
return index;
}

@Override
public int currentIndex() {
return currentIndex;
}

@Override
public Object[] getParameters() {
return parameters;
Expand Down
Expand Up @@ -178,7 +178,7 @@ 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 (IParameterInfo o : fm.invoke()) {
for (org.testng.IParameterInfo o : fm.invoke()) {
if (o == null) {
throw new TestNGException(
"The factory " + fm + " returned a null instance" + "at index " + i);
Expand Down Expand Up @@ -329,8 +329,8 @@ private <T> void addInstance(IInstanceInfo<T> ii) {

private void addInstance(IObject.IdentifiableObject o) {
Class<?> key = o.getInstance().getClass();
if (o.getInstance() instanceof IParameterInfo) {
key = ((IParameterInfo) o.getInstance()).getInstance().getClass();
if (o.getInstance() instanceof org.testng.IParameterInfo) {
key = ((org.testng.IParameterInfo) o.getInstance()).getInstance().getClass();
}
addInstance(key, o);
}
Expand Down
45 changes: 43 additions & 2 deletions testng-core/src/test/java/test/factory/FactoryIntegrationTest.java
Expand Up @@ -3,13 +3,24 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.testng.Assert;
import org.testng.IParameterInfo;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import org.testng.TestNG;
import org.testng.TestNGException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import test.InvokedMethodNameListener;
import test.SimpleBaseTest;
import test.factory.issue3111.SimpleFactoryPoweredTestSample;
import test.factory.issue3111.SimpleFactoryPoweredTestWithIndicesSample;
import test.factory.issue3111.SimpleFactoryPoweredTestWithoutDataProviderSample;
import test.factory.issue3111.SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample;

public class FactoryIntegrationTest extends SimpleBaseTest {

Expand All @@ -22,7 +33,9 @@ public void testExceptionWithNonStaticFactoryMethod() {
} catch (TestNGException e) {
assertThat(e)
.hasMessage(
"\nCan't invoke public java.lang.Object[] test.factory.GitHub876Sample.createInstances(): either make it static or add a no-args constructor to your class");
"\nCan't invoke public java.lang.Object[] test.factory.GitHub876Sample"
+ ".createInstances(): either make it static or add a no-args constructor to "
+ "your class");
}
}

Expand All @@ -46,7 +59,8 @@ public void testExceptionWithBadFactoryMethodReturnType() {
} catch (TestNGException e) {
assertThat(e)
.hasMessage(
"\ntest.factory.BadMethodReturnTypeFactory.createInstances MUST return [ java.lang.Object[] or org.testng.IInstanceInfo[] ] but returns java.lang.Object");
"\ntest.factory.BadMethodReturnTypeFactory.createInstances MUST return [ java.lang"
+ ".Object[] or org.testng.IInstanceInfo[] ] but returns java.lang.Object");
}
}

Expand All @@ -65,4 +79,31 @@ public void doubleFactoryMethodShouldWork() {
"FactoryBaseSample{1}#f",
"FactoryBaseSample{2}#f", "FactoryBaseSample{3}#f", "FactoryBaseSample{4}#f");
}

@Test(dataProvider = "testdata", description = "GITHUB-3111")
public void ensureCurrentIndexWorksForFactoryPoweredTests(Class<?> klass, Integer[] expected) {
List<IParameterInfo> params = new ArrayList<>();
TestNG testng = create(klass);
testng.addListener(
new ITestListener() {
@Override
public void onTestSuccess(ITestResult result) {
params.add(result.getMethod().getFactoryMethodParamsInfo());
}
});
testng.run();
List<Integer> actualIndices =
params.stream().map(IParameterInfo::currentIndex).sorted().collect(Collectors.toList());
assertThat(actualIndices).containsExactly(expected);
}

@DataProvider(name = "testdata")
public Object[][] testdata() {
return new Object[][] {
{SimpleFactoryPoweredTestSample.class, new Integer[] {0, 1, 2}},
{SimpleFactoryPoweredTestWithIndicesSample.class, new Integer[] {0}},
{SimpleFactoryPoweredTestWithoutDataProviderSample.class, new Integer[] {0, 1, 2}},
{SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample.class, new Integer[] {0}},
};
}
}
@@ -0,0 +1,26 @@
package test.factory.issue3111;

import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class SimpleFactoryPoweredTestSample {

private final int i;

@Factory(dataProvider = "data")
public SimpleFactoryPoweredTestSample(int i) {
this.i = i;
}

@Test
public void test() {
Reporter.log(Integer.toString(i));
}

@DataProvider
public static Object[][] data() {
return new Object[][] {{1}, {2}, {3}};
}
}
@@ -0,0 +1,28 @@
package test.factory.issue3111;

import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class SimpleFactoryPoweredTestWithIndicesSample {

private final int i;

@Factory(
dataProvider = "data",
indices = {1})
public SimpleFactoryPoweredTestWithIndicesSample(int i) {
this.i = i;
}

@Test
public void test() {
Reporter.log(Integer.toString(i));
}

@DataProvider
public static Object[][] data() {
return new Object[][] {{1}, {2}, {3}};
}
}
@@ -0,0 +1,28 @@
package test.factory.issue3111;

import org.testng.Reporter;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class SimpleFactoryPoweredTestWithoutDataProviderSample {

private final int i;

public SimpleFactoryPoweredTestWithoutDataProviderSample(int i) {
this.i = i;
}

@Test
public void test() {
Reporter.log(Integer.toString(i));
}

@Factory
public static Object[] data() {
return new Object[] {
new SimpleFactoryPoweredTestWithoutDataProviderSample(1),
new SimpleFactoryPoweredTestWithoutDataProviderSample(2),
new SimpleFactoryPoweredTestWithoutDataProviderSample(3),
};
}
}
@@ -0,0 +1,28 @@
package test.factory.issue3111;

import org.testng.Reporter;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample {

private final int i;

public SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample(int i) {
this.i = i;
}

@Test
public void test() {
Reporter.log(Integer.toString(i));
}

@Factory(indices = {1})
public static Object[] data() {
return new Object[] {
new SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample(1),
new SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample(2),
new SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample(3),
};
}
}

0 comments on commit b64403e

Please sign in to comment.