/* jBilling - The Enterprise Open Source Billing System Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde This file is part of jbilling. jbilling is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. jbilling is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with jbilling. If not, see <http://www.gnu.org/licenses/>. */ package com.sapienter.jbilling.server.mediation.task; import com.sapienter.jbilling.server.mediation.Record; import junit.framework.TestCase; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.util.StopWatch; import javax.sql.DataSource; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * @author Brian Cowdery * @since 28-09-2010 */ public class JDBCReaderTest extends TestCase { // local in-memory database for testing private static final String DATABASE_NAME = "jdbc_reader_test"; private static final String TABLE_NAME = "records"; private static final String URL = "jdbc:hsqldb:mem:" + DATABASE_NAME; private static final String DRIVER = "org.hsqldb.jdbcDriver"; private static final String USERNAME = "sa"; private static final String PASSWORD = ""; private JdbcTemplate jdbcTemplate; // plug-in parameters private static Map<String, String> parameters; static { parameters = new HashMap<String, String>(); parameters.put("database_name", DATABASE_NAME); parameters.put("table_name", TABLE_NAME); parameters.put("url", URL); parameters.put("username", USERNAME); parameters.put("password", PASSWORD); parameters.put("batch_size", "100"); } // class under test private AbstractJDBCReader reader = new JDBCReader(); public JDBCReaderTest() { } public JDBCReaderTest(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(DRIVER); dataSource.setUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); jdbcTemplate = new JdbcTemplate(dataSource); createTestSchema(); reader.setParameters(parameters); reader.validate(new ArrayList<String>()); } @Override protected void tearDown() throws Exception { super.tearDown(); } /* Testing database setup */ private void createTestSchema() { String sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";" + " CREATE TABLE " + TABLE_NAME + "(" + " id INTEGER NOT NULL, " + " content VARCHAR(255) NOT NULL, " + " jbilling_timestamp TIMESTAMP NULL, " + " PRIMARY KEY (id) " + " );"; jdbcTemplate.execute(sql); } private void fillTestDatabase(int rows) { fillTestDatabase(rows, 0); } private void fillTestDatabase(int rows, int startingId) { for (int i = 0; i < rows; i++) { jdbcTemplate.update("INSERT INTO " + TABLE_NAME + " (id, content) VALUES (?, ?);", new Object[] { startingId + i, "row number " + i}); } } /* Tests */ public void testReaderConfig() throws Exception { assertEquals(DATABASE_NAME, reader.getDatabaseName()); assertEquals(DRIVER, reader.getDriverClassName()); assertEquals(URL, reader.getUrl()); assertEquals(USERNAME, reader.getUsername()); assertEquals(PASSWORD, reader.getPassword()); // case corrected table/column names // table/column names are configured in lowercase, HSQLDB uses uppercase internally assertEquals("RECORDS", reader.getTableName()); assertEquals("ID", reader.getKeyColumns().get(0)); assertEquals("JBILLING_TIMESTAMP", reader.getTimestampColumnName()); // the "jbilling_timestamp" column exits in our test db // record marking method set to TIMESTAMP assertEquals(AbstractJDBCReader.MarkMethod.TIMESTAMP, reader.getMarkMethod()); // generated query string assertEquals("SELECT * FROM RECORDS WHERE JBILLING_TIMESTAMP IS NULL ORDER BY ID", reader.getSqlQueryString()); } public void testBatchRead() throws Exception { fillTestDatabase(500); // 5 batches (100 per batch) StopWatch read = new StopWatch("read 500 rows from HSQLDB"); read.start(); int rowcount = 0; int iterations = 0; for (List<Record> records : reader) { rowcount = rowcount + records.size(); iterations++; } read.stop(); System.out.println(read.shortSummary()); assertEquals("500 rows read", 500, rowcount); assertEquals("5 iterations to read", 5, iterations); } // NOTE: this test takes approximately 3.5 minutes, comment out to improve testing performance /** * Reader stress test. Reads a large volume of records to ensure that there are no memory * leaks adverse effects of reading in large volumes of data. * * @throws Exception jUnit thinks crazy things can happen */ public void testLargeRead() throws Exception { fillTestDatabase(150000); // 1500 batches (100 per batch) StopWatch read = new StopWatch("read 150000 rows from HSQLDB"); read.start(); int rowcount = 0; int iterations = 0; for (List<Record> records : reader) { rowcount = rowcount + records.size(); iterations++; } read.stop(); System.out.println(read.shortSummary()); assertEquals("150000 rows read", 150000, rowcount); assertEquals("1500 iterations to read", 1500, iterations); } public void testMultipleReads() throws Exception { int rowcount = 0; // read 100 records fillTestDatabase(100, 0); for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("100 rows read", 100, rowcount); // read again, no new records inserted for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("100 rows read, no new rows", 100, rowcount); // read another 100 records fillTestDatabase(100, 100); for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("200 rows read", 200, rowcount); } public void testPartialBatchRead() throws Exception { int rowcount = 0; int iterations = 0; // read 133 records fillTestDatabase(133); for (List<Record> records : reader) { rowcount = rowcount + records.size(); iterations++; } assertEquals("133 rows read", 133, rowcount); assertEquals("2 iterations to read", 2, iterations); // read again, no new records inserted for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("133 rows read, no new rows", 133, rowcount); } public void testTimestampMarking() throws Exception { int rowcount = 0; // read 100 records, verify timestamps are set fillTestDatabase(100, 0); for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("100 rows read", 100, rowcount); assertAllTimestampsSet(); // read another 100 records, verify timestamps are set fillTestDatabase(100, 100); for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("200 rows, 100 new rows", 200, rowcount); assertAllTimestampsSet(); // read a partial batch, verify timestamps are set fillTestDatabase(73, 200); for (List<Record> records : reader) { rowcount = rowcount + records.size(); } assertEquals("273 rows, 73 new rows", 273, rowcount); assertAllTimestampsSet(); } public void assertAllTimestampsSet() { int rows = jdbcTemplate.queryForInt("SELECT count(*) from " + TABLE_NAME + " WHERE jbilling_timestamp is null"); assertEquals("all rows should be timestamped", 0, rows); } }