| /******************************************************************************* |
| * 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 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| |
| 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.datastore.core.interval.HTInterval; |
| import org.eclipse.tracecompass.datastore.core.interval.IHTInterval; |
| import org.eclipse.tracecompass.datastore.core.interval.IHTIntervalReader; |
| 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.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()); |
| int expectedNodeUsagePercent = expectedSize == 0 ? 100 : 99; |
| assertEquals(expectedNodeUsagePercent, 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); |
| } |
| |
| } |