/******************************************************************************* * Copyright (c) 2017 École Polytechnique de Montréal * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.eclipse.tracecompass.internal.provisional.datastore.core.historytree; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Arrays; import org.eclipse.tracecompass.internal.datastore.core.historytree.HtIo; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.AbstractHistoryTree.IHTNodeFactory; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.HTNode; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.HistoryTreeStub; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHTNode; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHTNode.NodeType; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.classic.ClassicHistoryTreeStub; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.overlapping.OverlappingHistoryTreeStub; import org.eclipse.tracecompass.internal.provisional.datastore.core.interval.HTInterval; import org.eclipse.tracecompass.internal.provisional.datastore.core.interval.IHTInterval; import org.eclipse.tracecompass.internal.provisional.datastore.core.interval.IHTIntervalReader; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; /** * Test the {@link HTNode} base class for nodes. This class has the different * node types as parameter. It tests specifically the leaf node methods. * * @author Geneviève Bastien * @param <E> * The type of element to add in the nodes * @param <N> * The type of node to test */ @RunWith(Parameterized.class) public class HTNodeTest<E extends IHTInterval, N extends HTNode<E>> { /** * Factory to create new objects to insert in the nodes * * @param <T> * The type of object to create */ protected interface ObjectFactory<T extends IHTInterval> { /** * Create an object that fits in the tree with the given start/end time * * @param start * The start time * @param end * The end time * @return The object */ T createObject(long start, long end); } /** * A factory to create base objects for test */ protected static final ObjectFactory<HTInterval> BASE_OBJ_FACTORY = (s, e) -> new HTInterval(s, e); /** The nodes' block size */ protected static final int BLOCKSIZE = HtTestUtils.BLOCKSIZE; /** The maximum number of children for the nodes */ protected static final int NB_CHILDREN = 3; private static final long TREE_START = 10L; /** * @return The arrays of parameters */ @Parameters(name = "{index}: {0}") public static Iterable<Object[]> getParameters() { return Arrays.asList(new Object[][] { { "Leaf node", HTNode.COMMON_HEADER_SIZE, HistoryTreeStub.NODE_FACTORY, HtTestUtils.READ_FACTORY, BASE_OBJ_FACTORY }, { "Classic leaf node", HTNode.COMMON_HEADER_SIZE, ClassicHistoryTreeStub.CLASSIC_NODE_FACTORY, HtTestUtils.READ_FACTORY, BASE_OBJ_FACTORY }, { "Overlapping leaf node", HTNode.COMMON_HEADER_SIZE, OverlappingHistoryTreeStub.OVERLAPPING_NODE_FACTORY, HtTestUtils.READ_FACTORY, BASE_OBJ_FACTORY }, }); } private final HtIo<E, N> fHtIo; private final int fHeaderSize; private final NodeType fType; private final IHTIntervalReader<E> fHtObjectReader; private final IHTNodeFactory<E, N> fNodeFactory; private final ObjectFactory<E> fObjectFactory; /** * Constructor * * @param name * The name of the test * @param headerSize * The size of the header for this node type * @param factory * The node factory to use * @param objReader * The factory to read element data from disk * @param objFactory * The factory to create objects for this tree * @throws IOException * Any exception occurring with the file */ public HTNodeTest(String name, int headerSize, IHTNodeFactory<E, N> factory, IHTIntervalReader<E> objReader, ObjectFactory<E> objFactory) throws IOException { this(name, headerSize, NodeType.LEAF, factory, objReader, objFactory); } /** * Constructor * * @param name * The name of the test * @param headerSize * The size of the header for this node * @param type * The node type * @param nodeFactory * The node factory to use * @param objReader * The factory to read element data from disk * @param objFactory * The factory to create objects for this tree * @throws IOException * Any exception occurring with the file */ protected HTNodeTest(String name, int headerSize, NodeType type, IHTNodeFactory<E, N> nodeFactory, IHTIntervalReader<E> objReader, ObjectFactory<E> objFactory) throws IOException { File file = File.createTempFile("tmp", null); assertNotNull(file); fHtObjectReader = objReader; fNodeFactory = nodeFactory; fHtIo = new HtIo<>(file, HtTestUtils.BLOCKSIZE, NB_CHILDREN, true, objReader, nodeFactory); fHeaderSize = headerSize; fType = type; fObjectFactory = objFactory; } /** * Get a new node * * @param seqNb * The sequence number * @param parentNb * The parent sequence number * @param nodeStart * The node start * @return A new node, created with the factory sent in parameter in the * constructor */ public N newNode(int seqNb, int parentNb, long nodeStart) { return fNodeFactory.createNode(fType, BLOCKSIZE, NB_CHILDREN, seqNb, parentNb, nodeStart); } /** * Delete the file after test */ @After public void cleanUp() { fHtIo.deleteFile(); } /** * Fills a node with objects of length 1, going incrementally * * @param node * The node to fill * @param nbObjects * The number of objects to add * @param start * The start time of the objects */ protected void fillNode(HTNode<E> node, int nbObjects, long start) { for (int i = 0; i < nbObjects; i++) { node.add(fObjectFactory.createObject(i + start, i + start + 1)); } } /** * Get the header size of this node * * @return The header size */ protected int getHeaderSize() { return fHeaderSize; } /** * Create a new object for this type of node * * @param start * The start of the object * @param end * The end of the object * @return The new object */ protected E createObject(long start, long end) { return fObjectFactory.createObject(start, end); } /** * Write a node to the file * * @param node * Node to write to disk * @throws IOException * Exceptions while writing to file */ protected void write(HTNode<E> node) throws IOException { HtIo<E, N> htIo = fHtIo; // Close the node and write it to disk node.writeSelf(htIo.getFileWriter(node.getSequenceNumber()).getChannel()); } /** * Reads a node from the history tree file * * @param seqNb * The sequence number of the node to get * @return The read node * @throws IOException * Exceptions while reading the node */ protected HTNode<E> read(int seqNb) throws IOException { HtIo<E, N> htIo = fHtIo; return HTNode.readNode(BLOCKSIZE, NB_CHILDREN, htIo.supplyATReader(seqNb).getChannel(), fHtObjectReader, fNodeFactory); } /** * Test the leaf node methods without adding the node to disk */ @Test public void testNodeData() { HTNode<E> node = newNode(0, -1, TREE_START); // Test the values at the beginning assertFalse(node.isOnDisk()); assertEquals(TREE_START, node.getNodeStart()); assertEquals(Long.MAX_VALUE, node.getNodeEnd()); assertEquals(0, node.getSequenceNumber()); assertEquals(-1, node.getParentSequenceNumber()); assertEquals(fHeaderSize, node.getTotalHeaderSize()); assertTrue(node.isEmpty()); assertEquals(fType, node.getNodeType()); assertEquals(HtTestUtils.BLOCKSIZE - fHeaderSize, node.getNodeFreeSpace()); assertEquals(0, node.getNodeUsagePercent()); // Add an element. It is possible to add an element outside the // boundaries of the node E object = fObjectFactory.createObject(0L, 10L); node.add(object); assertEquals(HtTestUtils.BLOCKSIZE - fHeaderSize - object.getSizeOnDisk(), node.getNodeFreeSpace()); // Fill the node with objects int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); fillNode(node, nbObjects - 1, TREE_START); // Check the free space and sizes int expectedSize = HtTestUtils.BLOCKSIZE - fHeaderSize - object.getSizeOnDisk() * nbObjects; assertEquals(expectedSize, node.getNodeFreeSpace()); assertEquals(99, node.getNodeUsagePercent()); assertEquals(nbObjects, node.getIntervals().size()); } /** * Test adding an element to a full leaf node */ @Test(expected = IllegalArgumentException.class) public void testNodeInvalidAdd() { HTNode<E> node = newNode(0, -1, TREE_START); // Fill the node with objects E object = fObjectFactory.createObject(0L, 10L); int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); fillNode(node, nbObjects, TREE_START); // Add a new object node.add(object); } /** * Test closing a node at an invalid end time */ @Test(expected = IllegalArgumentException.class) public void testNodeInvalidEnd() { HTNode<E> node = newNode(0, -1, TREE_START); // Fill the node with objects E object = fObjectFactory.createObject(0L, 10L); int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); fillNode(node, nbObjects, TREE_START); // Close the node at a wrong time node.closeThisNode(TREE_START); } /** * Test adding an element to a closed node */ @Test public void testAddToCloseNode() { HTNode<E> node = newNode(0, -1, TREE_START); // Fill the node with objects E object = fObjectFactory.createObject(TREE_START, TREE_START + 10); int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); fillNode(node, nbObjects - 1, TREE_START); // Add a new object node.closeThisNode(TREE_START + nbObjects); // FIXME: shouldn't this fail? node.add(object); } /** * test closing, writing and reading a leaf node * * @throws IOException * Exception while writing/reading the file */ @Test public void testCloseNode() throws IOException { HTNode<E> node = newNode(0, -1, TREE_START); // Fill the node with objects E object = fObjectFactory.createObject(0L, 10L); int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); fillNode(node, nbObjects, TREE_START); assertEquals(nbObjects, node.getIntervals().size()); // Close the node and write it to disk node.closeThisNode(TREE_START + nbObjects + 1); write(node); assertTrue(node.isOnDisk()); // Read the node and make sure its data is equal to that of the original // node HTNode<E> readNode = read(0); assertTrue(readNode.isOnDisk()); assertEquals(node, readNode); } /** * Test the {@link HTNode#getNbChildren()} method */ @Test public void testNbChildren() { HTNode<E> node = newNode(0, -1, TREE_START); assertEquals(0, node.getNbChildren()); } /** * Test the {@link HTNode#getChild(int)} method */ @Test(expected = IndexOutOfBoundsException.class) public void testGetChild() { HTNode<E> node = newNode(0, -1, TREE_START); node.getChild(0); } /** * Test the {@link HTNode#getLatestChild()} method */ @Test(expected = UnsupportedOperationException.class) public void testGetLatestChild() { HTNode<E> node = newNode(0, -1, TREE_START); node.getLatestChild(); } /** * Test the {@link HTNode#linkNewChild(IHTNode)} method * * @throws IOException * Exceptiosn thrown when reading/writing */ @SuppressWarnings("unused") @Test(expected = UnsupportedOperationException.class) public void testLinkNewChild() throws IOException { HTNode<E> node = newNode(0, -1, TREE_START); HTNode<E> childNode = newNode(1, 0, TREE_START); node.linkNewChild(childNode); } }