testng-team / testng

TestNG testing framework
https://testng.org
Apache License 2.0
1.99k stars 1.02k forks source link

org.testng.Reporter#m_currentTestResult should be cleared #578

Open orange-buffalo opened 9 years ago

orange-buffalo commented 9 years ago

Currently org.testng.Reporter#m_currentTestResult is not cleared after test execution in case when SkipException was thrown. This causes memory leaks in some cases, for instance when using Arquillian (thread local contains reference to deployment module, for each test).

alexey-anufriev commented 9 years ago

Yeah man, I've faced this problem too. Guys 'TestNG' can we expect to get some fix for this?

dyorgio commented 9 years ago

Same problem here...

I'm doing it to solve problems with Arquillian:

@WebListener
public class ArquillianTestCleanerListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

        try {
            Field field = Class.forName("org.testng.TestNG").getDeclaredField("m_instance");
            field.setAccessible(true);
            field.set(null, null);
        } catch (Throwable e) {
            //ignore
        }

        try {
            Field field = Class.forName("org.testng.Reporter").getDeclaredField("m_currentTestResult");
            field.setAccessible(true);
            ThreadLocal threadLocal = (ThreadLocal)field.get(null);
            field.set(null, null);

            field = Thread.class.getDeclaredField("threadLocals");
            field.setAccessible(true);

            Method removeMethod = null;

            for (Thread t : Thread.getAllStackTraces().keySet()) {
                Object threadLocalsMapObject = field.get(t);
                if (threadLocalsMapObject != null){
                    if (removeMethod == null){
                        removeMethod = threadLocalsMapObject.getClass().getDeclaredMethod("remove", ThreadLocal.class);
                        removeMethod.setAccessible(true);
                    }

                     removeMethod.invoke(threadLocalsMapObject, threadLocal);
                }
            }

        } catch (Throwable e) {
            //ignore
        }

        try {
            Field field = Class.forName("org.jboss.arquillian.testenricher.cdi.container.CDIExtension")
                    .getDeclaredField("beanManager");
            field.setAccessible(true);
            field.set(null, null);

        } catch (Throwable e) {
            //ignore
        }
    }

}
orange-buffalo commented 9 years ago

Yeap, this is a good workaround, thanks!

dyorgio commented 9 years ago

Hi, new version (one more Threadlocal field cleanup):

@WebListener
public class ArquillianTestCleanerListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

        cleanStaticField("org.testng.TestNG", "m_instance");
        cleanStaticField("org.jboss.arquillian.testenricher.cdi.container.CDIExtension", "beanManager");

        cleanStaticThreadLocalField("org.testng.Reporter", "m_currentTestResult");
        cleanStaticThreadLocalField("org.jboss.arquillian.testng.State", "caughtExceptionAfter");
    }

    private void cleanStaticField(String className, String fieldName) {
        try {
            Field field = Class.forName(className).getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(null, null);
        } catch (Throwable e) {
            //ignore
        }
    }

    private void cleanStaticThreadLocalField(String className, String fieldName) {
        try {
            Field field = Class.forName(className).getDeclaredField(fieldName);
            field.setAccessible(true);
            ThreadLocal threadLocal = (ThreadLocal) field.get(null);
            field.set(null, null);

            field = Thread.class.getDeclaredField("threadLocals");
            field.setAccessible(true);

            Method removeMethod = null;

            for (Thread t : Thread.getAllStackTraces().keySet()) {
                Object threadLocalsMapObject = field.get(t);
                if (threadLocalsMapObject != null) {
                    if (removeMethod == null) {
                        removeMethod = threadLocalsMapObject.getClass().getDeclaredMethod("remove", ThreadLocal.class);
                        removeMethod.setAccessible(true);
                    }

                    removeMethod.invoke(threadLocalsMapObject, threadLocal);
                }
            }

        } catch (Throwable e) {
            //ignore
        }
    }
}
orange-buffalo commented 9 years ago

Hi! Could you please clarify which version contains this fix? I couldn't find this code in 6.8.17

dyorgio commented 9 years ago

Hi @orange-buffalo , this is a code to put in your application, not a TestNG code.

orange-buffalo commented 9 years ago

Ah, okay, thanks :)