001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (license2)
004: * Initial Developer: H2 Group
005: */
006: package org.h2.test.synth;
007:
008: import java.io.PrintWriter;
009: import java.io.StringWriter;
010: import java.lang.reflect.Array;
011: import java.lang.reflect.InvocationTargetException;
012: import java.lang.reflect.Method;
013: import java.sql.BatchUpdateException;
014: import java.sql.Blob;
015: import java.sql.CallableStatement;
016: import java.sql.Clob;
017: import java.sql.Connection;
018: import java.sql.DriverManager;
019: import java.sql.ParameterMetaData;
020: import java.sql.PreparedStatement;
021: import java.sql.ResultSet;
022: import java.sql.ResultSetMetaData;
023: import java.sql.SQLException;
024: import java.sql.Savepoint;
025: import java.sql.Statement;
026: import java.util.ArrayList;
027: import java.util.Calendar;
028: import java.util.HashMap;
029: import java.util.Map;
030:
031: import org.h2.constant.ErrorCode;
032: import org.h2.constant.SysProperties;
033: import org.h2.jdbc.JdbcConnection;
034: import org.h2.test.TestAll;
035: import org.h2.test.TestBase;
036: import org.h2.test.db.TestScript;
037: import org.h2.test.synth.sql.RandomGen;
038: import org.h2.util.RandomUtils;
039:
040: /**
041: * A test that calls random methods with random parameters from JDBC objects.
042: * This is sometimes called 'Fuzz Testing'.
043: */
044: public class TestCrashAPI extends TestBase {
045: public static final Class[] INTERFACES = { Connection.class,
046: PreparedStatement.class, Statement.class, ResultSet.class,
047: ResultSetMetaData.class, Savepoint.class,
048: ParameterMetaData.class, Clob.class, Blob.class,
049: Array.class, CallableStatement.class };
050: private ArrayList objects = new ArrayList();
051: private HashMap classMethods = new HashMap();
052: private RandomGen random = new RandomGen(null);
053: private ArrayList statements = new ArrayList();
054: private int openCount;
055: private long callCount;
056: private static final String DIR = "synth";
057:
058: private void deleteDb() {
059: try {
060: deleteDb(baseDir + "/" + DIR, null);
061: } catch (Exception e) {
062: // ignore
063: }
064: }
065:
066: private Connection getConnection(int seed, boolean delete)
067: throws Exception {
068: openCount++;
069: if (delete) {
070: deleteDb();
071: }
072: // can not use FILE_LOCK=NO, otherwise something could be written into
073: // the database in the finalize method
074: String add = ""; // ";STORAGE=TEXT";
075:
076: // int testing;
077: // add = ";STORAGE=TEXT";
078: // if(openCount >= 32) {
079: // int test;
080: // Runtime.getRuntime().halt(0);
081: // System.exit(1);
082: // }
083: // add = ";LOG=2";
084: // System.out.println("now open " + openCount);
085: // add += ";TRACE_LEVEL_FILE=3";
086: // config.logMode = 2;
087: // }
088:
089: String url = getURL(DIR + "/crashApi" + seed, true) + add;
090:
091: // int test;
092: // url += ";DB_CLOSE_ON_EXIT=FALSE";
093: // int test;
094: // url += ";TRACE_LEVEL_FILE=3";
095:
096: Connection conn = null;
097: // System.gc();
098: conn = DriverManager.getConnection(url, "sa", "");
099: int len = random.getInt(50);
100: int start = random.getInt(statements.size() - len);
101: int end = start + len;
102: Statement stat = conn.createStatement();
103: stat.execute("SET LOCK_TIMEOUT 10");
104: stat.execute("SET WRITE_DELAY 0");
105: if (random.nextBoolean()) {
106: if (random.nextBoolean()) {
107: double g = random.nextGaussian();
108: int size = (int) Math.abs(10000 * g * g);
109: stat.execute("SET CACHE_SIZE " + size);
110: } else {
111: stat.execute("SET CACHE_SIZE 0");
112: }
113: }
114: stat.execute("SCRIPT NOPASSWORDS NOSETTINGS");
115: for (int i = start; i < end && i < statements.size(); i++) {
116: try {
117: stat.execute("SELECT * FROM TEST WHERE ID=1");
118: } catch (Throwable t) {
119: printIfBad(seed, -i, -1, t);
120: }
121: try {
122: stat.execute("SELECT * FROM TEST WHERE ID=1 OR ID=1");
123: } catch (Throwable t) {
124: printIfBad(seed, -i, -1, t);
125: }
126:
127: String sql = (String) statements.get(i);
128: try {
129: // if(openCount == 32) {
130: // int test;
131: // System.out.println("stop!");
132: // }
133: stat.execute(sql);
134: } catch (Throwable t) {
135: printIfBad(seed, -i, -1, t);
136: }
137: }
138: if (random.nextBoolean()) {
139: try {
140: conn.commit();
141: } catch (Throwable t) {
142: printIfBad(seed, 0, -1, t);
143: }
144: }
145: return conn;
146: }
147:
148: private void testOne(int seed) throws Exception {
149: printTime("seed: " + seed);
150: callCount = 0;
151: openCount = 0;
152: random = new RandomGen(null);
153: random.setSeed(seed);
154: Connection c1 = getConnection(seed, true);
155: Connection conn = null;
156: for (int i = 0; i < 2000; i++) {
157: // if(i % 10 == 0) {
158: // for(int j=0; j<objects.size(); j++) {
159: // System.out.print(objects.get(j));
160: // System.out.print(" ");
161: // }
162: // System.out.println();
163: // Thread.sleep(1);
164: // }
165:
166: if (objects.size() == 0) {
167: try {
168: conn = getConnection(seed, false);
169: } catch (SQLException e) {
170: if (e.getSQLState().equals("08004")) {
171: // Wrong user/password [08004]
172: try {
173: c1.createStatement().execute(
174: "SET PASSWORD ''");
175: } catch (Throwable t) {
176: // power off or so
177: break;
178: }
179: try {
180: conn = getConnection(seed, false);
181: } catch (Throwable t) {
182: printIfBad(seed, -i, -1, t);
183: }
184: } else if (e.getSQLState().equals("90098")) {
185: // The database has been closed
186: break;
187: } else {
188: printIfBad(seed, -i, -1, e);
189: }
190: }
191: objects.add(conn);
192: }
193: int objectId = random.getInt(objects.size());
194: if (random.getBoolean(1)) {
195: objects.remove(objectId);
196: continue;
197: }
198: if (random.getInt(2000) == 0 && conn != null) {
199: ((JdbcConnection) conn).setPowerOffCount(random
200: .getInt(50));
201: }
202: Object o = objects.get(objectId);
203: if (o == null) {
204: objects.remove(objectId);
205: continue;
206: }
207: Class in = getJdbcInterface(o);
208: ArrayList methods = (ArrayList) classMethods.get(in);
209: Method m = (Method) methods.get(random.getInt(methods
210: .size()));
211: Object o2 = callRandom(seed, i, objectId, o, m);
212: if (o2 != null) {
213: objects.add(o2);
214: }
215: }
216: try {
217: if (conn != null) {
218: conn.close();
219: }
220: c1.close();
221: } catch (Throwable t) {
222: printIfBad(seed, -101010, -1, t);
223: try {
224: deleteDb(null, "test");
225: } catch (Throwable t2) {
226: printIfBad(seed, -101010, -1, t2);
227: }
228: }
229: objects.clear();
230: }
231:
232: private void printError(int seed, int id, Throwable t) {
233: StringWriter writer = new StringWriter();
234: t.printStackTrace(new PrintWriter(writer));
235: String s = writer.toString();
236: TestBase.logError("new TestCrashAPI().init(test).testCase("
237: + seed + "); // Bug " + s.hashCode() + " id=" + id
238: + " callCount=" + callCount + " openCount=" + openCount
239: + " " + t.getMessage(), t);
240: }
241:
242: private Object callRandom(int seed, int id, int objectId, Object o,
243: Method m) throws Exception {
244: Class[] paramClasses = m.getParameterTypes();
245: Object[] params = new Object[paramClasses.length];
246: for (int i = 0; i < params.length; i++) {
247: params[i] = getRandomParam(id, paramClasses[i]);
248: }
249: Object result = null;
250: try {
251: callCount++;
252: result = m.invoke(o, params);
253: } catch (IllegalArgumentException e) {
254: TestBase.logError("error", e);
255: } catch (IllegalAccessException e) {
256: TestBase.logError("error", e);
257: } catch (InvocationTargetException e) {
258: Throwable t = e.getTargetException();
259: printIfBad(seed, id, objectId, t);
260: }
261: if (result == null) {
262: return null;
263: }
264: Class in = getJdbcInterface(result);
265: if (in == null) {
266: return null;
267: }
268: return result;
269: }
270:
271: private void printIfBad(int seed, int id, int objectId, Throwable t) {
272: if (t instanceof BatchUpdateException) {
273: // do nothing
274: } else if (t.getClass().getName().indexOf(
275: "SQLClientInfoException") >= 0) {
276: // do nothing
277: } else if (t instanceof SQLException) {
278: SQLException s = (SQLException) t;
279: int errorCode = s.getErrorCode();
280: if (errorCode == 0) {
281: printError(seed, id, s);
282: } else if (errorCode == ErrorCode.OBJECT_CLOSED) {
283: if (objectId >= 0) {
284: // TODO at least call a few more times after close - maybe
285: // there is still an error
286: objects.remove(objectId);
287: }
288: } else if (errorCode == ErrorCode.GENERAL_ERROR_1) {
289: // General error [HY000]
290: printError(seed, id, s);
291: }
292: } else {
293: printError(seed, id, t);
294: }
295: }
296:
297: private Object getRandomParam(int id, Class type) {
298: if (type == int.class) {
299: return new Integer(random.getRandomInt());
300: } else if (type == byte.class) {
301: return new Byte((byte) random.getRandomInt());
302: } else if (type == short.class) {
303: return new Short((short) random.getRandomInt());
304: } else if (type == long.class) {
305: return new Long(random.getRandomLong());
306: } else if (type == float.class) {
307: return new Float(random.getRandomDouble());
308: } else if (type == boolean.class) {
309: return new Boolean(random.nextBoolean());
310: } else if (type == double.class) {
311: return new Double(random.getRandomDouble());
312: } else if (type == String.class) {
313: if (random.getInt(10) == 0) {
314: return null;
315: } else {
316: int randomId = random.getInt(statements.size());
317: String sql = (String) statements.get(randomId);
318: if (random.getInt(10) == 0) {
319: sql = random.modify(sql);
320: }
321: return sql;
322: }
323: } else if (type == int[].class) {
324: // TODO test with 'shared' arrays (make sure database creates a
325: // copy)
326: return random.getIntArray();
327: } else if (type == java.io.Reader.class) {
328: return null;
329: } else if (type == java.sql.Array.class) {
330: return null;
331: } else if (type == byte[].class) {
332: // TODO test with 'shared' arrays (make sure database creates a
333: // copy)
334: return random.getByteArray();
335: } else if (type == Map.class) {
336: return null;
337: } else if (type == Object.class) {
338: return null;
339: } else if (type == java.sql.Date.class) {
340: return random.randomDate();
341: } else if (type == java.sql.Time.class) {
342: return random.randomTime();
343: } else if (type == java.sql.Timestamp.class) {
344: return random.randomTimestamp();
345: } else if (type == java.io.InputStream.class) {
346: return null;
347: } else if (type == String[].class) {
348: return null;
349: } else if (type == java.sql.Clob.class) {
350: return null;
351: } else if (type == java.sql.Blob.class) {
352: return null;
353: } else if (type == Savepoint.class) {
354: // TODO should use generated savepoints
355: return null;
356: } else if (type == Calendar.class) {
357: return Calendar.getInstance();
358: } else if (type == java.net.URL.class) {
359: return null;
360: } else if (type == java.math.BigDecimal.class) {
361: return new java.math.BigDecimal(""
362: + random.getRandomDouble());
363: } else if (type == java.sql.Ref.class) {
364: return null;
365: }
366: return null;
367: }
368:
369: private Class getJdbcInterface(Object o) {
370: Class[] list = o.getClass().getInterfaces();
371: for (int i = 0; i < list.length; i++) {
372: Class in = list[i];
373: if (classMethods.get(in) != null) {
374: return in;
375: }
376: }
377: return null;
378: }
379:
380: private void initMethods() {
381: for (int i = 0; i < INTERFACES.length; i++) {
382: Class inter = INTERFACES[i];
383: classMethods.put(inter, new ArrayList());
384: }
385: for (int i = 0; i < INTERFACES.length; i++) {
386: Class inter = INTERFACES[i];
387: ArrayList list = (ArrayList) classMethods.get(inter);
388: Method[] methods = inter.getMethods();
389: for (int j = 0; j < methods.length; j++) {
390: Method m = methods[j];
391: list.add(m);
392: }
393: }
394: }
395:
396: public TestBase init(TestAll conf) throws Exception {
397: super .init(conf);
398: if (config.mvcc || config.networked || config.logMode == 0) {
399: return this ;
400: }
401: baseDir = TestBase.getTestDir("crash");
402: startServerIfRequired();
403: TestScript script = new TestScript();
404: ArrayList add = script.getAllStatements(config,
405: "org/h2/test/test.in.txt");
406: initMethods();
407: Class.forName("org.h2.Driver");
408: statements.addAll(add);
409: return this ;
410: }
411:
412: public void testCase(int i) throws Exception {
413: int old = SysProperties.getMaxQueryTimeout();
414: String oldBaseDir = baseDir;
415: try {
416: System.setProperty(SysProperties.H2_MAX_QUERY_TIMEOUT,
417: "" + 10000);
418: baseDir = TestBase.getTestDir("crash");
419: testOne(i);
420: } finally {
421: baseDir = oldBaseDir;
422: System.setProperty(SysProperties.H2_MAX_QUERY_TIMEOUT, ""
423: + old);
424: }
425: }
426:
427: public void test() throws Exception {
428: if (config.mvcc || config.networked || config.logMode == 0) {
429: return;
430: }
431: int len = getSize(2, 6);
432: for (int i = 0; i < len; i++) {
433: int seed = RandomUtils.nextInt(Integer.MAX_VALUE);
434: testCase(seed);
435: deleteDb();
436: }
437: }
438:
439: }
|