Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ITestObjectFactory injection via listeners #2677

Merged
merged 1 commit into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Current
Fixed: GITHUB-2676: NPE is triggered when working with ITestObjectFactory (Krishnan Mahadevan)
Fixed: GITHUB-2674: Run onTestSkipped for each value from data provider (Krishnan Mahadevan)
Fixed: GITHUB-2672: Log real stacktrace when test times out. (cdalexndr)
Fixed: GITHUB-2669: A failed retry with ITestContext will lose the ITestContext. (Nan Liang)
Expand Down
15 changes: 9 additions & 6 deletions testng-core/src/main/java/org/testng/SuiteRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ private void init(
!configuration.getObjectFactory().getClass().equals(suite.getObjectFactoryClass());
final ITestObjectFactory suiteObjectFactory;
if (create) {
if (objectFactory == null) {
objectFactory = configuration.getObjectFactory();
}
// Dont keep creating the object factory repeatedly since our current object factory
// Was already created based off of a suite level object factory.
suiteObjectFactory = objectFactory.newInstance(suite.getObjectFactoryClass());
Expand All @@ -166,27 +169,27 @@ private void init(
@Override
public <T> T newInstance(Class<T> cls, Object... parameters) {
try {
return configuration.getObjectFactory().newInstance(cls, parameters);
} catch (Exception e) {
return suiteObjectFactory.newInstance(cls, parameters);
} catch (Exception e) {
return configuration.getObjectFactory().newInstance(cls, parameters);
}
}

@Override
public <T> T newInstance(String clsName, Object... parameters) {
try {
return configuration.getObjectFactory().newInstance(clsName, parameters);
} catch (Exception e) {
return suiteObjectFactory.newInstance(clsName, parameters);
} catch (Exception e) {
return configuration.getObjectFactory().newInstance(clsName, parameters);
}
}

@Override
public <T> T newInstance(Constructor<T> constructor, Object... parameters) {
try {
return configuration.getObjectFactory().newInstance(constructor, parameters);
} catch (Exception e) {
return suiteObjectFactory.newInstance(constructor, parameters);
} catch (Exception e) {
return configuration.getObjectFactory().newInstance(constructor, parameters);
}
}
};
Expand Down
3 changes: 2 additions & 1 deletion testng-core/src/main/java/org/testng/TestNG.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.testng.internal.annotations.JDK15AnnotationFinder;
import org.testng.internal.invokers.SuiteRunnerMap;
import org.testng.internal.invokers.objects.GuiceContext;
import org.testng.internal.objects.DefaultTestObjectFactory;
import org.testng.internal.objects.Dispenser;
import org.testng.internal.objects.IObjectDispenser;
import org.testng.internal.objects.pojo.BasicAttributes;
Expand Down Expand Up @@ -169,7 +170,7 @@ public class TestNG {

private final Set<XmlMethodSelector> m_selectors = Sets.newLinkedHashSet();

private static final ITestObjectFactory DEFAULT_OBJECT_FACTORY = new ITestObjectFactory() {};
private static final ITestObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultTestObjectFactory();
private ITestObjectFactory m_objectFactory = DEFAULT_OBJECT_FACTORY;

private final Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener>
Expand Down
2 changes: 1 addition & 1 deletion testng-core/src/main/java/org/testng/TestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ private void init(
m_host = suite.getHost();
m_testClassesFromXml = test.getXmlClasses();
m_injectorFactory = m_configuration.getInjectorFactory();
m_objectFactory = m_configuration.getObjectFactory();
m_objectFactory = suite.getObjectFactory();
setVerbose(test.getVerbose());

boolean preserveOrder = test.getPreserveOrder();
Expand Down
13 changes: 11 additions & 2 deletions testng-core/src/main/java/org/testng/internal/ClassImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.testng.collections.Lists;
import org.testng.collections.Objects;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.objects.DefaultTestObjectFactory;
import org.testng.internal.objects.Dispenser;
import org.testng.internal.objects.IObjectDispenser;
import org.testng.internal.objects.pojo.BasicAttributes;
Expand Down Expand Up @@ -94,7 +95,11 @@ private Object getDefaultInstance(boolean create, String errMsgPrefix) {
if (m_instance != null) {
m_defaultInstance = m_instance;
} else {
IObjectDispenser dispenser = Dispenser.newInstance(m_objectFactory);
ITestObjectFactory factory = m_objectFactory;
if (factory instanceof DefaultTestObjectFactory) {
factory = m_testContext.getSuite().getObjectFactory();
}
IObjectDispenser dispenser = Dispenser.newInstance(factory);
BasicAttributes basic = new BasicAttributes(this, null);
DetailedAttributes detailed = newDetailedAttributes(create, errMsgPrefix);
CreationAttributes attributes = new CreationAttributes(m_testContext, basic, detailed);
Expand All @@ -118,7 +123,11 @@ public Object[] getInstances(boolean create, String errorMsgPrefix) {
if (create) {
DetailedAttributes ea = newDetailedAttributes(create, errorMsgPrefix);
CreationAttributes attributes = new CreationAttributes(m_testContext, null, ea);
result = new Object[] {Dispenser.newInstance(m_objectFactory).dispense(attributes)};
result =
new Object[] {
Dispenser.newInstance(m_testContext.getSuite().getObjectFactory())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juherr - I didnt understand this comment and the link you shared is taking me to the same class. Can you please help me understand ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was not explicit enough.
You have 2 different ways to create a dispenser in the same class. Is it expected? If it is expected, could you add a comment that explains why? If it is not expected, you can share a same builder method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juherr - Yes you are right. One place is for the JUnit flow but I dont even see that codepath being executed and so I dont know why it exists even. I was first thinking that, this particular code path is going to get executed when we have junit attribute in suite set to true and I am running a bunch of JUnit tests using TestNG. But I noticed that the tests don't even take that path. It may be dead code but I cannot remove it until I know for sure. I will dig into it deeper separately and not in this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From looking at the code, it looks like we perhaps never supported the notion of a test object factory for JUnit tests, because the codepath (when I execute a JUnit annotated test using TestNG) is going such that, JUnit is internally invoking the constructor of the test class and our objectfactory is not even coming into picture. I think if there are no other comments on this PR (which is to fix test objectfactory to be set for TestNG tests via listeners) we should merge this PR and deal with cleaning up the test object factory impact for JUnit tests separately (and maybe even remove the irrelevant code sections. )

Maybe I dont know enough but this is my first impression from what I see

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you ask me, I will propose to drop the junit support and advice to use https://github.com/junit-team/testng-engine when users ask for running both junit and testng.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juherr sure. So can we go ahead with merging this PR ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just add a comment that link to the current discussion ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and captured this as a defect and included all the details #2683

.dispense(attributes)
};
}
}
if (m_instances.size() > 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.testng.internal.objects;

import org.testng.ITestObjectFactory;

/**
* Intended to be the default way of instantiating objects within TestNG. Intentionally does not
* provide any specific implementation because the interface already defines the default behavior.
* This class still exists to ensure that we dont use an anonymous object instantiation so that its
* easy to find out what type of object factory is being injected into our object dispensing
* mechanisms.
*/
public class DefaultTestObjectFactory implements ITestObjectFactory {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package test.objectfactory;

import static org.assertj.core.api.Assertions.assertThat;

import org.testng.TestNG;
import org.testng.TestNGException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.xml.XmlSuite;
import test.SimpleBaseTest;
import test.objectfactory.github1131.EmptyConstructorSample;
import test.objectfactory.github1131.IntConstructorSample;
import test.objectfactory.github1131.MyObjectFactory;
import test.objectfactory.github1131.StringConstructorSample;
import test.objectfactory.github1827.GitHub1827Sample;
import test.objectfactory.issue2676.LocalSuiteAlteringListener;
import test.objectfactory.issue2676.LoggingObjectFactorySample;
import test.objectfactory.issue2676.TestClassSample;

public class ObjectFactoryTest extends SimpleBaseTest {

@Test(description = "GITHUB-2676")
public void ensureObjectFactoryIsInvokedWhenAddedViaListeners() {
TestNG testng = create(TestClassSample.class);
testng.addListener(new LocalSuiteAlteringListener());
testng.run();
assertThat(LoggingObjectFactorySample.wasInvoked).isTrue();
}

@Test(
expectedExceptions = TestNGException.class,
expectedExceptionsMessageRegExp = ".*Check to make sure it can be instantiated",
description = "GITHUB-1827")
public void ensureExceptionThrownWhenNoSuitableConstructorFound() {

TestNG testng = create(GitHub1827Sample.class);
testng.run();
}

@Test(dataProvider = "dp", description = "GITHUB-1131")
public void factoryWithEmptyConstructorShouldWork(boolean bool) {
testFactory(bool, EmptyConstructorSample.class);
assertThat(MyObjectFactory.allParams).containsExactly(new Object[] {}, new Object[] {});
}

@Test(dataProvider = "dp", description = "GITHUB-1131")
public void factoryWithIntConstructorShouldWork(boolean bool) {
testFactory(bool, IntConstructorSample.class);
assertThat(MyObjectFactory.allParams).containsExactly(new Object[] {1}, new Object[] {2});
}

@Test(dataProvider = "dp", description = "GITHUB-1131")
public void factoryWithStringConstructorShouldWork(boolean bool) {
testFactory(bool, StringConstructorSample.class);
assertThat(MyObjectFactory.allParams)
.containsExactly(new Object[] {"foo"}, new Object[] {"bar"});
}

private void testFactory(boolean onSuite, Class<?> sample) {
MyObjectFactory.allParams.clear();

XmlSuite suite = createXmlSuite("Test IObjectFactory2", "TmpTest", sample);
TestNG tng = create(suite);

if (onSuite) {
suite.setObjectFactoryClass(MyObjectFactory.class);
} else {
tng.setObjectFactory(MyObjectFactory.class);
}

tng.run();
}

@DataProvider
public static Object[][] dp() {
return new Object[][] {new Object[] {true}, new Object[] {false}};
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package test.objectfactory.issue2676;

import java.util.List;
import org.testng.IAlterSuiteListener;
import org.testng.xml.XmlSuite;

public class LocalSuiteAlteringListener implements IAlterSuiteListener {

@Override
public void alter(List<XmlSuite> suites) {
suites.forEach(each -> each.setObjectFactoryClass(LoggingObjectFactorySample.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package test.objectfactory.issue2676;

import java.lang.reflect.Constructor;
import org.testng.ITestObjectFactory;
import org.testng.internal.objects.InstanceCreator;

public class LoggingObjectFactorySample implements ITestObjectFactory {

public static boolean wasInvoked = false;

@Override
public <T> T newInstance(Constructor<T> constructor, Object... parameters) {
wasInvoked = true;
return InstanceCreator.newInstance(constructor, parameters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package test.objectfactory.issue2676;

import org.testng.annotations.Test;

public class TestClassSample {

@Test
public void sampleTestMethod() {}
}
3 changes: 1 addition & 2 deletions testng-core/src/test/resources/testng.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
<class name="test.factory.issue1924.IssueTest"/>
<class name="test.factory.github328.GitHub328Test" />
<class name="test.factory.issue1745.Github1745Test"/>
<class name="test.objectfactory.github1827.GitHub1827Test"/>
<class name="test.objectfactory.ObjectFactoryTest"/>
<class name="test.github1490.VerifyDataProviderListener"/>
<class name="test.listeners.issue2456.IssueTest"/>
<class name="test.methodselection.MethodSelectionTest"/>
Expand Down Expand Up @@ -432,7 +432,6 @@
<class name="test.factory.FactoryIntegrationTest" />
<class name="test.factory.EmptyFactoryDataProviderTest" />

<class name="test.factory.github1131.GitHub1131Test" />
<class name="test.factory.nested.GitHub1307Test"/>

</classes>
Expand Down