From 6ff15ff3fb78af44d052462af6929e489880332b Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 17 Jul 2023 19:16:43 +0100 Subject: [PATCH] HHH-16911 Integration test for empty SessionFactory to not leak the CL --- .../HibernateClassLoaderLeaksTest.java | 71 +++++++++++++++++++ .../HibernateLoadingTestAction.java | 58 +++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateClassLoaderLeaksTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateLoadingTestAction.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateClassLoaderLeaksTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateClassLoaderLeaksTest.java new file mode 100644 index 0000000000..fe13eab671 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateClassLoaderLeaksTest.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.registry.classloading; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Set; +import java.util.stream.Collectors; + +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.junit.Assert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Verifies that Hibernate ORM won't leak the classloader. + * We only test in H2 to save some time; we also need to avoid + * leaks by JDBC drivers, and since these aren't our responsibility + * it's best to focus on a single DB. + */ +@RequiresDialect(H2Dialect.class) +public class HibernateClassLoaderLeaksTest { + + private static Set knownDrivers; + + @BeforeAll + public static void prepareForClassLoaderLeakTest() { + final String property = System.getProperty( "log4j2.disableJmx" ); + Assert.assertEquals( "To be able to test against leaks, the system property 'log4j2.disableJmx' must be set to true", + "true", property ); + + //Attempt to workaround the mess of DriverManager leaks by clearing it before the test; + //it will most certainly re-register all drivers again within the test running context, + //but that will imply that the isolated classloader will also have permission to de-register them. + knownDrivers = DriverManager.drivers().collect( Collectors.toUnmodifiableSet() ); + knownDrivers.forEach( HibernateClassLoaderLeaksTest::cleanup ); + } + + @AfterAll + public static void restoreRegisteredDrivers() throws SQLException { + if ( knownDrivers != null ) { + for ( Driver driver : knownDrivers ) { + DriverManager.registerDriver( driver ); + } + } + } + + @Test + public void hibernateDoesNotLeakClassloader() { + ClassLoaderLeakDetector.assertNotLeakingAction( HibernateLoadingTestAction.class.getName() ); + } + + private static void cleanup(Driver driver) { + System.out.println( "Attempting de-registration of driver: " + driver ); + try { + DriverManager.deregisterDriver( driver ); + } + catch ( SQLException e ) { + throw new RuntimeException( e ); + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateLoadingTestAction.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateLoadingTestAction.java new file mode 100644 index 0000000000..44729b558e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/registry/classloading/HibernateLoadingTestAction.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.registry.classloading; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; + +import jakarta.persistence.EntityManagerFactory; + +/** + * A Runnable which initializes an EntityManagerFactory; + * this is meant to test against classloader leaks, so needs + * to be packaged as a Runnable rather than using our usual + * testing facilities. + */ +public class HibernateLoadingTestAction extends NotLeakingTestAction implements Runnable { + + @Override + public void run() { + super.run(); //for basic sanity self-check + final Map config = new HashMap(); + EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( + new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + config + ).build(); + try { + emf.close(); + } + finally { + cleanupJDBCDrivers(); + } + } + + private void cleanupJDBCDrivers() { + DriverManager.drivers().forEach( this::deregister ); + } + + private void deregister(final Driver driver) { + System.out.println( "Unregistering driver: " +driver); + try { + DriverManager.deregisterDriver( driver ); + } + catch ( SQLException e ) { + throw new RuntimeException( e ); + } + } + +}