From b64403e479253fd35e8d99423353a77880e00f10 Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Mon, 22 Apr 2024 18:34:50 +0530 Subject: [PATCH] Allow users to access factory method params info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #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. --- CHANGES.txt | 1 + .../main/java/org/testng/IParameterInfo.java | 36 +++++++++++++++ .../org/testng/internal/IParameterInfo.java | 26 +++-------- .../org/testng/internal/BaseTestMethod.java | 2 +- .../java/org/testng/internal/ClassImpl.java | 2 +- .../org/testng/internal/FactoryMethod.java | 18 +++++--- .../org/testng/internal/ParameterInfo.java | 13 ++++-- .../testng/internal/TestNGClassFinder.java | 6 +-- .../test/factory/FactoryIntegrationTest.java | 45 ++++++++++++++++++- .../SimpleFactoryPoweredTestSample.java | 26 +++++++++++ ...leFactoryPoweredTestWithIndicesSample.java | 28 ++++++++++++ ...yPoweredTestWithoutDataProviderSample.java | 28 ++++++++++++ ...tWithoutDataProviderWithIndicesSample.java | 28 ++++++++++++ .../java/org/testng/internal/TestResult.java | 2 +- 14 files changed, 226 insertions(+), 35 deletions(-) create mode 100644 testng-core-api/src/main/java/org/testng/IParameterInfo.java create mode 100644 testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestSample.java create mode 100644 testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithIndicesSample.java create mode 100644 testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderSample.java create mode 100644 testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample.java diff --git a/CHANGES.txt b/CHANGES.txt index fa0add3e75..0a2108680a 100644 --- a/CHANGES.txt +++ b/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) diff --git a/testng-core-api/src/main/java/org/testng/IParameterInfo.java b/testng-core-api/src/main/java/org/testng/IParameterInfo.java new file mode 100644 index 0000000000..7a29d240ae --- /dev/null +++ b/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 indices attribute of the + * @Factory 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 @Factory annotated constructor or static + * method. For e.g., lets say your @Factory method returned 4 instances, then + * each of the invocations to this method would return a value from 0 to 3 + * + */ + 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; + } +} diff --git a/testng-core-api/src/main/java/org/testng/internal/IParameterInfo.java b/testng-core-api/src/main/java/org/testng/internal/IParameterInfo.java index 6ccf1b041c..3446078ec8 100644 --- a/testng-core-api/src/main/java/org/testng/internal/IParameterInfo.java +++ b/testng-core-api/src/main/java/org/testng/internal/IParameterInfo.java @@ -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 7.11.0. + */ +@Deprecated +public interface IParameterInfo extends org.testng.IParameterInfo {} 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 c0ad3379f9..fbee5f4c6c 100644 --- a/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java +++ b/testng-core/src/main/java/org/testng/internal/BaseTestMethod.java @@ -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); } 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 f893b60086..098e7f191d 100644 --- a/testng-core/src/main/java/org/testng/internal/ClassImpl.java +++ b/testng-core/src/main/java/org/testng/internal/ClassImpl.java @@ -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) { 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 c5bf4de1fe..901ad7cb7e 100644 --- a/testng-core/src/main/java/org/testng/internal/FactoryMethod.java +++ b/testng-core/src/main/java/org/testng/internal/FactoryMethod.java @@ -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; @@ -144,8 +145,8 @@ private static String[] getAllGroups( return groups.toArray(new String[0]); } - public IParameterInfo[] invoke() { - List result = Lists.newArrayList(); + public org.testng.IParameterInfo[] invoke() { + List result = Lists.newArrayList(); Map allParameterNames = Maps.newHashMap(); Parameters.MethodParameters methodParameters = @@ -174,6 +175,7 @@ public IParameterInfo[] invoke() { try { List indices = factoryAnnotation.getIndices(); int position = 0; + AtomicInteger counter = new AtomicInteger(0); while (parameterIterator.hasNext()) { Object[] parameters = parameterIterator.next(); if (parameters == null) { @@ -196,13 +198,18 @@ 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())); } } } @@ -210,7 +217,8 @@ public IParameterInfo[] invoke() { } 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++; } diff --git a/testng-core/src/main/java/org/testng/internal/ParameterInfo.java b/testng-core/src/main/java/org/testng/internal/ParameterInfo.java index 7b514d1da4..82e427caa4 100644 --- a/testng-core/src/main/java/org/testng/internal/ParameterInfo.java +++ b/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 @@ -21,6 +23,11 @@ public int getIndex() { return index; } + @Override + public int currentIndex() { + return currentIndex; + } + @Override public Object[] getParameters() { return parameters; 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 b8d00d8889..aa2d39efea 100644 --- a/testng-core/src/main/java/org/testng/internal/TestNGClassFinder.java +++ b/testng-core/src/main/java/org/testng/internal/TestNGClassFinder.java @@ -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); @@ -329,8 +329,8 @@ private void addInstance(IInstanceInfo 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); } diff --git a/testng-core/src/test/java/test/factory/FactoryIntegrationTest.java b/testng-core/src/test/java/test/factory/FactoryIntegrationTest.java index f18c8289d0..21476eb896 100644 --- a/testng-core/src/test/java/test/factory/FactoryIntegrationTest.java +++ b/testng-core/src/test/java/test/factory/FactoryIntegrationTest.java @@ -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 { @@ -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"); } } @@ -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"); } } @@ -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 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 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}}, + }; + } } diff --git a/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestSample.java b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestSample.java new file mode 100644 index 0000000000..a85ba3e2b0 --- /dev/null +++ b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestSample.java @@ -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}}; + } +} diff --git a/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithIndicesSample.java b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithIndicesSample.java new file mode 100644 index 0000000000..3a745ac1bc --- /dev/null +++ b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithIndicesSample.java @@ -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}}; + } +} diff --git a/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderSample.java b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderSample.java new file mode 100644 index 0000000000..fb8bb9dac3 --- /dev/null +++ b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderSample.java @@ -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), + }; + } +} diff --git a/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample.java b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample.java new file mode 100644 index 0000000000..be564b3022 --- /dev/null +++ b/testng-core/src/test/java/test/factory/issue3111/SimpleFactoryPoweredTestWithoutDataProviderWithIndicesSample.java @@ -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), + }; + } +} diff --git a/testng-runner-api/src/main/java/org/testng/internal/TestResult.java b/testng-runner-api/src/main/java/org/testng/internal/TestResult.java index e48352d8c0..2e730047ce 100644 --- a/testng-runner-api/src/main/java/org/testng/internal/TestResult.java +++ b/testng-runner-api/src/main/java/org/testng/internal/TestResult.java @@ -298,7 +298,7 @@ public void setParameters(Object[] parameters) { @Override public Object getInstance() { - return IParameterInfo.embeddedInstance(this.m_method.getInstance()); + return org.testng.IParameterInfo.embeddedInstance(this.m_method.getInstance()); } @Override