package org.basex.data; import static org.junit.Assert.*; import java.io.*; import java.lang.reflect.*; import org.basex.*; import org.basex.build.*; import org.basex.core.*; import org.basex.core.cmd.*; import org.basex.io.*; import org.basex.io.random.*; import org.basex.util.*; import org.junit.*; import org.junit.Test; /** * This class tests the update functionality of the block storage. * * @author BaseX Team 2005-17, BSD License * @author Tim Petrowsky */ public final class DiskTableTest extends SandboxTest { /** Test file we do updates with. */ private static final String TESTFILE = "src/test/resources/xmark.xml"; /** BlockStorage. */ private TableDiskAccess tda; /** Data reference. */ private Data data; /** Test file size. */ private int size; /** Starting storage. */ private byte[] storage; /** Expected blocks in file. */ private int blocks; /** Nodes per block. */ private int nodes; /** * Initializes the test class. */ @BeforeClass public static void setUpBeforeClass() { set(MainOptions.TEXTINDEX, false); set(MainOptions.ATTRINDEX, false); } /** * Loads the JUnitTest database. * @throws Exception exception */ @Before public void setUp() throws Exception { final Parser parser = Parser.xmlParser(IO.get(TESTFILE)); data = new DiskBuilder(NAME, parser, context.soptions, context.options).build(); size = data.meta.size; data.close(); tda = new TableDiskAccess(data.meta, true); final int bc = size * (1 << IO.NODEPOWER); storage = new byte[bc]; for(int i = 0; i < bc; ++i) { storage[i] = (byte) tda.read1(i >> IO.NODEPOWER, i % (1 << IO.NODEPOWER)); } nodes = IO.BLOCKSIZE >>> IO.NODEPOWER; blocks = (int) Math.ceil((double) size / nodes); } /** * Drops the JUnitTest database. * @throws Exception exception */ @After public void tearDown() throws Exception { if(tda != null) tda.close(); DropDB.drop(NAME, context.soptions); } /** * Closes and reloads storage. */ private void closeAndReload() { try { tda.close(); tda = new TableDiskAccess(data.meta, true); } catch(final IOException ex) { fail(Util.message(ex)); } } /** * Compares old with new entries. * @param startNodeNumber first old entry to compare * @param currentNodeNumber first new entry to compare * @param count number of entries to compare */ private void assertEntrysEqual(final int startNodeNumber, final int currentNodeNumber, final int count) { final int startOffset = startNodeNumber << IO.NODEPOWER; final int currentOffset = currentNodeNumber << IO.NODEPOWER; for(int i = 0; i < count << IO.NODEPOWER; ++i) { final int startByteNum = startOffset + i; final int currentByteNum = currentOffset + i; final byte startByte = storage[startByteNum]; final byte currentByte = (byte) tda.read1(currentByteNum >> IO.NODEPOWER, currentByteNum % (1 << IO.NODEPOWER)); assertEquals("Old entry " + (startByteNum >> IO.NODEPOWER) + " (byte " + startByteNum % (1 << IO.NODEPOWER) + ") and new entry " + (currentByteNum >> IO.NODEPOWER) + " (byte " + currentByteNum % (1 << IO.NODEPOWER) + ')', startByte, currentByte); } } /** * Tests size of file. */ @Test public void size() { assertEquals("Testfile size changed!", size, tdaSize()); assertTrue("Need at least 3 blocks for testing!", blocks > 2); assertEquals("Unexpected number of blocks!", blocks, tdaBlocks()); closeAndReload(); assertEquals("Testfile size changed!", size, tdaSize()); assertTrue("Need at least 3 blocks for testing!", blocks > 2); assertEquals("Unexpected number of blocks!", blocks, tdaBlocks()); } /** * Returns the number of block entries. * @return number of entries */ private int tdaSize() { try { final Field f = tda.getClass().getSuperclass().getDeclaredField("meta"); f.setAccessible(true); return ((MetaData) f.get(tda)).size; } catch(final Exception ex) { Util.stack(ex); return 0; } } /** * Returns the number of blocks. * @return number of blocks */ private int tdaBlocks() { try { final Field f = tda.getClass().getDeclaredField("used"); f.setAccessible(true); return f.getInt(tda); } catch(final Exception ex) { Util.stack(ex); return 0; } } /** * Tests delete. */ @Test public void deleteOneNode() { tda.delete(3, 1); assertEquals("One node deleted => size-1", size - 1, tdaSize()); assertEntrysEqual(0, 0, 3); assertEntrysEqual(4, 3, size - 4); closeAndReload(); assertEquals("One node deleted => size-1", size - 1, tdaSize()); assertEntrysEqual(0, 0, 3); assertEntrysEqual(4, 3, size - 4); } /** * Tests delete at beginning. */ @Test public void deleteAtBeginning() { tda.delete(0, 3); assertEquals("Three nodes deleted => size-3", size - 3, tdaSize()); assertEntrysEqual(3, 0, size - 3); closeAndReload(); assertEquals("Three nodes deleted => size-3", size - 3, tdaSize()); assertEntrysEqual(3, 0, size - 3); } /** * Tests delete at end. */ @Test public void deleteAtEnd() { tda.delete(size - 3, 3); assertEquals("Three nodes deleted => size-3", size - 3, tdaSize()); assertEntrysEqual(0, 0, size - 3); closeAndReload(); assertEquals("Three nodes deleted => size-3", size - 3, tdaSize()); assertEntrysEqual(0, 0, size - 3); } /** * Deletes first block. */ @Test public void deleteFirstBlock() { tda.delete(0, nodes); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(nodes, 0, size - nodes); closeAndReload(); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(nodes, 0, size - nodes); } /** * Deletes the second block. */ @Test public void deleteSecondBlock() { tda.delete(nodes, nodes); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes); assertEntrysEqual(2 * nodes, nodes, size - 2 * nodes); closeAndReload(); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes); assertEntrysEqual(2 * nodes, nodes, size - 2 * nodes); } /** * Deletes the last block. */ @Test public void deleteLastBlock() { tda.delete(size / nodes * nodes, size % nodes); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes - size % nodes); closeAndReload(); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes - size % nodes); } /** * Deletes the second block with some surrounding nodes. */ @Test public void deleteSecondBlockAndSurroundingNodes() { tda.delete(nodes - 1, nodes + 2); assertEquals(size - 2 - nodes, tdaSize()); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes - 1); assertEntrysEqual(2 * nodes + 1, nodes - 1, size - 2 * nodes - 1); closeAndReload(); assertEquals(size - 2 - nodes, tdaSize()); assertEquals(blocks - 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes - 1); assertEntrysEqual(2 * nodes + 1, nodes - 1, size - 2 * nodes - 1); } /** * Tests basic insertion. */ @Test public void simpleInsert() { tda.insert(4, getTestEntries(1)); assertEquals(size + 1, tdaSize()); assertEntrysEqual(0, 0, 4); assertAreInserted(4, 1); assertEntrysEqual(4, 5, size - 4); closeAndReload(); assertEquals(size + 1, tdaSize()); assertEntrysEqual(0, 0, 4); assertAreInserted(4, 1); assertEntrysEqual(4, 5, size - 4); } /** * Tests inserting multiple entries. */ @Test public void insertMultiple() { tda.insert(4, getTestEntries(3)); assertEquals(size + 3, tdaSize()); assertEntrysEqual(0, 0, 4); assertAreInserted(4, 3); assertEntrysEqual(4, 7, size - 4); closeAndReload(); assertEquals(size + 3, tdaSize()); assertEntrysEqual(0, 0, 4); assertAreInserted(4, 3); assertEntrysEqual(4, 7, size - 4); } /** * Tests inserting multiple entries. */ @Test public void insertMany() { tda.insert(4, getTestEntries(nodes - 1)); assertEquals(size + nodes - 1, tdaSize()); assertEntrysEqual(0, 0, 4); assertAreInserted(4, nodes - 1); assertEntrysEqual(4, 4 + nodes - 1, size - 4); closeAndReload(); assertEquals(size + nodes - 1, tdaSize()); assertEntrysEqual(0, 0, 4); assertAreInserted(4, nodes - 1); assertEntrysEqual(4, 4 + nodes - 1, size - 4); } /** * Tests inserting multiple entries. */ @Test public void insertAtBlockBoundary() { tda.insert(nodes, getTestEntries(nodes)); assertEquals(size + nodes, tdaSize()); assertEquals(blocks + 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes); assertAreInserted(nodes, nodes); assertEntrysEqual(nodes, 2 * nodes, size - nodes); closeAndReload(); assertEquals(size + nodes, tdaSize()); assertEquals(blocks + 1, tdaBlocks()); assertEntrysEqual(0, 0, nodes); assertAreInserted(nodes, nodes); assertEntrysEqual(nodes, 2 * nodes, size - nodes); } /** * Asserts that the chosen entries are inserted by a test case. * @param startNum first entry * @param count number of entries */ private void assertAreInserted(final int startNum, final int count) { for(int i = 0; i < count; ++i) for(int j = 0; j < 1 << IO.NODEPOWER; ++j) assertEquals(5, tda.read1(startNum + i, j)); } /** * Creates a test-byte array containing the specified number of entries. * All bytes are set to (byte) 5. * @param e number of entries to create * @return byte array containing the number of entries (all bytes 5) */ private static byte[] getTestEntries(final int e) { final int rl = e << IO.NODEPOWER; final byte[] result = new byte[rl]; for(int r = 0; r < rl; ++r) result[r] = 5; return result; } }