blob: 906d6f3d600b1462012d7909e9fb1b9c93208ed5 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}