1 8 9 package com.sleepycat.je.recovery; 10 11 import java.io.File ; 12 import java.io.IOException ; 13 import java.util.ArrayList ; 14 import java.util.Arrays ; 15 import java.util.Comparator ; 16 import java.util.HashMap ; 17 import java.util.HashSet ; 18 import java.util.Hashtable ; 19 import java.util.Iterator ; 20 import java.util.List ; 21 import java.util.Map ; 22 import java.util.Set ; 23 24 import junit.framework.TestCase; 25 26 import com.sleepycat.je.CheckpointConfig; 27 import com.sleepycat.je.Cursor; 28 import com.sleepycat.je.Database; 29 import com.sleepycat.je.DatabaseConfig; 30 import com.sleepycat.je.DatabaseEntry; 31 import com.sleepycat.je.DatabaseException; 32 import com.sleepycat.je.DbInternal; 33 import com.sleepycat.je.Environment; 34 import com.sleepycat.je.EnvironmentConfig; 35 import com.sleepycat.je.LockMode; 36 import com.sleepycat.je.OperationStatus; 37 import com.sleepycat.je.Transaction; 38 import com.sleepycat.je.XAEnvironment; 39 import com.sleepycat.je.config.EnvironmentParams; 40 import com.sleepycat.je.dbi.EnvironmentImpl; 41 import com.sleepycat.je.log.FileManager; 42 import com.sleepycat.je.tree.Key; 43 import com.sleepycat.je.util.TestUtils; 44 45 public class RecoveryTestBase extends TestCase { 46 private static final boolean DEBUG = false; 47 48 protected static final int NUM_RECS = 257; 49 protected static final int N_DUPLICATES_PER_KEY = 28; 50 protected static final int NUM_DBS = 3; 51 52 protected static final String DB_NAME = "testDb"; 53 54 protected File envHome; 55 protected Environment env; 56 protected Database[] dbs; 57 protected EnvironmentConfig envConfig; 58 protected CheckpointConfig forceConfig; 59 protected Comparator btreeComparisonFunction = null; 60 61 public RecoveryTestBase() { 62 init(); 63 } 64 65 public RecoveryTestBase(boolean reduceMemory) { 66 init(); 67 envConfig.setConfigParam(EnvironmentParams.MAX_MEMORY.getName(), 68 new Long (1 << 24).toString()); 69 } 70 71 private void init() { 72 envHome = new File (System.getProperty(TestUtils.DEST_DIR)); 73 Key.DUMP_BINARY = true; 74 envConfig = TestUtils.initEnvConfig(); 75 forceConfig = new CheckpointConfig(); 76 forceConfig.setForce(true); 77 } 78 79 public void setUp() 80 throws IOException , DatabaseException { 81 82 TestUtils.removeFiles("Setup", envHome, FileManager.JE_SUFFIX); 83 } 84 85 public void tearDown() 86 throws IOException , DatabaseException { 87 88 if (env != null) { 89 try { 90 env.close(); 91 } catch (DatabaseException E) { 92 } 93 } 94 env = null; 95 dbs = null; 96 envConfig = null; 97 forceConfig = null; 98 101 } 102 103 108 protected void createEnv(int fileSize, boolean runCheckpointDaemon) 109 throws DatabaseException { 110 111 createEnvInternal(fileSize, runCheckpointDaemon, false); 112 } 113 114 protected void createXAEnv(int fileSize, boolean runCheckpointDaemon) 115 throws DatabaseException { 116 117 createEnvInternal(fileSize, runCheckpointDaemon, true); 118 } 119 120 private void createEnvInternal(int fileSize, 121 boolean runCheckpointDaemon, 122 boolean createXAEnv) 123 throws DatabaseException { 124 125 126 DbInternal.disableParameterValidation(envConfig); 127 envConfig.setTransactional(true); 128 envConfig.setAllowCreate(true); 129 envConfig.setTxnNoSync(Boolean.getBoolean(TestUtils.NO_SYNC)); 130 envConfig. 131 setConfigParam(EnvironmentParams.LOG_FILE_MAX.getName(), 132 Integer.toString(fileSize)); 133 envConfig.setConfigParam(EnvironmentParams.ENV_CHECK_LEAKS.getName(), 134 "false"); 135 envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(), "6"); 136 envConfig.setConfigParam(EnvironmentParams.ENV_RUN_CLEANER.getName(), 137 "false"); 138 envConfig.setConfigParam(EnvironmentParams.ENV_RUN_EVICTOR.getName(), 139 "false"); 140 141 if (!runCheckpointDaemon) { 142 envConfig.setConfigParam 143 (EnvironmentParams.ENV_RUN_CHECKPOINTER.getName(), "false"); 144 } 145 setExtraProperties(); 146 if (createXAEnv) { 147 env = new XAEnvironment(envHome, envConfig); 148 } else { 149 env = new Environment(envHome, envConfig); 150 } 151 } 152 153 156 protected void setExtraProperties() 157 throws DatabaseException { 158 159 } 160 161 164 protected void createDbs(Transaction txn, int numDbs) 165 throws DatabaseException { 166 167 168 dbs = new Database[numDbs]; 169 170 DatabaseConfig dbConfig = new DatabaseConfig(); 171 if (btreeComparisonFunction != null) { 172 dbConfig.setBtreeComparator(btreeComparisonFunction.getClass()); 173 } 174 dbConfig.setTransactional(true); 175 dbConfig.setAllowCreate(true); 176 dbConfig.setSortedDuplicates(true); 177 for (int i = 0; i < numDbs; i++) { 178 dbs[i] = env.openDatabase(txn, DB_NAME + i, dbConfig); 179 } 180 } 181 182 185 protected void createEnvAndDbs(int fileSize, 186 boolean runCheckpointerDaemon, 187 int numDbs) 188 throws DatabaseException { 189 190 createEnvAndDbsInternal(fileSize, runCheckpointerDaemon, 191 numDbs, false); 192 } 193 194 protected void createXAEnvAndDbs(int fileSize, 195 boolean runCheckpointerDaemon, 196 int numDbs) 197 throws DatabaseException { 198 199 createEnvAndDbsInternal(fileSize, runCheckpointerDaemon, 200 numDbs, true); 201 } 202 203 protected void createEnvAndDbsInternal(int fileSize, 204 boolean runCheckpointerDaemon, 205 int numDbs, 206 boolean createXAEnv) 207 throws DatabaseException { 208 209 createEnvInternal(fileSize, runCheckpointerDaemon, createXAEnv); 210 Transaction txn = env.beginTransaction(null, null); 211 createDbs(txn, numDbs); 212 txn.commit(); 213 } 214 215 218 protected void closeEnv() 219 throws DatabaseException { 220 221 TestUtils.validateNodeMemUsage(DbInternal.envGetEnvironmentImpl(env), 222 false); 223 224 225 if (dbs != null) { 226 for (int i = 0; i < dbs.length; i++) { 227 if (dbs[i] != null) { 228 dbs[i].close(); 229 } 230 } 231 } 232 forceCloseEnvOnly(); 233 } 234 235 236 protected void forceCloseEnvOnly() 237 throws DatabaseException { 238 239 240 DbInternal.envGetEnvironmentImpl(env).close(false); 241 env = null; 242 } 243 244 248 protected List recoverAndVerify(Hashtable expectedData, int numDbs) 249 throws DatabaseException { 250 251 return recoverAndVerifyInternal(expectedData, numDbs, 252 false, false); } 255 256 protected List recoverROAndVerify(Hashtable expectedData, int numDbs) 257 throws DatabaseException { 258 259 return recoverAndVerifyInternal(expectedData, numDbs, 260 false, true); } 263 264 268 protected List xaRecoverAndVerify(Hashtable expectedData, int numDbs) 269 throws DatabaseException { 270 271 return recoverAndVerifyInternal(expectedData, numDbs, 272 true, false); } 275 276 private List recoverAndVerifyInternal(Hashtable expectedData, 277 int numDbs, 278 boolean createXAEnv, 279 boolean readOnlyMode) 280 throws DatabaseException { 281 282 List infoList = recoverOnlyInternal(numDbs, createXAEnv, readOnlyMode); 283 verifyData(expectedData, numDbs); 284 TestUtils.validateNodeMemUsage(DbInternal.envGetEnvironmentImpl(env), 285 false); 286 287 DbInternal.envGetEnvironmentImpl(env).close(false); 288 env = new Environment(envHome, getRecoveryConfig(readOnlyMode)); 289 EnvironmentImpl envImpl = 290 DbInternal.envGetEnvironmentImpl(env); 291 infoList.add(envImpl.getLastRecoveryInfo()); 292 verifyData(expectedData, numDbs); 293 TestUtils.validateNodeMemUsage(envImpl, false); 294 env.close(); 295 return infoList; 296 } 297 298 private EnvironmentConfig getRecoveryConfig(boolean readOnlyMode) { 299 EnvironmentConfig recoveryConfig = TestUtils.initEnvConfig(); 300 recoveryConfig.setConfigParam 301 (EnvironmentParams.NODE_MAX.getName(), "6"); 302 recoveryConfig.setConfigParam(EnvironmentParams.MAX_MEMORY.getName(), 303 new Long (1 << 24).toString()); 304 recoveryConfig.setReadOnly(readOnlyMode); 305 306 310 recoveryConfig.setConfigParam 311 (EnvironmentParams.ENV_CHECK_LEAKS.getName(), "false"); 312 recoveryConfig.setConfigParam 313 (EnvironmentParams.ENV_RUN_CLEANER.getName(), "false"); 314 recoveryConfig.setConfigParam 315 (EnvironmentParams.ENV_RUN_EVICTOR.getName(), "false"); 316 317 if (DEBUG) { 318 recoveryConfig.setConfigParam 319 (EnvironmentParams.JE_LOGGING_FILE.getName(), "true"); 320 recoveryConfig.setConfigParam 321 (EnvironmentParams.JE_LOGGING_LEVEL.getName(), "FINE"); 322 } 323 324 recoveryConfig.setTransactional(true); 325 return recoveryConfig; 326 } 327 328 protected List recoverOnly(int numDbs) 329 throws DatabaseException { 330 331 return recoverOnlyInternal(numDbs, 332 false, false); } 335 336 protected List xaRecoverOnly(int numDbs) 337 throws DatabaseException { 338 339 return recoverOnlyInternal(numDbs, 340 true, false); } 343 344 private List recoverOnlyInternal(int numDbs, 345 boolean createXAEnv, 346 boolean readOnlyMode) 347 throws DatabaseException { 348 349 List infoList = new ArrayList (); 350 351 352 if (createXAEnv) { 353 env = new XAEnvironment(envHome, getRecoveryConfig(readOnlyMode)); 354 } else { 355 env = new Environment(envHome, getRecoveryConfig(readOnlyMode)); 356 } 357 TestUtils.validateNodeMemUsage(DbInternal.envGetEnvironmentImpl(env), 358 false); 359 360 infoList.add 361 (DbInternal.envGetEnvironmentImpl(env).getLastRecoveryInfo()); 362 363 return infoList; 364 } 365 366 370 protected void verifyData(Hashtable expectedData, int numDbs) 371 throws DatabaseException { 372 373 verifyData(expectedData, true, numDbs); 374 } 375 376 380 protected void verifyData(Hashtable expectedData, 381 boolean checkInList, 382 int numDbs) 383 throws DatabaseException { 384 385 verifyData(expectedData, checkInList, 0, numDbs); 386 } 387 388 protected void verifyData(Hashtable expectedData, 389 boolean checkInList, 390 int startDb, 391 int endDb) 392 throws DatabaseException { 393 394 395 if (checkInList) { 396 assertTrue(env.verify(null, System.err)); 397 } else { 398 assertTrue(env.verify(null, System.err)); 399 } 400 401 407 Map useData = new Hashtable (); 408 Iterator iter = expectedData.entrySet().iterator(); 409 while (iter.hasNext()) { 410 Map.Entry entry = (Map.Entry ) iter.next(); 411 useData.put(entry.getKey(), ((HashSet ) entry.getValue()).clone()); 412 } 413 414 415 Map countMap = generateCountMap(expectedData); 416 417 418 DatabaseConfig dbConfig = new DatabaseConfig(); 419 if (btreeComparisonFunction != null) { 420 dbConfig.setBtreeComparator(btreeComparisonFunction.getClass()); 421 } 422 dbConfig.setTransactional(env.getConfig().getTransactional()); 423 dbConfig.setSortedDuplicates(true); 424 dbConfig.setReadOnly(true); 425 for (int d = startDb; d < endDb; d++) { 426 Database checkDb = env.openDatabase(null, DB_NAME + d, 427 dbConfig); 428 Cursor myCursor = checkDb.openCursor(null, null); 429 DatabaseEntry key = new DatabaseEntry(); 430 DatabaseEntry data = new DatabaseEntry(); 431 OperationStatus status = 432 myCursor.getFirst(key, data, LockMode.DEFAULT); 433 DbInternal.envGetEnvironmentImpl(env).verifyCursors(); 434 int numSeen = 0; 435 436 while (status == OperationStatus.SUCCESS) { 437 438 439 removeExpectedData(useData, d, key, data, true); 440 441 442 int count = myCursor.count(); 443 assertEquals("Count not right for key " + 444 TestUtils.dumpByteArray(key.getData()), 445 getExpectedCount(countMap, d, key), count); 446 447 status = myCursor.getNext(key, data, LockMode.DEFAULT); 448 numSeen++; 449 } 450 451 myCursor.close(); 452 453 454 if (DEBUG) { 455 System.out.println("Finished db" + d + " numSeen=" +numSeen); 456 dumpExpected(useData); 457 } 458 checkDb.close(); 459 } 460 461 assertEquals(0, useData.size()); 462 } 463 464 468 private Map generateCountMap(Map expectedData) { 469 470 Map countMap = new HashMap (); 471 472 Iterator iter = expectedData.values().iterator(); 473 while (iter.hasNext()) { 474 Set dataSet = (Set ) iter.next(); 475 Iterator dataIter = dataSet.iterator(); 476 while (dataIter.hasNext()) { 477 TestData t = (TestData) dataIter.next(); 478 TestData countKey = new TestData(t.dbNum, t.key); 479 Integer count = (Integer ) countMap.get(countKey); 480 if (count == null) { 481 countMap.put(countKey, new Integer (1)); 482 } else { 483 countMap.put(countKey, new Integer (count.intValue()+1)); 484 } 485 } 486 } 487 return countMap; 488 } 489 490 493 private int getExpectedCount(Map countMap, 494 int whichDb, 495 DatabaseEntry key) { 496 return ((Integer ) 497 countMap.get(new TestData(whichDb, key.getData()))).intValue(); 498 } 499 500 503 protected void insertData(Transaction txn, 504 int startVal, 505 int endVal, 506 Map expectedData, 507 int nDuplicatesPerKey, 508 boolean addToExpectedData, 509 int numDbs) 510 throws DatabaseException { 511 512 insertData(txn, startVal, endVal, expectedData, 513 nDuplicatesPerKey, false, addToExpectedData, 514 0, numDbs); 515 } 516 517 protected void insertData(Transaction txn, 518 int startVal, 519 int endVal, 520 Map expectedData, 521 int nDuplicatesPerKey, 522 boolean addToExpectedData, 523 int startDb, 524 int endDb) 525 throws DatabaseException { 526 527 insertData(txn, startVal, endVal, expectedData, 528 nDuplicatesPerKey, false, addToExpectedData, 529 startDb, endDb); 530 } 531 532 537 protected void insertData(Transaction txn, 538 int startVal, 539 int endVal, 540 Map expectedData, 541 int nDuplicatesPerKey, 542 boolean toggle, 543 boolean addToExpectedData, 544 int numDbs) 545 throws DatabaseException { 546 547 insertData(txn, startVal, endVal, expectedData, nDuplicatesPerKey, 548 toggle, addToExpectedData, 0, numDbs); 549 } 550 551 556 protected void insertData(Transaction txn, 557 int startVal, 558 int endVal, 559 Map expectedData, 560 int nDuplicatesPerKey, 561 boolean toggle, 562 boolean addToExpectedData, 563 int startDb, 564 int endDb) 565 throws DatabaseException { 566 567 Cursor[] cursors = getCursors(txn, startDb, endDb); 568 569 570 assertTrue(endVal - startVal > -1); 571 572 573 int incVal = (toggle) ? 2 : 1; 574 if (startVal < endVal) { 575 for (int i = startVal; i <= endVal; i += incVal) { 576 insertOneRecord(cursors, i, expectedData, 577 nDuplicatesPerKey, addToExpectedData); 578 } 579 } else { 580 for (int i = startVal; i >= endVal; i -= incVal) { 581 insertOneRecord(cursors, i, expectedData, 582 nDuplicatesPerKey, addToExpectedData); 583 } 584 } 585 586 for (int i = 0; i < cursors.length; i++) { 587 cursors[i].close(); 588 } 589 } 590 591 596 protected void addExpectedData(Map expectedData, 597 int dbNum, 598 DatabaseEntry key, 599 DatabaseEntry data, 600 boolean expectCommit) { 601 if (expectCommit) { 602 TestData keyTestData = new TestData(dbNum, key, null); 603 Set dataSet = (Set ) expectedData.get(keyTestData); 604 if (dataSet == null) { 605 dataSet = new HashSet (); 606 expectedData.put(keyTestData, dataSet); 607 } 608 609 dataSet.add(new TestData(dbNum, key, data)); 610 } 611 } 612 613 616 private void removeExpectedData(Map expectedData, 617 int dbNum, 618 DatabaseEntry key, 619 DatabaseEntry data, 620 boolean expectCommit) { 621 if (expectCommit) { 622 TestData keyTestData = new TestData(dbNum, key, null); 623 Set dataSet = (Set ) expectedData.get(keyTestData); 624 assertTrue("Should be a data set for " + keyTestData, 625 (dataSet != null)); 626 assertTrue("Should be able to remove key " + key + 627 " from expected data ", 628 dataSet.remove(new TestData(dbNum, key, data))); 629 if (dataSet.size() == 0) { 630 expectedData.remove(keyTestData); 631 } 632 } 633 } 634 635 638 private Cursor[] getCursors(Transaction txn, int startDb, int endDb) 639 throws DatabaseException { 640 641 Cursor[] cursors = new Cursor[endDb - startDb]; 642 for (int i = 0; i < cursors.length; i++) { 643 cursors[i] = dbs[startDb + i].openCursor(txn, null); 644 } 645 return cursors; 646 } 647 648 651 private void insertOneRecord(Cursor[] cursors, 652 int val, 653 Map expectedData, 654 int nDuplicatesPerKey, 655 boolean expectCommit) 656 throws DatabaseException { 657 658 DatabaseEntry key = new DatabaseEntry(); 659 DatabaseEntry data = new DatabaseEntry(); 660 661 for (int c = 0; c < cursors.length; c++) { 662 663 int testVal = val + c; 664 byte[] keyData = TestUtils.getTestArray(testVal); 665 byte[] dataData = TestUtils.byteArrayCopy(keyData); 666 key.setData(keyData); 667 for (int d = 0; d < nDuplicatesPerKey; d++) { 668 dataData = TestUtils.byteArrayCopy(dataData); 669 dataData[1]++; 670 data.setData(dataData); 671 672 assertEquals("Insertion of key " + 673 TestUtils.dumpByteArray(keyData), 674 OperationStatus.SUCCESS, 675 cursors[c].putNoDupData(key, data)); 676 677 addExpectedData(expectedData, c, key, data, expectCommit); 678 } 679 } 680 } 681 682 685 protected void deleteData(Transaction txn, Map expectedData, 686 boolean all, boolean expectCommit, int numDbs) 687 throws DatabaseException { 688 689 Cursor[] cursors = getCursors(txn, 0, numDbs); 690 DatabaseEntry key = new DatabaseEntry(); 691 DatabaseEntry data = new DatabaseEntry(); 692 693 for (int d = 0; d < cursors.length; d++) { 694 OperationStatus status = 695 cursors[d].getFirst(key, data, LockMode.DEFAULT); 696 boolean toggle = true; 697 int deleteCount = 0; 698 while (status == OperationStatus.SUCCESS) { 699 if (toggle) { 700 removeExpectedData(expectedData, d, key, data, 701 expectCommit); 702 assertEquals(OperationStatus.SUCCESS, cursors[d].delete()); 703 deleteCount++; 704 toggle = all; 705 } else { 706 toggle = true; 707 } 708 status = cursors[d].getNext(key, data, LockMode.DEFAULT); 709 } 710 711 assertTrue(deleteCount > 0); 712 } 713 714 for (int i = 0; i < cursors.length; i++) { 715 cursors[i].close(); 716 } 717 } 718 719 728 protected void modifyData(Transaction txn, int endVal, 729 Map expectedData, int increment, 730 boolean expectCommit, int numDbs) 731 throws DatabaseException { 732 733 Cursor[] cursors = getCursors(txn, 0, numDbs); 734 DatabaseEntry key = new DatabaseEntry(); 735 DatabaseEntry data = new DatabaseEntry(); 736 737 for (int d = 0; d < cursors.length; d++) { 738 739 740 OperationStatus status = 741 cursors[d].getFirst(key, data, LockMode.DEFAULT); 742 743 744 int modCount = 0; 745 int keyVal = TestUtils.getTestVal(key.getData()); 746 while ((status == OperationStatus.SUCCESS) && (keyVal <= endVal)) { 747 748 749 removeExpectedData(expectedData, d, key, data, expectCommit); 750 data.setData(TestUtils.getTestArray(keyVal + increment)); 751 cursors[d].delete(); 752 cursors[d].put(key, data); 753 addExpectedData(expectedData, d, key, data, expectCommit); 754 modCount++; 755 756 status = cursors[d].getNext(key, data, LockMode.DEFAULT); 757 758 if (status == OperationStatus.SUCCESS) { 759 keyVal = TestUtils.getTestVal(key.getData()); 760 } 761 } 762 763 assertTrue(modCount > 0); 764 } 765 766 for (int i = 0; i < cursors.length; i++) { 767 cursors[i].close(); 768 } 769 } 770 771 772 775 protected void dumpData(int numDbs) 776 throws DatabaseException { 777 778 DatabaseConfig dbConfig = new DatabaseConfig(); 779 if (btreeComparisonFunction != null) { 780 dbConfig.setBtreeComparator(btreeComparisonFunction.getClass()); 781 } 782 dbConfig.setSortedDuplicates(true); 783 dbConfig.setTransactional(true); 784 for (int d = 0; d < numDbs; d++) { 785 Database checkDb = env.openDatabase(null, DB_NAME + d, dbConfig); 786 Cursor myCursor = checkDb.openCursor(null, null); 787 DatabaseEntry key = new DatabaseEntry(); 788 DatabaseEntry data = new DatabaseEntry(); 789 790 OperationStatus status = 791 myCursor.getFirst(key, data, LockMode.DEFAULT); 792 while (status == OperationStatus.SUCCESS) { 793 System.out.println("Database " + d + 794 " seen = " + 795 800 TestUtils.dumpByteArray(key.getData()) + 801 "/" + 802 TestUtils.dumpByteArray(data.getData())); 803 status = myCursor.getNext(key, data, LockMode.DEFAULT); 804 } 805 myCursor.close(); 806 } 807 } 808 809 812 protected void dumpExpected(Map expectedData) 813 throws DatabaseException { 814 System.out.println("Expected = " ); 815 Iterator iter = expectedData.values().iterator(); 816 while (iter.hasNext()) { 817 Set dataSet = (Set ) iter.next(); 818 Iterator dataIter = dataSet.iterator(); 819 while (dataIter.hasNext()) { 820 TestData t = (TestData) dataIter.next(); 821 System.out.println(t); 822 } 823 } 824 } 825 826 protected class TestData { 827 public int dbNum; 828 public byte[] key; 829 public byte[] data; 830 831 TestData(int dbNum, DatabaseEntry keyDbt, DatabaseEntry dataDbt) { 832 this.dbNum = dbNum; 833 key = keyDbt.getData(); 834 if (dataDbt == null) { 835 dataDbt = new DatabaseEntry(); 836 dataDbt.setData(new byte[1]); 837 } 838 data = dataDbt.getData(); 839 } 840 841 TestData(int dbNum, byte[] key) { 842 this.dbNum = dbNum; 843 this.key = key; 844 } 845 846 public boolean equals(Object o ) { 847 if (this == o) 848 return true; 849 if (!(o instanceof TestData)) 850 return false; 851 852 TestData other = (TestData) o; 853 if ((dbNum == other.dbNum) && 854 Arrays.equals(key, other.key) && 855 Arrays.equals(data, other.data)) { 856 return true; 857 } else 858 return false; 859 } 860 861 public String toString() { 862 if (data == null) { 863 return "db=" + dbNum + 864 " k=" + TestUtils.dumpByteArray(key); 865 } else { 866 return "db=" + dbNum + 867 " k=" + TestUtils.dumpByteArray(key) + 868 " d=" + TestUtils.dumpByteArray(data); 869 } 870 } 871 872 public int hashCode() { 873 return toString().hashCode(); 874 } 875 } 876 } 877 | Popular Tags |