/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.message.TraceSystem;
import org.h2.store.FileLock;
import org.h2.store.fs.FileSystemSplit;
import org.h2.test.utils.ProxyCodeGenerator;
import org.h2.test.utils.RecordingFileSystem;
import org.h2.test.utils.ResultVerifier;
import org.h2.tools.DeleteDbFiles;
import org.h2.util.IOUtils;
/**
* The base class for all tests.
*/
public abstract class TestBase {
/**
* The base directory.
*/
public static final String BASE_TEST_DIR = "data";
/**
* The temporary directory.
*/
protected static final String TEMP_DIR = "data/temp";
/**
* An id used to create unique file names.
*/
protected static int uniqueId;
/**
* The base directory to write test databases.
*/
private static String baseDir = getTestDir("");
/**
* The last time something was printed.
*/
private static long lastPrint;
/**
* The test configuration.
*/
public TestAll config;
/**
* The time when the test was started.
*/
protected long start;
private LinkedList<byte[]> memory = new LinkedList<byte[]>();
/**
* Get the test directory for this test.
*
* @param name the directory name suffix
* @return the test directory
*/
public static String getTestDir(String name) {
return BASE_TEST_DIR + "/test" + name;
}
/**
* Start the TCP server if enabled in the configuration.
*/
protected void startServerIfRequired() throws SQLException {
config.beforeTest();
}
/**
* Initialize the test configuration using the default settings.
*
* @return itself
*/
public TestBase init() throws Exception {
return init(new TestAll());
}
/**
* Initialize the test configuration.
*
* @param conf the configuration
* @return itself
*/
public TestBase init(TestAll conf) throws Exception {
baseDir = getTestDir("");
System.setProperty("java.io.tmpdir", TEMP_DIR);
this.config = conf;
return this;
}
/**
* Run a test case using the given seed value.
*
* @param seed the random seed value
*/
public void testCase(int seed) throws Exception {
// do nothing
}
/**
* This method is initializes the test, runs the test by calling the test()
* method, and prints status information. It also catches exceptions so that
* the tests can continue.
*
* @param conf the test configuration
*/
public void runTest(TestAll conf) {
try {
init(conf);
start = System.currentTimeMillis();
test();
println("");
} catch (Throwable e) {
println("FAIL " + e.toString());
logError("FAIL " + e.toString(), e);
if (config.stopOnError) {
throw new AssertionError("ERROR");
}
if (e instanceof OutOfMemoryError) {
throw (OutOfMemoryError) e;
}
} finally {
try {
IOUtils.deleteRecursive("memFS:", false);
IOUtils.deleteRecursive("memLZF:", false);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
/**
* Open a database connection in admin mode. The default user name and
* password is used.
*
* @param name the database name
* @return the connection
*/
public Connection getConnection(String name) throws SQLException {
return getConnectionInternal(getURL(name, true), getUser(), getPassword());
}
/**
* Open a database connection.
*
* @param name the database name
* @param user the user name to use
* @param password the password to use
* @return the connection
*/
protected Connection getConnection(String name, String user, String password) throws SQLException {
return getConnectionInternal(getURL(name, false), user, password);
}
/**
* Get the password to use to login for the given user password. The file
* password is added if required.
*
* @param userPassword the password of this user
* @return the login password
*/
protected String getPassword(String userPassword) {
return config == null || config.cipher == null ? userPassword : getFilePassword() + " " + userPassword;
}
/**
* Get the file password (only required if file encryption is used).
*
* @return the file password
*/
protected String getFilePassword() {
return "filePassword";
}
/**
* Get the login password. This is usually the user password. If file
* encryption is used it is combined with the file password.
*
* @return the login password
*/
protected String getPassword() {
return getPassword("123");
}
/**
* Get the base directory for tests.
* If a special file system is used, the prefix is prepended.
*
* @return the directory, possibly including file system prefix
*/
public String getBaseDir() {
String dir = baseDir;
if (config != null) {
if (config.record) {
dir = RecordingFileSystem.PREFIX + "memFS:" + dir;
}
if (config.splitFileSystem) {
dir = FileSystemSplit.PREFIX + "16:" + dir;
}
}
// return "split:nioMapped:" + baseDir;
return dir;
}
/**
* Get the database URL for the given database name using the current
* configuration options.
*
* @param name the database name
* @param admin true if the current user is an admin
* @return the database URL
*/
protected String getURL(String name, boolean admin) {
String url;
if (name.startsWith("jdbc:")) {
return name;
}
if (config.memory) {
name = "mem:" + name;
} else {
int idx = name.indexOf(':');
if (idx < 0 || idx > 10) {
// index > 10 if in options
name = getBaseDir() + "/" + name;
}
}
if (config.networked) {
if (config.ssl) {
url = "ssl://localhost:9192/" + name;
} else {
url = "tcp://localhost:9192/" + name;
}
} else if (config.googleAppEngine) {
url = "gae://" + name + ";FILE_LOCK=NO;AUTO_SERVER=FALSE;DB_CLOSE_ON_EXIT=FALSE";
} else {
url = name;
}
if (!config.memory) {
if (config.smallLog && admin) {
url = addOption(url, "MAX_LOG_SIZE", "1");
}
}
if (config.traceSystemOut) {
url = addOption(url, "TRACE_LEVEL_SYSTEM_OUT", "2");
}
if (config.traceLevelFile > 0 && admin) {
url = addOption(url, "TRACE_LEVEL_FILE", "" + config.traceLevelFile);
url = addOption(url, "TRACE_MAX_FILE_SIZE", "8");
}
url = addOption(url, "LOG", "1");
if (config.throttleDefault > 0) {
url = addOption(url, "THROTTLE", "" + config.throttleDefault);
} else if (config.throttle > 0) {
url = addOption(url, "THROTTLE", "" + config.throttle);
}
url = addOption(url, "LOCK_TIMEOUT", "50");
if (config.diskUndo && admin) {
url = addOption(url, "MAX_MEMORY_UNDO", "3");
}
if (config.big && admin) {
// force operations to disk
url = addOption(url, "MAX_OPERATION_MEMORY", "1");
}
if (config.mvcc) {
url = addOption(url, "MVCC", "TRUE");
}
if (config.cacheType != null && admin) {
url = addOption(url, "CACHE_TYPE", config.cacheType);
}
if (config.diskResult && admin) {
url = addOption(url, "MAX_MEMORY_ROWS", "100");
url = addOption(url, "CACHE_SIZE", "0");
}
if (config.cipher != null) {
url = addOption(url, "CIPHER", config.cipher);
}
if (config.defrag) {
url = addOption(url, "DEFRAG_ALWAYS", "TRUE");
}
if (config.nestedJoins) {
url = addOption(url, "NESTED_JOINS", "TRUE");
}
return "jdbc:h2:" + url;
}
private static String addOption(String url, String option, String value) {
if (url.indexOf(";" + option + "=") < 0) {
url += ";" + option + "=" + value;
}
return url;
}
private static Connection getConnectionInternal(String url, String user, String password) throws SQLException {
org.h2.Driver.load();
// url += ";DEFAULT_TABLE_TYPE=1";
// Class.forName("org.hsqldb.jdbcDriver");
// return DriverManager.getConnection("jdbc:hsqldb:" + name, "sa", "");
return DriverManager.getConnection(url, user, password);
}
/**
* Get the small or the big value depending on the configuration.
*
* @param small the value to return if the current test mode is 'small'
* @param big the value to return if the current test mode is 'big'
* @return small or big, depending on the configuration
*/
protected int getSize(int small, int big) {
return config.endless ? Integer.MAX_VALUE : config.big ? big : small;
}
protected String getUser() {
return "sa";
}
/**
* Write a message to system out if trace is enabled.
*
* @param x the value to write
*/
protected void trace(int x) {
trace("" + x);
}
/**
* Write a message to system out if trace is enabled.
*
* @param s the message to write
*/
public void trace(String s) {
if (config.traceTest) {
lastPrint = 0;
println(s);
}
}
/**
* Print how much memory is currently used.
*/
protected void traceMemory() {
if (config.traceTest) {
trace("mem=" + getMemoryUsed());
}
}
/**
* Print the currently used memory, the message and the given time in
* milliseconds.
*
* @param s the message
* @param time the time in millis
*/
public void printTimeMemory(String s, long time) {
if (config.big) {
println(getMemoryUsed() + " MB: " + s + " ms: " + time);
}
}
/**
* Get the number of megabytes heap memory in use.
*
* @return the used megabytes
*/
public static int getMemoryUsed() {
Runtime rt = Runtime.getRuntime();
long memory = Long.MAX_VALUE;
for (int i = 0; i < 8; i++) {
rt.gc();
long memNow = rt.totalMemory() - rt.freeMemory();
if (memNow >= memory) {
break;
}
memory = memNow;
}
int mb = (int) (memory / 1024 / 1024);
return mb;
}
/**
* Called if the test reached a point that was not expected.
*
* @throws AssertionError always throws an AssertionError
*/
public void fail() {
fail("Unexpected success");
}
/**
* Called if the test reached a point that was not expected.
*
* @param string the error message
* @throws AssertionError always throws an AssertionError
*/
protected void fail(String string) {
lastPrint = 0;
println(string);
throw new AssertionError(string);
}
/**
* Log an error message.
*
* @param s the message
* @param e the exception
*/
public static void logError(String s, Throwable e) {
if (e == null) {
e = new Exception(s);
}
System.out.flush();
System.err.println("ERROR: " + s + " " + e.toString() + " ------------------------------");
e.printStackTrace();
try {
TraceSystem ts = new TraceSystem(null);
FileLock lock = new FileLock(ts, "error.lock", 1000);
lock.lock(FileLock.LOCK_FILE);
FileWriter fw = new FileWriter("error.txt", true);
PrintWriter pw = new PrintWriter(fw);
e.printStackTrace(pw);
pw.close();
fw.close();
lock.unlock();
} catch (Throwable t) {
t.printStackTrace();
}
System.err.flush();
}
/**
* Print a message to system out.
*
* @param s the message
*/
public void println(String s) {
long now = System.currentTimeMillis();
if (now > lastPrint + 1000) {
lastPrint = now;
long time = now - start;
printlnWithTime(time, getClass().getName() + " " + s);
}
}
/**
* Print a message, prepended with the specified time in milliseconds.
*
* @param millis the time in milliseconds
* @param s the message
*/
static void printlnWithTime(long millis, String s) {
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
s = dateFormat.format(new java.util.Date()) + " " + formatTime(millis) + " " + s;
System.out.println(s);
}
/**
* Print the current time and a message to system out.
*
* @param s the message
*/
protected void printTime(String s) {
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
println(dateFormat.format(new java.util.Date()) + " " + s);
}
/**
* Format the time in the format hh:mm:ss.1234 where 1234 is milliseconds.
*
* @param millis the time in milliseconds
* @return the formatted time
*/
static String formatTime(long millis) {
String s = new java.sql.Time(java.sql.Time.valueOf("0:0:0").getTime() + millis).toString()
+ "." + ("" + (1000 + (millis % 1000))).substring(1);
if (s.startsWith("00:")) {
s = s.substring(3);
}
return s;
}
/**
* Delete all database files for this database.
*
* @param name the database name
*/
protected void deleteDb(String name) {
deleteDb(getBaseDir(), name);
}
/**
* Delete all database files for a database.
*
* @param dir the directory where the database files are located
* @param name the database name
*/
protected void deleteDb(String dir, String name) {
DeleteDbFiles.execute(dir, name, true);
// ArrayList<String> list;
// list = FileLister.getDatabaseFiles(baseDir, name, true);
// if (list.size() > 0) {
// System.out.println("Not deleted: " + list);
// }
}
/**
* This method will be called by the test framework.
*
* @throws Exception if an exception in the test occurs
*/
public abstract void test() throws Exception;
/**
* Check if two values are equal, and if not throw an exception.
*
* @param message the message to print in case of error
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
public void assertEquals(String message, int expected, int actual) {
if (expected != actual) {
fail("Expected: " + expected + " actual: " + actual + " message: " + message);
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
public void assertEquals(int expected, int actual) {
if (expected != actual) {
fail("Expected: " + expected + " actual: " + actual);
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
public void assertEquals(byte[] expected, byte[] actual) {
if (expected == null || actual == null) {
assertTrue(expected == actual);
return;
}
assertEquals(expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
if (expected[i] != actual[i]) {
fail("[" + i + "]: expected: " + (int) expected[i] + " actual: " + (int) actual[i]);
}
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
public void assertEquals(Object[] expected, Object[] actual) {
if (expected == null || actual == null) {
assertTrue(expected == actual);
return;
}
assertEquals(expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
if (expected[i] == null || actual[i] == null) {
if (expected[i] != actual[i]) {
fail("[" + i + "]: expected: " + expected[i] + " actual: " + actual[i]);
}
} else if (!expected[i].equals(actual[i])) {
fail("[" + i + "]: expected: " + expected[i] + " actual: " + actual[i]);
}
}
}
/**
* Check if two readers are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @param len the maximum length, or -1
* @throws AssertionError if the values are not equal
*/
protected void assertEqualReaders(Reader expected, Reader actual, int len) throws IOException {
for (int i = 0; len < 0 || i < len; i++) {
int ce = expected.read();
int ca = actual.read();
assertEquals(ce, ca);
if (ce == -1) {
break;
}
}
expected.close();
actual.close();
}
/**
* Check if two streams are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @param len the maximum length, or -1
* @throws AssertionError if the values are not equal
*/
protected void assertEqualStreams(InputStream expected, InputStream actual, int len) throws IOException {
// this doesn't actually read anything - just tests reading 0 bytes
actual.read(new byte[0]);
expected.read(new byte[0]);
actual.read(new byte[10], 3, 0);
expected.read(new byte[10], 0, 0);
for (int i = 0; len < 0 || i < len; i++) {
int ca = actual.read();
actual.read(new byte[0]);
int ce = expected.read();
assertEquals(ce, ca);
if (ca == -1) {
break;
}
}
actual.read(new byte[10], 3, 0);
expected.read(new byte[10], 0, 0);
actual.read(new byte[0]);
expected.read(new byte[0]);
actual.close();
expected.close();
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param message the message to use if the check fails
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(String message, String expected, String actual) {
if (expected == null && actual == null) {
return;
} else if (expected == null || actual == null) {
fail("Expected: " + expected + " Actual: " + actual + " " + message);
}
if (!expected.equals(actual)) {
for (int i = 0; i < expected.length(); i++) {
String s = expected.substring(0, i);
if (!actual.startsWith(s)) {
expected = expected.substring(0, i) + "<*>" + expected.substring(i);
break;
}
}
int al = expected.length();
int bl = actual.length();
if (al > 4000) {
expected = expected.substring(0, 4000);
}
if (bl > 4000) {
actual = actual.substring(0, 4000);
}
fail("Expected: " + expected + " (" + al + ") actual: " + actual + " (" + bl + ") " + message);
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(String expected, String actual) {
assertEquals("", expected, actual);
}
/**
* Check if two result sets are equal, and if not throw an exception.
*
* @param message the message to use if the check fails
* @param rs0 the first result set
* @param rs1 the second result set
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(String message, ResultSet rs0, ResultSet rs1) throws SQLException {
ResultSetMetaData meta = rs0.getMetaData();
int columns = meta.getColumnCount();
assertEquals(columns, rs1.getMetaData().getColumnCount());
while (rs0.next()) {
assertTrue(message, rs1.next());
for (int i = 0; i < columns; i++) {
assertEquals(message, rs0.getString(i + 1), rs1.getString(i + 1));
}
}
assertFalse(message, rs0.next());
assertFalse(message, rs1.next());
}
/**
* Check if the first value is larger or equal than the second value, and if
* not throw an exception.
*
* @param a the first value
* @param b the second value (must be smaller than the first value)
* @throws AssertionError if the first value is smaller
*/
protected void assertSmaller(long a, long b) {
if (a >= b) {
fail("a: " + a + " is not smaller than b: " + b);
}
}
/**
* Check that a result contains the given substring.
*
* @param result the result value
* @param contains the term that should appear in the result
* @throws AssertionError if the term was not found
*/
protected void assertContains(String result, String contains) {
if (result.indexOf(contains) < 0) {
fail(result + " does not contain: " + contains);
}
}
/**
* Check that a text starts with the expected characters..
*
* @param text the text
* @param expectedStart the expected prefix
* @throws AssertionError if the text does not start with the expected characters
*/
protected void assertStartsWith(String text, String expectedStart) {
if (!text.startsWith(expectedStart)) {
fail("[" + text + "] does not start with: [" + expectedStart + "]");
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(long expected, long actual) {
if (expected != actual) {
fail("Expected: " + expected + " actual: " + actual);
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(double expected, double actual) {
if (expected != actual) {
if (Double.isNaN(expected) && Double.isNaN(actual)) {
// if both a NaN, then there is no error
} else {
fail("Expected: " + expected + " actual: " + actual);
}
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(float expected, float actual) {
if (expected != actual) {
if (Float.isNaN(expected) && Float.isNaN(actual)) {
// if both a NaN, then there is no error
} else {
fail("Expected: " + expected + " actual: " + actual);
}
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(boolean expected, boolean actual) {
if (expected != actual) {
fail("Boolean expected: " + expected + " actual: " + actual);
}
}
/**
* Check that the passed boolean is true.
*
* @param condition the condition
* @throws AssertionError if the condition is false
*/
public void assertTrue(boolean condition) {
assertTrue("Expected: true got: false", condition);
}
/**
* Check that the passed boolean is true.
*
* @param message the message to print if the condition is false
* @param condition the condition
* @throws AssertionError if the condition is false
*/
protected void assertTrue(String message, boolean condition) {
if (!condition) {
fail(message);
}
}
/**
* Check that the passed boolean is false.
*
* @param value the condition
* @throws AssertionError if the condition is true
*/
protected void assertFalse(boolean value) {
assertFalse("Expected: false got: true", value);
}
/**
* Check that the passed boolean is false.
*
* @param message the message to print if the condition is false
* @param value the condition
* @throws AssertionError if the condition is true
*/
protected void assertFalse(String message, boolean value) {
if (value) {
fail(message);
}
}
/**
* Check that the result set row count matches.
*
* @param expected the number of expected rows
* @param rs the result set
* @throws AssertionError if a different number of rows have been found
*/
protected void assertResultRowCount(int expected, ResultSet rs) throws SQLException {
int i = 0;
while (rs.next()) {
i++;
}
assertEquals(expected, i);
}
/**
* Check that the result set of a query is exactly this value.
*
* @param stat the statement
* @param sql the SQL statement to execute
* @param expected the expected result value
* @throws AssertionError if a different result value was returned
*/
protected void assertSingleValue(Statement stat, String sql, int expected) throws SQLException {
ResultSet rs = stat.executeQuery(sql);
assertTrue(rs.next());
assertEquals(expected, rs.getInt(1));
assertFalse(rs.next());
}
/**
* Check that the result set of a query is exactly this value.
*
* @param expected the expected result value
* @param stat the statement
* @param sql the SQL statement to execute
* @throws AssertionError if a different result value was returned
*/
protected void assertResult(String expected, Statement stat, String sql) throws SQLException {
ResultSet rs = stat.executeQuery(sql);
if (rs.next()) {
String actual = rs.getString(1);
assertEquals(expected, actual);
} else {
assertEquals(expected, null);
}
}
/**
* Check if the result set meta data is correct.
*
* @param rs the result set
* @param columnCount the expected column count
* @param labels the expected column labels
* @param datatypes the expected data types
* @param precision the expected precisions
* @param scale the expected scales
*/
protected void assertResultSetMeta(ResultSet rs, int columnCount, String[] labels, int[] datatypes, int[] precision,
int[] scale) throws SQLException {
ResultSetMetaData meta = rs.getMetaData();
int cc = meta.getColumnCount();
if (cc != columnCount) {
fail("result set contains " + cc + " columns not " + columnCount);
}
for (int i = 0; i < columnCount; i++) {
if (labels != null) {
String l = meta.getColumnLabel(i + 1);
if (!labels[i].equals(l)) {
fail("column label " + i + " is " + l + " not " + labels[i]);
}
}
if (datatypes != null) {
int t = meta.getColumnType(i + 1);
if (datatypes[i] != t) {
fail("column datatype " + i + " is " + t + " not " + datatypes[i] + " (prec="
+ meta.getPrecision(i + 1) + " scale=" + meta.getScale(i + 1) + ")");
}
String typeName = meta.getColumnTypeName(i + 1);
String className = meta.getColumnClassName(i + 1);
switch (t) {
case Types.INTEGER:
assertEquals("INTEGER", typeName);
assertEquals("java.lang.Integer", className);
break;
case Types.VARCHAR:
assertEquals("VARCHAR", typeName);
assertEquals("java.lang.String", className);
break;
case Types.SMALLINT:
assertEquals("SMALLINT", typeName);
assertEquals("java.lang.Short", className);
break;
case Types.TIMESTAMP:
assertEquals("TIMESTAMP", typeName);
assertEquals("java.sql.Timestamp", className);
break;
case Types.DECIMAL:
assertEquals("DECIMAL", typeName);
assertEquals("java.math.BigDecimal", className);
break;
default:
}
}
if (precision != null) {
int p = meta.getPrecision(i + 1);
if (precision[i] != p) {
fail("column precision " + i + " is " + p + " not " + precision[i]);
}
}
if (scale != null) {
int s = meta.getScale(i + 1);
if (scale[i] != s) {
fail("column scale " + i + " is " + s + " not " + scale[i]);
}
}
}
}
/**
* Check if a result set contains the expected data.
* The sort order is significant
*
* @param rs the result set
* @param data the expected data
* @throws AssertionError if there is a mismatch
*/
protected void assertResultSetOrdered(ResultSet rs, String[][] data) throws SQLException {
assertResultSet(true, rs, data);
}
/**
* Check if a result set contains the expected data.
*
* @param ordered if the sort order is significant
* @param rs the result set
* @param data the expected data
* @throws AssertionError if there is a mismatch
*/
private void assertResultSet(boolean ordered, ResultSet rs, String[][] data) throws SQLException {
int len = rs.getMetaData().getColumnCount();
int rows = data.length;
if (rows == 0) {
// special case: no rows
if (rs.next()) {
fail("testResultSet expected rowCount:" + rows + " got:0");
}
}
int len2 = data[0].length;
if (len < len2) {
fail("testResultSet expected columnCount:" + len2 + " got:" + len);
}
for (int i = 0; i < rows; i++) {
if (!rs.next()) {
fail("testResultSet expected rowCount:" + rows + " got:" + i);
}
String[] row = getData(rs, len);
if (ordered) {
String[] good = data[i];
if (!testRow(good, row, good.length)) {
fail("testResultSet row not equal, got:\n" + formatRow(row) + "\n" + formatRow(good));
}
} else {
boolean found = false;
for (int j = 0; j < rows; j++) {
String[] good = data[i];
if (testRow(good, row, good.length)) {
found = true;
break;
}
}
if (!found) {
fail("testResultSet no match for row:" + formatRow(row));
}
}
}
if (rs.next()) {
String[] row = getData(rs, len);
fail("testResultSet expected rowcount:" + rows + " got:>=" + (rows + 1) + " data:" + formatRow(row));
}
}
private static boolean testRow(String[] a, String[] b, int len) {
for (int i = 0; i < len; i++) {
String sa = a[i];
String sb = b[i];
if (sa == null || sb == null) {
if (sa != sb) {
return false;
}
} else {
if (!sa.equals(sb)) {
return false;
}
}
}
return true;
}
private static String[] getData(ResultSet rs, int len) throws SQLException {
String[] data = new String[len];
for (int i = 0; i < len; i++) {
data[i] = rs.getString(i + 1);
// just check if it works
rs.getObject(i + 1);
}
return data;
}
private static String formatRow(String[] row) {
String sb = "";
for (String r : row) {
sb += "{" + r + "}";
}
return "{" + sb + "}";
}
/**
* Simulate a database crash. This method will also close the database
* files, but the files are in a state as the power was switched off. It
* doesn't throw an exception.
*
* @param conn the database connection
*/
protected void crash(Connection conn) {
((JdbcConnection) conn).setPowerOffCount(1);
try {
conn.createStatement().execute("SET WRITE_DELAY 0");
conn.createStatement().execute("CREATE TABLE TEST_A(ID INT)");
fail("should be crashed already");
} catch (SQLException e) {
// expected
}
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
/**
* Read a string from the reader. This method reads until end of file.
*
* @param reader the reader
* @return the string read
*/
protected String readString(Reader reader) {
if (reader == null) {
return null;
}
StringBuilder buffer = new StringBuilder();
try {
while (true) {
int c = reader.read();
if (c == -1) {
break;
}
buffer.append((char) c);
}
return buffer.toString();
} catch (Exception e) {
assertTrue(false);
return null;
}
}
/**
* Check that a given exception is not an unexpected 'general error'
* exception.
*
* @param e the error
*/
public void assertKnownException(SQLException e) {
assertKnownException("", e);
}
/**
* Check that a given exception is not an unexpected 'general error'
* exception.
*
* @param message the message
* @param e the exception
*/
protected void assertKnownException(String message, SQLException e) {
if (e != null && e.getSQLState().startsWith("HY000")) {
TestBase.logError("Unexpected General error " + message, e);
}
}
/**
* Check if two values are equal, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the values are not equal
*/
protected void assertEquals(Integer expected, Integer actual) {
if (expected == null || actual == null) {
assertTrue(expected == null && actual == null);
} else {
assertEquals(expected.intValue(), actual.intValue());
}
}
/**
* Check if two databases contain the same met data.
*
* @param stat1 the connection to the first database
* @param stat2 the connection to the second database
* @throws AssertionError if the databases don't match
*/
protected void assertEqualDatabases(Statement stat1, Statement stat2) throws SQLException {
ResultSet rs = stat1.executeQuery("select value from information_schema.settings where name='ANALYZE_AUTO'");
int analyzeAuto = rs.next() ? rs.getInt(1) : 0;
if (analyzeAuto > 0) {
stat1.execute("analyze");
stat2.execute("analyze");
}
ResultSet rs1 = stat1.executeQuery("SCRIPT simple NOPASSWORDS");
ResultSet rs2 = stat2.executeQuery("SCRIPT simple NOPASSWORDS");
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<String> list2 = new ArrayList<String>();
while (rs1.next()) {
String s1 = rs1.getString(1);
if (!rs2.next()) {
fail("expected: " + s1);
}
String s2 = rs2.getString(1);
if (!s1.equals(s2)) {
list1.add(s1);
list2.add(s2);
}
}
for (String s : list1) {
if (!list2.remove(s)) {
fail("only found in first: " + s);
}
}
assertEquals(0, list2.size());
assertFalse(rs2.next());
}
/**
* Create a new object of the calling class.
*
* @return the new test
*/
public static TestBase createCaller() {
return createCaller(new Exception().getStackTrace()[1].getClassName());
}
/**
* Create a new object of the given class.
*
* @param className the class name
* @return the new test
*/
public static TestBase createCaller(String className) {
org.h2.Driver.load();
try {
return (TestBase) Class.forName(className).newInstance();
} catch (Exception e) {
throw new RuntimeException("Can not create object " + className, e);
}
}
/**
* Get the classpath list used to execute java -cp ...
*
* @return the classpath list
*/
protected String getClassPath() {
return "bin" + File.pathSeparator + "temp" + File.pathSeparator + ".";
}
/**
* Use up almost all memory.
*
* @param remainingKB the number of kilobytes that are not referenced
*/
protected void eatMemory(int remainingKB) {
byte[] reserve = new byte[remainingKB * 1024];
// first, eat memory in 16 KB blocks, then eat in 16 byte blocks
for (int size = 16 * 1024; size > 0; size /= 1024) {
while (true) {
try {
byte[] block = new byte[16 * 1024];
memory.add(block);
} catch (OutOfMemoryError e) {
break;
}
}
}
// silly code - makes sure there are no warnings
reserve[0] = reserve[1];
}
/**
* Remove the hard reference to the memory.
*/
protected void freeMemory() {
memory.clear();
}
/**
* Verify the next method call on the object will throw an exception.
*
* @param <T> the class of the object
* @param expectedExceptionClass the expected exception class to be thrown
* @param obj the object to wrap
* @return a proxy for the object
*/
protected <T> T assertThrows(final Class<?> expectedExceptionClass, final T obj) {
return assertThrows(new ResultVerifier() {
public boolean verify(Object returnValue, Throwable t, Method m, Object... args) {
if (t == null) {
throw new AssertionError("Expected an exception of type " +
expectedExceptionClass.getSimpleName() +
" to be thrown, but the method returned " +
returnValue +
" for " + ProxyCodeGenerator.methodCallFormatter(m, args));
}
if (!expectedExceptionClass.isAssignableFrom(t.getClass())) {
AssertionError ae = new AssertionError(
"Expected an exception of type\n" +
expectedExceptionClass.getSimpleName() +
" to be thrown, but the method under test threw an exception of type\n" +
t.getClass().getSimpleName() +
" (see in the 'Caused by' for the exception that was thrown) " +
" for " + ProxyCodeGenerator.methodCallFormatter(m, args));
ae.initCause(t);
throw ae;
}
return false;
}
}, obj);
}
/**
* Verify the next method call on the object will throw an exception.
*
* @param <T> the class of the object
* @param expectedErrorCode the expected error code
* @param obj the object to wrap
* @return a proxy for the object
*/
protected <T> T assertThrows(final int expectedErrorCode, final T obj) {
return assertThrows(new ResultVerifier() {
public boolean verify(Object returnValue, Throwable t, Method m, Object... args) {
int errorCode;
if (t instanceof DbException) {
errorCode = ((DbException) t).getErrorCode();
} else if (t instanceof SQLException) {
errorCode = ((SQLException) t).getErrorCode();
} else {
errorCode = 0;
}
if (errorCode != expectedErrorCode) {
AssertionError ae = new AssertionError(
"Expected an SQLException or DbException with error code " + expectedErrorCode);
ae.initCause(t);
throw ae;
}
return false;
}
}, obj);
}
/**
* Verify the next method call on the object will throw an exception.
*
* @param <T> the class of the object
* @param handler the exception handler to call
* @param obj the object to wrap
* @return a proxy for the object
*/
@SuppressWarnings("unchecked")
protected <T> T assertThrows(final ResultVerifier verifier, final T obj) {
Class<?> c = obj.getClass();
InvocationHandler ih = new InvocationHandler() {
private Exception called = new Exception("No method called");
public void finalize() {
if (called != null) {
called.printStackTrace(System.err);
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
try {
called = null;
Object ret = method.invoke(obj, args);
verifier.verify(ret, null, method, args);
return ret;
} catch (InvocationTargetException e) {
verifier.verify(null, e.getTargetException(), method, args);
Class<?> retClass = method.getReturnType();
if (!retClass.isPrimitive()) {
return null;
}
if (retClass == boolean.class) {
return false;
} else if (retClass == byte.class) {
return (byte) 0;
} else if (retClass == char.class) {
return (char) 0;
} else if (retClass == short.class) {
return (short) 0;
} else if (retClass == int.class) {
return 0;
} else if (retClass == long.class) {
return 0L;
} else if (retClass == float.class) {
return 0F;
} else if (retClass == double.class) {
return 0D;
}
return null;
}
}
};
if (!ProxyCodeGenerator.isGenerated(c)) {
Class<?>[] interfaces = c.getInterfaces();
if (Modifier.isFinal(c.getModifiers()) || (interfaces.length > 0 && getClass() != c)) {
// interface class proxies
if (interfaces.length == 0) {
throw new RuntimeException("Can not create a proxy for the class " +
c.getSimpleName() +
" because it doesn't implement any interfaces and is final");
}
return (T) Proxy.newProxyInstance(c.getClassLoader(), interfaces, ih);
}
}
try {
Class<?> pc = ProxyCodeGenerator.getClassProxy(c);
Constructor cons = pc.getConstructor(new Class<?>[] { InvocationHandler.class });
return (T) cons.newInstance(new Object[] { ih });
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Create a proxy class that extends the given class.
*
* @param clazz the class
*/
protected void createClassProxy(Class<?> clazz) {
try {
ProxyCodeGenerator.getClassProxy(clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}