From c82fed3d87e6a80044c97096340f6475a2ae5588 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Tue, 30 Jul 2002 11:41:10 +0000 Subject: Added DataSource code and tests submitted by Aaron Mulder --- .../test/jdbc2/optional/BaseDataSourceTest.java | 160 ++++++++++ .../test/jdbc2/optional/ConnectionPoolTest.java | 327 +++++++++++++++++++++ .../test/jdbc2/optional/OptionalTestSuite.java | 24 ++ .../test/jdbc2/optional/SimpleDataSourceTest.java | 38 +++ 4 files changed, 549 insertions(+) create mode 100644 src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java create mode 100644 src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java create mode 100644 src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java create mode 100644 src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java (limited to 'src/interfaces/jdbc/org/postgresql/test') diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java new file mode 100644 index 00000000000..f26fca20c91 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java @@ -0,0 +1,160 @@ +package org.postgresql.test.jdbc2.optional; + +import junit.framework.TestCase; +import org.postgresql.test.JDBC2Tests; +import org.postgresql.jdbc2.optional.SimpleDataSource; +import org.postgresql.jdbc2.optional.BaseDataSource; + +import java.sql.*; + +/** + * Common tests for all the BaseDataSource implementations. This is + * a small variety to make sure that a connection can be opened and + * some basic queries run. The different BaseDataSource subclasses + * have different subclasses of this which add additional custom + * tests. + * + * @author Aaron Mulder (ammulder@chariotsolutions.com) + * @version $Revision: 1.1 $ + */ +public abstract class BaseDataSourceTest extends TestCase { + protected Connection con; + protected BaseDataSource bds; + + /** + * Constructor required by JUnit + */ + public BaseDataSourceTest(String name) { + super(name); + } + + /** + * Creates a test table using a standard connection (not from a + * DataSource). + */ + protected void setUp() throws Exception { + con = JDBC2Tests.openDB(); + JDBC2Tests.createTable(con, "poolingtest", "id int4 not null primary key, name varchar(50)"); + Statement stmt = con.createStatement(); + stmt.executeUpdate("INSERT INTO poolingtest VALUES (1, 'Test Row 1')"); + stmt.executeUpdate("INSERT INTO poolingtest VALUES (2, 'Test Row 2')"); + JDBC2Tests.closeDB(con); + } + + /** + * Removes the test table using a standard connection (not from + * a DataSource) + */ + protected void tearDown() throws Exception { + con = JDBC2Tests.openDB(); + JDBC2Tests.dropTable(con, "poolingtest"); + JDBC2Tests.closeDB(con); + } + + /** + * Gets a connection from the current BaseDataSource + */ + protected Connection getDataSourceConnection() throws SQLException { + initializeDataSource(); + return bds.getConnection(); + } + + /** + * Creates an instance of the current BaseDataSource for + * testing. Must be customized by each subclass. + */ + protected abstract void initializeDataSource(); + + /** + * Test to make sure you can instantiate and configure the + * appropriate DataSource + */ + public void testCreateDataSource() { + initializeDataSource(); + } + + /** + * Test to make sure you can get a connection from the DataSource, + * which in turn means the DataSource was able to open it. + */ + public void testGetConnection() { + try { + con = getDataSourceConnection(); + con.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * A simple test to make sure you can execute SQL using the + * Connection from the DataSource + */ + public void testUseConnection() { + try { + con = getDataSourceConnection(); + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM poolingtest"); + if(rs.next()) { + int count = rs.getInt(1); + if(rs.next()) { + fail("Should only have one row in SELECT COUNT result set"); + } + if(count != 2) { + fail("Count returned "+count+" expecting 2"); + } + } else { + fail("Should have one row in SELECT COUNT result set"); + } + rs.close(); + st.close(); + con.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * A test to make sure you can execute DDL SQL using the + * Connection from the DataSource. + */ + public void testDdlOverConnection() { + try { + con = getDataSourceConnection(); + JDBC2Tests.dropTable(con, "poolingtest"); + JDBC2Tests.createTable(con, "poolingtest", "id int4 not null primary key, name varchar(50)"); + con.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * A test to make sure the connections are not being pooled by the + * current DataSource. Obviously need to be overridden in the case + * of a pooling Datasource. + */ + public void testNotPooledConnection() { + try { + con = getDataSourceConnection(); + String name = con.toString(); + con.close(); + con = getDataSourceConnection(); + String name2 = con.toString(); + con.close(); + assertTrue(!name.equals(name2)); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Eventually, we must test stuffing the DataSource in JNDI and + * then getting it back out and make sure it's still usable. This + * should ideally test both Serializable and Referenceable + * mechanisms. Will probably be multiple tests when implemented. + */ + public void testJndi() { + // TODO: Put the DS in JNDI, retrieve it, and try some of this stuff again + } +} diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java new file mode 100644 index 00000000000..5802f156caa --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java @@ -0,0 +1,327 @@ +package org.postgresql.test.jdbc2.optional; + +import org.postgresql.jdbc2.optional.ConnectionPool; +import org.postgresql.test.JDBC2Tests; +import javax.sql.*; +import java.sql.*; + +/** + * Tests for the ConnectionPoolDataSource and PooledConnection + * implementations. They are tested together because the only client + * interface to the PooledConnection is through the CPDS. + * + * @author Aaron Mulder (ammulder@chariotsolutions.com) + * @version $Revision: 1.1 $ + */ +public class ConnectionPoolTest extends BaseDataSourceTest { + /** + * Constructor required by JUnit + */ + public ConnectionPoolTest(String name) { + super(name); + } + + /** + * Creates and configures a ConnectionPool + */ + protected void initializeDataSource() { + if(bds == null) { + bds = new ConnectionPool(); + String db = JDBC2Tests.getURL(); + if(db.indexOf('/') > -1) { + db = db.substring(db.lastIndexOf('/')+1); + } else if(db.indexOf(':') > -1) { + db = db.substring(db.lastIndexOf(':')+1); + } + bds.setDatabaseName(db); + bds.setUser(JDBC2Tests.getUser()); + bds.setPassword(JDBC2Tests.getPassword()); + } + } + + /** + * Though the normal client interface is to grab a Connection, in + * order to test the middleware/server interface, we need to deal + * with PooledConnections. Some tests use each. + */ + protected PooledConnection getPooledConnection() throws SQLException { + initializeDataSource(); + return ((ConnectionPool)bds).getPooledConnection(); + } + + /** + * Instead of just fetching a Connection from the ConnectionPool, + * get a PooledConnection, add a listener to close it when the + * Connection is closed, and then get the Connection. Without + * the listener the PooledConnection (and thus the physical connection) + * would never by closed. Probably not a disaster during testing, but + * you never know. + */ + protected Connection getDataSourceConnection() throws SQLException { + initializeDataSource(); + final PooledConnection pc = getPooledConnection(); + // Since the pooled connection won't be reused in these basic tests, close it when the connection is closed + pc.addConnectionEventListener(new ConnectionEventListener() { + public void connectionClosed(ConnectionEvent event) { + try { + pc.close(); + } catch (SQLException e) { + fail("Unable to close PooledConnection: "+e); + } + } + + public void connectionErrorOccurred(ConnectionEvent event) { + } + }); + return pc.getConnection(); + } + + /** + * Makes sure that if you get a connection from a PooledConnection, + * close it, and then get another one, you're really using the same + * physical connection. Depends on the implementation of toString + * for the connection handle. + */ + public void testPoolReuse() { + try { + PooledConnection pc = getPooledConnection(); + con = pc.getConnection(); + String name = con.toString(); + con.close(); + con = pc.getConnection(); + String name2 = con.toString(); + con.close(); + pc.close(); + assertTrue("Physical connection doesn't appear to be reused across PooledConnection wrappers", name.equals(name2)); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Makes sure that when you request a connection from the + * PooledConnection, and previous connection it might have given + * out is closed. See JDBC 2.0 Optional Package spec section + * 6.2.3 + */ + public void testPoolCloseOldWrapper() { + try { + PooledConnection pc = getPooledConnection(); + con = pc.getConnection(); + Connection con2 = pc.getConnection(); + try { + con.createStatement(); + fail("Original connection wrapper should be closed when new connection wrapper is generated"); + } catch(SQLException e) {} + try { + con.close(); + fail("Original connection wrapper should be closed when new connection wrapper is generated"); + } catch(SQLException e) {} + con2.close(); + pc.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Makes sure that if you get two connection wrappers from the same + * PooledConnection, they are different, even though the represent + * the same physical connection. See JDBC 2.0 Optional Pacakge spec + * section 6.2.2 + */ + public void testPoolNewWrapper() { + try { + PooledConnection pc = getPooledConnection(); + con = pc.getConnection(); + Connection con2 = pc.getConnection(); + con2.close(); + pc.close(); + assertTrue("Two calls to PooledConnection.getConnection should not return the same connection wrapper", con != con2); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Makes sure that exactly one close event is fired for each time a + * connection handle is closed. Also checks that events are not + * fired after a given handle has been closed once. + */ + public void testCloseEvent() { + try { + PooledConnection pc = getPooledConnection(); + CountClose cc = new CountClose(); + pc.addConnectionEventListener(cc); + con = pc.getConnection(); + assertTrue(cc.getCount() == 0); + assertTrue(cc.getErrorCount() == 0); + con.close(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + con = pc.getConnection(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + con.close(); + assertTrue(cc.getCount() == 2); + assertTrue(cc.getErrorCount() == 0); + try { + con.close(); + fail("Should not be able to close a connection wrapper twice"); + } catch (SQLException e) {} + assertTrue(cc.getCount() == 2); + assertTrue(cc.getErrorCount() == 0); + pc.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Makes sure that close events are not fired after a listener has + * been removed. + */ + public void testNoCloseEvent() { + try { + PooledConnection pc = getPooledConnection(); + CountClose cc = new CountClose(); + pc.addConnectionEventListener(cc); + con = pc.getConnection(); + assertTrue(cc.getCount() == 0); + assertTrue(cc.getErrorCount() == 0); + con.close(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + pc.removeConnectionEventListener(cc); + con = pc.getConnection(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + con.close(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Makes sure that a listener can be removed while dispatching + * events. Sometimes this causes a ConcurrentModificationException + * or something. + */ + public void testInlineCloseEvent() { + try { + PooledConnection pc = getPooledConnection(); + RemoveClose rc1 = new RemoveClose(); + RemoveClose rc2 = new RemoveClose(); + RemoveClose rc3 = new RemoveClose(); + pc.addConnectionEventListener(rc1); + pc.addConnectionEventListener(rc2); + pc.addConnectionEventListener(rc3); + con = pc.getConnection(); + con.close(); + con = pc.getConnection(); + con.close(); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * Tests that a close event is not generated when a connection + * handle is closed automatically due to a new connection handle + * being opened for the same PooledConnection. See JDBC 2.0 + * Optional Package spec section 6.3 + */ + public void testAutomaticCloseEvent() { + try { + PooledConnection pc = getPooledConnection(); + CountClose cc = new CountClose(); + pc.addConnectionEventListener(cc); + con = pc.getConnection(); + assertTrue(cc.getCount() == 0); + assertTrue(cc.getErrorCount() == 0); + con.close(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + con = pc.getConnection(); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + // Open a 2nd connection, causing the first to be closed. No even should be generated. + Connection con2 = pc.getConnection(); + assertTrue("Connection handle was not closed when new handle was opened", con.isClosed()); + assertTrue(cc.getCount() == 1); + assertTrue(cc.getErrorCount() == 0); + con2.close(); + assertTrue(cc.getCount() == 2); + assertTrue(cc.getErrorCount() == 0); + pc.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Makes sure the isClosed method on a connection wrapper does what + * you'd expect. Checks the usual case, as well as automatic + * closure when a new handle is opened on the same physical connection. + */ + public void testIsClosed() { + try { + PooledConnection pc = getPooledConnection(); + Connection con = pc.getConnection(); + assertTrue(!con.isClosed()); + con.close(); + assertTrue(con.isClosed()); + con = pc.getConnection(); + Connection con2 = pc.getConnection(); + assertTrue(con.isClosed()); + assertTrue(!con2.isClosed()); + con2.close(); + assertTrue(con.isClosed()); + pc.close(); + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * Helper class to remove a listener during event dispatching. + */ + private class RemoveClose implements ConnectionEventListener { + public void connectionClosed(ConnectionEvent event) { + ((PooledConnection)event.getSource()).removeConnectionEventListener(this); + } + + public void connectionErrorOccurred(ConnectionEvent event) { + ((PooledConnection)event.getSource()).removeConnectionEventListener(this); + } + } + + /** + * Helper class that implements the event listener interface, and + * counts the number of events it sees. + */ + private class CountClose implements ConnectionEventListener { + private int count = 0, errorCount = 0; + public void connectionClosed(ConnectionEvent event) { + count++; + } + + public void connectionErrorOccurred(ConnectionEvent event) { + errorCount++; + } + + public int getCount() { + return count; + } + + public int getErrorCount() { + return errorCount; + } + + public void clear() { + count = errorCount = 0; + } + } +} diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java new file mode 100644 index 00000000000..ded7503a457 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java @@ -0,0 +1,24 @@ +package org.postgresql.test.jdbc2.optional; + +import junit.framework.TestSuite; + +/** + * Test suite for the JDBC 2.0 Optional Package implementation. This + * includes the DataSource, ConnectionPoolDataSource, and + * PooledConnection implementations. + * + * @author Aaron Mulder (ammulder@chariotsolutions.com) + * @version $Revision: 1.1 $ + */ +public class OptionalTestSuite extends TestSuite { + /** + * Gets the test suite for the entire JDBC 2.0 Optional Package + * implementation. + */ + public static TestSuite suite() { + TestSuite suite = new TestSuite(); + suite.addTestSuite(SimpleDataSourceTest.class); + suite.addTestSuite(ConnectionPoolTest.class); + return suite; + } +} diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java new file mode 100644 index 00000000000..1c917db6a1b --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java @@ -0,0 +1,38 @@ +package org.postgresql.test.jdbc2.optional; + +import org.postgresql.test.JDBC2Tests; +import org.postgresql.jdbc2.optional.SimpleDataSource; + +/** + * Performs the basic tests defined in the superclass. Just adds the + * configuration logic. + * + * @author Aaron Mulder (ammulder@chariotsolutions.com) + * @version $Revision: 1.1 $ + */ +public class SimpleDataSourceTest extends BaseDataSourceTest { + /** + * Constructor required by JUnit + */ + public SimpleDataSourceTest(String name) { + super(name); + } + + /** + * Creates and configures a new SimpleDataSource. + */ + protected void initializeDataSource() { + if(bds == null) { + bds = new SimpleDataSource(); + String db = JDBC2Tests.getURL(); + if(db.indexOf('/') > -1) { + db = db.substring(db.lastIndexOf('/')+1); + } else if(db.indexOf(':') > -1) { + db = db.substring(db.lastIndexOf(':')+1); + } + bds.setDatabaseName(db); + bds.setUser(JDBC2Tests.getUser()); + bds.setPassword(JDBC2Tests.getPassword()); + } + } +} -- cgit v1.2.3