001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: TestUtils.java,v 1.75.2.3 2008/01/07 15:14:34 cwl Exp $
007: */
008:
009: package com.sleepycat.je.util;
010:
011: import java.io.File;
012: import java.io.FileInputStream;
013: import java.io.FileOutputStream;
014: import java.io.FilenameFilter;
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.OutputStream;
018: import java.text.NumberFormat;
019: import java.util.Iterator;
020: import java.util.Random;
021:
022: import junit.framework.TestCase;
023:
024: import com.sleepycat.je.Cursor;
025: import com.sleepycat.je.Database;
026: import com.sleepycat.je.DatabaseException;
027: import com.sleepycat.je.DbInternal;
028: import com.sleepycat.je.DbTestProxy;
029: import com.sleepycat.je.Environment;
030: import com.sleepycat.je.EnvironmentConfig;
031: import com.sleepycat.je.StatsConfig;
032: import com.sleepycat.je.dbi.CursorImpl;
033: import com.sleepycat.je.dbi.EnvironmentImpl;
034: import com.sleepycat.je.dbi.INList;
035: import com.sleepycat.je.latch.LatchSupport;
036: import com.sleepycat.je.log.FileManager;
037: import com.sleepycat.je.tree.BIN;
038: import com.sleepycat.je.tree.ChildReference;
039: import com.sleepycat.je.tree.IN;
040: import com.sleepycat.je.tree.SearchResult;
041: import com.sleepycat.je.tree.Tree;
042: import com.sleepycat.je.tree.WithRootLatched;
043:
044: public class TestUtils {
045: public static String DEST_DIR = "testdestdir";
046: public static String NO_SYNC = "txnnosync";
047: public static String LONG_TEST = "longtest";
048:
049: public static final String LOG_FILE_NAME = "00000000.jdb";
050:
051: public static final StatsConfig FAST_STATS;
052:
053: static {
054: FAST_STATS = new StatsConfig();
055: FAST_STATS.setFast(true);
056: }
057:
058: private static final boolean DEBUG = true;
059: private static Random rnd = new Random();
060:
061: public void debugMsg(String message) {
062:
063: if (DEBUG) {
064: System.out.println(Thread.currentThread().toString() + " "
065: + message);
066: }
067: }
068:
069: static public void setRandomSeed(int seed) {
070:
071: rnd = new Random(seed);
072: }
073:
074: static public void generateRandomAlphaBytes(byte[] bytes) {
075:
076: byte[] aAndZ = "AZ".getBytes();
077: int range = aAndZ[1] - aAndZ[0] + 1;
078:
079: for (int i = 0; i < bytes.length; i++) {
080: bytes[i] = (byte) (rnd.nextInt(range) + aAndZ[0]);
081: }
082: }
083:
084: static public void checkLatchCount() {
085: TestCase.assertTrue(LatchSupport.countLatchesHeld() == 0);
086: }
087:
088: static public void printLatchCount(String msg) {
089: System.out.println(msg + " : "
090: + LatchSupport.countLatchesHeld());
091: }
092:
093: static public void printLatches(String msg) {
094: System.out.println(msg + " : ");
095: LatchSupport.dumpLatchesHeld();
096: }
097:
098: /**
099: * Generate a synthetic base 26 four byte alpha key from an int.
100: * The bytes of the key are between 'A' and 'Z', inclusive. 0 maps
101: * to 'AAAA', 1 to 'AAAB', etc.
102: */
103: static public int alphaKey(int i) {
104:
105: int ret = 0;
106: for (int j = 0; j < 4; j++) {
107: byte b = (byte) (i % 26);
108: ret <<= 8;
109: ret |= (b + 65);
110: i /= 26;
111: }
112:
113: return ret;
114: }
115:
116: /**
117: * Marshall an unsigned int (long) into a four byte buffer.
118: */
119: static public void putUnsignedInt(byte[] buf, long value) {
120:
121: int i = 0;
122: buf[i++] = (byte) (value >>> 0);
123: buf[i++] = (byte) (value >>> 8);
124: buf[i++] = (byte) (value >>> 16);
125: buf[i] = (byte) (value >>> 24);
126: }
127:
128: /**
129: * All flavors of removeLogFiles should check if the remove has been
130: * disabled. (Used for debugging, so that the tester can dump the
131: * log file.
132: */
133: private static boolean removeDisabled() {
134:
135: String doRemove = System.getProperty("removeLogFiles");
136: return ((doRemove != null) && doRemove
137: .equalsIgnoreCase("false"));
138: }
139:
140: /**
141: * Remove je log files from the home directory. Will be disabled
142: * if the unit test is run with -DremoveLogFiles=false
143: * @param msg prefix to append to error messages
144: * @param envFile environment directory
145: */
146: public static void removeLogFiles(String msg, File envFile,
147: boolean checkRemove) throws IOException {
148:
149: removeFiles(msg, envFile, FileManager.JE_SUFFIX, checkRemove);
150: }
151:
152: /**
153: * Remove files with this suffix from the je home directory
154: * @param msg prefix to append to error messages
155: * @param envFile environment directory
156: * @param suffix files with this suffix will be removed
157: */
158: public static void removeFiles(String msg, File envFile,
159: String suffix) throws IOException {
160:
161: removeFiles(msg, envFile, suffix, false);
162: }
163:
164: /**
165: * Remove files with this suffix from the je home directory
166: * @param msg prefix to append to error messages
167: * @param envFile environment directory
168: * @param suffix files with this suffix will be removed
169: * @param checkRemove if true, check the -DremoveLogFiles system
170: * property before removing.
171: */
172: public static void removeFiles(String msg, File envFile,
173: String suffix, boolean checkRemove) throws IOException {
174:
175: if (checkRemove && removeDisabled()) {
176: return;
177: }
178:
179: String[] suffixes = new String[] { suffix };
180: String[] names = FileManager.listFiles(envFile, suffixes);
181:
182: /* Clean up any target files in this directory. */
183: for (int i = 0; i < names.length; i++) {
184: File oldFile = new File(envFile, names[i]);
185: boolean done = oldFile.delete();
186: assert done : msg + " couldn't delete " + names[i]
187: + " out of " + names[names.length - 1];
188: oldFile = null;
189: }
190: }
191:
192: /**
193: * Remove files with the pattern indicated by the filename filter from the
194: * environment home directory.
195: * Note that BadFileFilter looks for this pattern: NNNNNNNN.bad.#
196: * InfoFileFilter looks for this pattern: je.info.#
197: * @param envFile environment directory
198: */
199: public static void removeFiles(File envFile, FilenameFilter filter)
200: throws IOException {
201:
202: if (removeDisabled()) {
203: return;
204: }
205:
206: File[] targetFiles = envFile.listFiles(filter);
207:
208: // Clean up any target files in this directory
209: for (int i = 0; i < targetFiles.length; i++) {
210: boolean done = targetFiles[i].delete();
211: if (!done) {
212: System.out.println("Warning, couldn't delete "
213: + targetFiles[i] + " out of "
214: + targetFiles[targetFiles.length - 1]);
215: }
216: }
217: }
218:
219: /**
220: * Copies all files in fromDir to toDir. Does not copy subdirectories.
221: */
222: public static void copyFiles(File fromDir, File toDir)
223: throws IOException {
224:
225: String[] names = fromDir.list();
226: if (names != null) {
227: for (int i = 0; i < names.length; i += 1) {
228: File fromFile = new File(fromDir, names[i]);
229: if (fromFile.isDirectory()) {
230: continue;
231: }
232: File toFile = new File(toDir, names[i]);
233: int len = (int) fromFile.length();
234: byte[] data = new byte[len];
235: FileInputStream fis = null;
236: FileOutputStream fos = null;
237: try {
238: fis = new FileInputStream(fromFile);
239: fos = new FileOutputStream(toFile);
240: fis.read(data);
241: fos.write(data);
242: } finally {
243: if (fis != null) {
244: fis.close();
245: }
246: if (fos != null) {
247: fos.close();
248: }
249: }
250: }
251: }
252: }
253:
254: /**
255: * Useful utility for generating byte arrays with a known order.
256: * Vary the length just to introduce more variability.
257: * @return a byte array of length val % 100 with the value of "val"
258: */
259: public static byte[] getTestArray(int val) {
260:
261: int length = val % 10;
262: length = length < 4 ? 4 : length;
263: byte[] test = new byte[length];
264: test[3] = (byte) ((val >>> 0) & 0xff);
265: test[2] = (byte) ((val >>> 8) & 0xff);
266: test[1] = (byte) ((val >>> 16) & 0xff);
267: test[0] = (byte) ((val >>> 24) & 0xff);
268: return test;
269: }
270:
271: /**
272: * Return the value of a test data array generated with getTestArray
273: * as an int
274: */
275: public static int getTestVal(byte[] testArray) {
276:
277: int val = 0;
278: val |= (testArray[3] & 0xff);
279: val |= ((testArray[2] & 0xff) << 8);
280: val |= ((testArray[1] & 0xff) << 16);
281: val |= ((testArray[0] & 0xff) << 24);
282: return val;
283: }
284:
285: /**
286: * @return length and data of a byte array, printed as decimal numbers
287: */
288: public static String dumpByteArray(byte[] b) {
289:
290: StringBuffer sb = new StringBuffer();
291: sb.append("<byteArray len = ");
292: sb.append(b.length);
293: sb.append(" data = \"");
294: for (int i = 0; i < b.length; i++) {
295: sb.append(b[i]).append(",");
296: }
297: sb.append("\"/>");
298: return sb.toString();
299: }
300:
301: /**
302: * @return a copy of the passed in byte array
303: */
304: public static byte[] byteArrayCopy(byte[] ba) {
305:
306: int len = ba.length;
307: byte[] ret = new byte[len];
308: System.arraycopy(ba, 0, ret, 0, len);
309: return ret;
310: }
311:
312: /*
313: * Check that the stored memory count for all INs on the inlist
314: * matches their computed count. The environment mem usage check
315: * may be run with assertions or not.
316: *
317: * In a multithreaded environment (or one with daemons running),
318: * you can't be sure that the cached size will equal the calculated size.
319: *
320: * Nodes, txns, and locks are all counted within the memory budget.
321: */
322: public static long validateNodeMemUsage(EnvironmentImpl envImpl,
323: boolean assertOnError) throws DatabaseException {
324:
325: long total = tallyNodeMemUsage(envImpl);
326: long nodeCacheUsage = envImpl.getMemoryBudget()
327: .getTreeMemoryUsage();
328: NumberFormat formatter = NumberFormat.getNumberInstance();
329: if (assertOnError) {
330: assert (total == nodeCacheUsage) : "calculatedTotal="
331: + formatter.format(total) + " envCacheUsage="
332: + formatter.format(nodeCacheUsage);
333: } else {
334: if (DEBUG) {
335: if (nodeCacheUsage != total) {
336: long diff = Math.abs(nodeCacheUsage - total);
337: if ((diff / nodeCacheUsage) > .05) {
338: System.out.println("calculatedTotal="
339: + formatter.format(total)
340: + " envCacheUsage="
341: + formatter.format(nodeCacheUsage));
342: }
343: }
344: }
345: }
346:
347: return nodeCacheUsage;
348: }
349:
350: public static long tallyNodeMemUsage(EnvironmentImpl envImpl)
351: throws DatabaseException {
352:
353: INList inList = envImpl.getInMemoryINs();
354: inList.latchMajor();
355: long total = 0;
356: try {
357: Iterator iter = inList.iterator();
358: while (iter.hasNext()) {
359: IN in = (IN) iter.next();
360: in.latch();
361: try {
362: assert in.verifyMemorySize() : "in nodeId="
363: + in.getNodeId() + ' '
364: + in.getClass().getName();
365: total += in.getInMemorySize();
366: } finally {
367: in.releaseLatch();
368: }
369: }
370: } finally {
371: inList.releaseMajorLatch();
372: }
373: return total;
374: }
375:
376: /**
377: * Called by each unit test to enforce isolation level settings specified
378: * in the isolationLevel system property. Other system properties or
379: * default settings may be applied in the future.
380: */
381: public static EnvironmentConfig initEnvConfig() {
382:
383: EnvironmentConfig config = new EnvironmentConfig();
384: String val = System.getProperty("isolationLevel");
385: if (val != null && val.length() > 0) {
386: if ("serializable".equals(val)) {
387: config.setTxnSerializableIsolation(true);
388: } else if ("readCommitted".equals(val)) {
389: DbInternal.setTxnReadCommitted(config, true);
390: } else {
391: throw new IllegalArgumentException(
392: "Unknown isolationLevel system property value: "
393: + val);
394: }
395: }
396: return config;
397: }
398:
399: /**
400: * If a unit test needs to override the isolation level, it should call
401: * this method after calling initEnvConfig.
402: */
403: public static void clearIsolationLevel(EnvironmentConfig config) {
404: DbInternal.setTxnReadCommitted(config, false);
405: config.setTxnSerializableIsolation(false);
406: }
407:
408: /**
409: * Loads the given resource relative to the given class, and copies it to
410: * log file zero in the given directory.
411: */
412: public static void loadLog(Class cls, String resourceName,
413: File envHome) throws IOException {
414:
415: File logFile = new File(envHome, LOG_FILE_NAME);
416: InputStream is = cls.getResourceAsStream(resourceName);
417: OutputStream os = new FileOutputStream(logFile);
418: byte[] buf = new byte[is.available()];
419: int len = is.read(buf);
420: if (buf.length != len) {
421: throw new IllegalStateException();
422: }
423: os.write(buf, 0, len);
424: is.close();
425: os.close();
426: }
427:
428: /**
429: * Logs the BIN at the cursor provisionally and the parent IN
430: * non-provisionally. Used to simulate a partial checkpoint or eviction.
431: */
432: public static void logBINAndIN(Environment env, Cursor cursor)
433: throws DatabaseException {
434:
435: BIN bin = getBIN(cursor);
436: Tree tree = bin.getDatabase().getTree();
437:
438: /* Log the BIN and update its parent entry. */
439: bin.latch();
440: SearchResult result = tree.getParentINForChildIN(bin, true,
441: true);
442: assert result.parent != null;
443: assert result.exactParentFound;
444: IN binParent = result.parent;
445: long binLsn = logIN(env, bin, true, binParent);
446: binParent.updateEntry(result.index, bin, binLsn);
447: result.parent.releaseLatch();
448:
449: /* Log the BIN parent and update its parent entry. */
450: binParent.latch();
451: result = tree.getParentINForChildIN(binParent, true, true);
452: IN inParent = null;
453: if (result.parent != null) {
454: result.parent.releaseLatch();
455: assert result.exactParentFound;
456: inParent = result.parent;
457: inParent.latch();
458: }
459: final long inLsn = logIN(env, binParent, false, null);
460: if (inParent != null) {
461: inParent.updateEntry(result.index, binParent, inLsn);
462: inParent.releaseLatch();
463: } else {
464: tree.withRootLatchedExclusive(new WithRootLatched() {
465: public IN doWork(ChildReference root)
466: throws DatabaseException {
467: root.setLsn(inLsn);
468: return null;
469: }
470: });
471: }
472: }
473:
474: /**
475: * Logs the given IN.
476: */
477: public static long logIN(Environment env, IN in,
478: boolean provisional, IN parent) throws DatabaseException {
479:
480: EnvironmentImpl envImpl = DbInternal.envGetEnvironmentImpl(env);
481: in.latch();
482: long lsn;
483: if (provisional) {
484: lsn = in.log(envImpl.getLogManager(), false, // allowDeltas
485: true, // isProvisional
486: false, // proactiveMigration
487: false, // backgroundIO
488: parent);// provisional parent
489: } else {
490: lsn = in.log(envImpl.getLogManager());
491: }
492: in.releaseLatch();
493: return lsn;
494: }
495:
496: /**
497: * Returns the parent IN of the given BIN.
498: */
499: public static IN getIN(BIN bin) throws DatabaseException {
500:
501: Tree tree = bin.getDatabase().getTree();
502: bin.latch();
503: SearchResult result = tree.getParentINForChildIN(bin, true,
504: true);
505: assert result.parent != null;
506: result.parent.releaseLatch();
507: assert result.exactParentFound;
508: return result.parent;
509: }
510:
511: /**
512: * Returns the target BIN for the given cursor.
513: */
514: public static BIN getBIN(Cursor cursor) throws DatabaseException {
515:
516: CursorImpl impl = DbTestProxy.dbcGetCursorImpl(cursor);
517: BIN bin = impl.getDupBIN();
518: if (bin == null) {
519: bin = impl.getBIN();
520: assert bin != null;
521: }
522: return bin;
523: }
524:
525: /**
526: * Assert if the tree is not this deep. Use to ensure that data setups
527: * are as expected.
528: */
529: public static boolean checkTreeDepth(Database db, int desiredDepth)
530: throws DatabaseException {
531:
532: Tree tree = DbInternal.dbGetDatabaseImpl(db).getTree();
533: IN rootIN = tree.getRootIN(false /* update generation */);
534: int level = 0;
535: if (rootIN != null) {
536: level = rootIN.getLevel() & IN.LEVEL_MASK;
537: rootIN.releaseLatch();
538: }
539:
540: return (desiredDepth == level);
541: }
542:
543: /**
544: * @return true if long running tests are enabled.
545: */
546: static public boolean runLongTests() {
547: String longTestProp = System.getProperty(TestUtils.LONG_TEST);
548: if ((longTestProp != null)
549: && longTestProp.equalsIgnoreCase("true")) {
550: return true;
551: } else {
552: return false;
553: }
554: }
555:
556: /**
557: * Skip over the JE version number at the start of the exception
558: * message for tests which are looking for a specific message.
559: */
560: public static String skipVersion(Exception e) {
561: int versionHeaderLen = DatabaseException.getVersionHeader()
562: .length();
563: return (e.getMessage().substring(versionHeaderLen));
564: }
565: }
|