Commit | Line | Data |
---|---|---|
8b03451b GB |
1 | /******************************************************************************* |
2 | * Copyright (c) 2017 École Polytechnique de Montréal | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | *******************************************************************************/ | |
9 | ||
10 | package org.eclipse.tracecompass.internal.provisional.datastore.core.historytree; | |
11 | ||
12 | import static org.junit.Assert.assertEquals; | |
13 | import static org.junit.Assert.assertFalse; | |
14 | import static org.junit.Assert.assertNotNull; | |
15 | import static org.junit.Assert.assertTrue; | |
16 | ||
17 | import java.io.File; | |
18 | import java.io.IOException; | |
19 | import java.util.Arrays; | |
20 | ||
dad84716 GB |
21 | import org.eclipse.tracecompass.datastore.core.interval.HTInterval; |
22 | import org.eclipse.tracecompass.datastore.core.interval.IHTInterval; | |
23 | import org.eclipse.tracecompass.datastore.core.interval.IHTIntervalReader; | |
8b03451b GB |
24 | import org.eclipse.tracecompass.internal.datastore.core.historytree.HtIo; |
25 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.AbstractHistoryTree.IHTNodeFactory; | |
26 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.HTNode; | |
27 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.HistoryTreeStub; | |
28 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHTNode; | |
29 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHTNode.NodeType; | |
a145e64d | 30 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.classic.ClassicHistoryTreeStub; |
fb7125d6 | 31 | import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.overlapping.OverlappingHistoryTreeStub; |
8b03451b GB |
32 | import org.junit.After; |
33 | import org.junit.Test; | |
34 | import org.junit.runner.RunWith; | |
35 | import org.junit.runners.Parameterized; | |
36 | import org.junit.runners.Parameterized.Parameters; | |
37 | ||
38 | /** | |
39 | * Test the {@link HTNode} base class for nodes. This class has the different | |
40 | * node types as parameter. It tests specifically the leaf node methods. | |
41 | * | |
42 | * @author Geneviève Bastien | |
43 | * @param <E> | |
44 | * The type of element to add in the nodes | |
45 | * @param <N> | |
46 | * The type of node to test | |
47 | */ | |
48 | @RunWith(Parameterized.class) | |
49 | public class HTNodeTest<E extends IHTInterval, N extends HTNode<E>> { | |
50 | ||
51 | /** | |
52 | * Factory to create new objects to insert in the nodes | |
53 | * | |
54 | * @param <T> | |
55 | * The type of object to create | |
56 | */ | |
57 | protected interface ObjectFactory<T extends IHTInterval> { | |
58 | /** | |
59 | * Create an object that fits in the tree with the given start/end time | |
60 | * | |
61 | * @param start | |
62 | * The start time | |
63 | * @param end | |
64 | * The end time | |
65 | * @return The object | |
66 | */ | |
67 | T createObject(long start, long end); | |
68 | } | |
69 | ||
70 | /** | |
71 | * A factory to create base objects for test | |
72 | */ | |
73 | protected static final ObjectFactory<HTInterval> BASE_OBJ_FACTORY = (s, e) -> new HTInterval(s, e); | |
74 | ||
75 | /** The nodes' block size */ | |
76 | protected static final int BLOCKSIZE = HtTestUtils.BLOCKSIZE; | |
77 | /** The maximum number of children for the nodes */ | |
78 | protected static final int NB_CHILDREN = 3; | |
79 | private static final long TREE_START = 10L; | |
80 | ||
81 | /** | |
82 | * @return The arrays of parameters | |
83 | */ | |
84 | @Parameters(name = "{index}: {0}") | |
85 | public static Iterable<Object[]> getParameters() { | |
86 | return Arrays.asList(new Object[][] { | |
87 | { "Leaf node", | |
88 | HTNode.COMMON_HEADER_SIZE, | |
89 | HistoryTreeStub.NODE_FACTORY, | |
90 | HtTestUtils.READ_FACTORY, BASE_OBJ_FACTORY | |
91 | }, | |
a145e64d AM |
92 | { "Classic leaf node", |
93 | HTNode.COMMON_HEADER_SIZE, | |
94 | ClassicHistoryTreeStub.CLASSIC_NODE_FACTORY, | |
95 | HtTestUtils.READ_FACTORY, | |
fb7125d6 LPD |
96 | BASE_OBJ_FACTORY |
97 | }, | |
98 | { "Overlapping leaf node", | |
99 | HTNode.COMMON_HEADER_SIZE, | |
100 | OverlappingHistoryTreeStub.OVERLAPPING_NODE_FACTORY, | |
101 | HtTestUtils.READ_FACTORY, | |
102 | BASE_OBJ_FACTORY | |
103 | }, | |
8b03451b GB |
104 | }); |
105 | } | |
106 | ||
107 | private final HtIo<E, N> fHtIo; | |
108 | private final int fHeaderSize; | |
109 | private final NodeType fType; | |
110 | private final IHTIntervalReader<E> fHtObjectReader; | |
111 | private final IHTNodeFactory<E, N> fNodeFactory; | |
112 | private final ObjectFactory<E> fObjectFactory; | |
113 | ||
114 | /** | |
115 | * Constructor | |
116 | * | |
117 | * @param name | |
118 | * The name of the test | |
119 | * @param headerSize | |
120 | * The size of the header for this node type | |
121 | * @param factory | |
122 | * The node factory to use | |
123 | * @param objReader | |
124 | * The factory to read element data from disk | |
125 | * @param objFactory | |
126 | * The factory to create objects for this tree | |
127 | * @throws IOException | |
128 | * Any exception occurring with the file | |
129 | */ | |
130 | public HTNodeTest(String name, | |
131 | int headerSize, | |
132 | IHTNodeFactory<E, N> factory, | |
133 | IHTIntervalReader<E> objReader, | |
134 | ObjectFactory<E> objFactory) throws IOException { | |
135 | this(name, headerSize, NodeType.LEAF, factory, objReader, objFactory); | |
136 | } | |
137 | ||
138 | /** | |
139 | * Constructor | |
140 | * | |
141 | * @param name | |
142 | * The name of the test | |
143 | * @param headerSize | |
144 | * The size of the header for this node | |
145 | * @param type | |
146 | * The node type | |
147 | * @param nodeFactory | |
148 | * The node factory to use | |
149 | * @param objReader | |
150 | * The factory to read element data from disk | |
151 | * @param objFactory | |
152 | * The factory to create objects for this tree | |
153 | * @throws IOException | |
154 | * Any exception occurring with the file | |
155 | */ | |
156 | protected HTNodeTest(String name, | |
157 | int headerSize, | |
158 | NodeType type, | |
159 | IHTNodeFactory<E, N> nodeFactory, | |
160 | IHTIntervalReader<E> objReader, | |
161 | ObjectFactory<E> objFactory) throws IOException { | |
162 | File file = File.createTempFile("tmp", null); | |
163 | assertNotNull(file); | |
164 | fHtObjectReader = objReader; | |
165 | fNodeFactory = nodeFactory; | |
166 | ||
167 | fHtIo = new HtIo<>(file, | |
168 | HtTestUtils.BLOCKSIZE, | |
169 | NB_CHILDREN, | |
170 | true, | |
171 | objReader, | |
172 | nodeFactory); | |
173 | ||
174 | fHeaderSize = headerSize; | |
175 | fType = type; | |
176 | fObjectFactory = objFactory; | |
177 | } | |
178 | ||
179 | /** | |
180 | * Get a new node | |
181 | * | |
182 | * @param seqNb | |
183 | * The sequence number | |
184 | * @param parentNb | |
185 | * The parent sequence number | |
186 | * @param nodeStart | |
187 | * The node start | |
188 | * @return A new node, created with the factory sent in parameter in the | |
189 | * constructor | |
190 | */ | |
191 | public N newNode(int seqNb, int parentNb, long nodeStart) { | |
192 | return fNodeFactory.createNode(fType, BLOCKSIZE, NB_CHILDREN, seqNb, parentNb, nodeStart); | |
193 | } | |
194 | ||
195 | /** | |
196 | * Delete the file after test | |
197 | */ | |
198 | @After | |
199 | public void cleanUp() { | |
200 | fHtIo.deleteFile(); | |
201 | } | |
202 | ||
203 | /** | |
204 | * Fills a node with objects of length 1, going incrementally | |
205 | * | |
206 | * @param node | |
207 | * The node to fill | |
208 | * @param nbObjects | |
209 | * The number of objects to add | |
210 | * @param start | |
211 | * The start time of the objects | |
212 | */ | |
213 | protected void fillNode(HTNode<E> node, int nbObjects, long start) { | |
214 | for (int i = 0; i < nbObjects; i++) { | |
215 | node.add(fObjectFactory.createObject(i + start, i + start + 1)); | |
216 | } | |
217 | } | |
218 | ||
219 | /** | |
220 | * Get the header size of this node | |
221 | * | |
222 | * @return The header size | |
223 | */ | |
224 | protected int getHeaderSize() { | |
225 | return fHeaderSize; | |
226 | } | |
227 | ||
228 | /** | |
229 | * Create a new object for this type of node | |
230 | * | |
231 | * @param start | |
232 | * The start of the object | |
233 | * @param end | |
234 | * The end of the object | |
235 | * @return The new object | |
236 | */ | |
237 | protected E createObject(long start, long end) { | |
238 | return fObjectFactory.createObject(start, end); | |
239 | } | |
240 | ||
241 | /** | |
242 | * Write a node to the file | |
243 | * | |
244 | * @param node | |
245 | * Node to write to disk | |
246 | * @throws IOException | |
247 | * Exceptions while writing to file | |
248 | */ | |
249 | protected void write(HTNode<E> node) throws IOException { | |
250 | HtIo<E, N> htIo = fHtIo; | |
251 | ||
252 | // Close the node and write it to disk | |
253 | node.writeSelf(htIo.getFileWriter(node.getSequenceNumber()).getChannel()); | |
254 | } | |
255 | ||
256 | /** | |
257 | * Reads a node from the history tree file | |
258 | * | |
259 | * @param seqNb | |
260 | * The sequence number of the node to get | |
261 | * @return The read node | |
262 | * @throws IOException | |
263 | * Exceptions while reading the node | |
264 | */ | |
265 | protected HTNode<E> read(int seqNb) throws IOException { | |
266 | HtIo<E, N> htIo = fHtIo; | |
267 | ||
268 | return HTNode.readNode(BLOCKSIZE, | |
269 | NB_CHILDREN, | |
270 | htIo.supplyATReader(seqNb).getChannel(), | |
271 | fHtObjectReader, | |
272 | fNodeFactory); | |
273 | ||
274 | } | |
275 | ||
276 | /** | |
277 | * Test the leaf node methods without adding the node to disk | |
278 | */ | |
279 | @Test | |
280 | public void testNodeData() { | |
281 | HTNode<E> node = newNode(0, -1, TREE_START); | |
282 | ||
283 | // Test the values at the beginning | |
284 | assertFalse(node.isOnDisk()); | |
285 | assertEquals(TREE_START, node.getNodeStart()); | |
286 | assertEquals(Long.MAX_VALUE, node.getNodeEnd()); | |
287 | assertEquals(0, node.getSequenceNumber()); | |
288 | assertEquals(-1, node.getParentSequenceNumber()); | |
289 | assertEquals(fHeaderSize, node.getTotalHeaderSize()); | |
290 | assertTrue(node.isEmpty()); | |
291 | assertEquals(fType, node.getNodeType()); | |
292 | assertEquals(HtTestUtils.BLOCKSIZE - fHeaderSize, node.getNodeFreeSpace()); | |
293 | assertEquals(0, node.getNodeUsagePercent()); | |
294 | ||
295 | // Add an element. It is possible to add an element outside the | |
296 | // boundaries of the node | |
297 | E object = fObjectFactory.createObject(0L, 10L); | |
298 | node.add(object); | |
299 | assertEquals(HtTestUtils.BLOCKSIZE - fHeaderSize - object.getSizeOnDisk(), node.getNodeFreeSpace()); | |
300 | ||
301 | // Fill the node with objects | |
302 | int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); | |
303 | fillNode(node, nbObjects - 1, TREE_START); | |
304 | // Check the free space and sizes | |
305 | int expectedSize = HtTestUtils.BLOCKSIZE - fHeaderSize - object.getSizeOnDisk() * nbObjects; | |
306 | assertEquals(expectedSize, node.getNodeFreeSpace()); | |
307 | assertEquals(99, node.getNodeUsagePercent()); | |
308 | assertEquals(nbObjects, node.getIntervals().size()); | |
309 | ||
310 | } | |
311 | ||
312 | /** | |
313 | * Test adding an element to a full leaf node | |
314 | */ | |
315 | @Test(expected = IllegalArgumentException.class) | |
316 | public void testNodeInvalidAdd() { | |
317 | HTNode<E> node = newNode(0, -1, TREE_START); | |
318 | // Fill the node with objects | |
319 | E object = fObjectFactory.createObject(0L, 10L); | |
320 | int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); | |
321 | fillNode(node, nbObjects, TREE_START); | |
322 | ||
323 | // Add a new object | |
324 | node.add(object); | |
325 | } | |
326 | ||
327 | /** | |
328 | * Test closing a node at an invalid end time | |
329 | */ | |
330 | @Test(expected = IllegalArgumentException.class) | |
331 | public void testNodeInvalidEnd() { | |
332 | HTNode<E> node = newNode(0, -1, TREE_START); | |
333 | // Fill the node with objects | |
334 | E object = fObjectFactory.createObject(0L, 10L); | |
335 | int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); | |
336 | fillNode(node, nbObjects, TREE_START); | |
337 | ||
338 | // Close the node at a wrong time | |
339 | node.closeThisNode(TREE_START); | |
340 | } | |
341 | ||
342 | /** | |
343 | * Test adding an element to a closed node | |
344 | */ | |
345 | @Test | |
346 | public void testAddToCloseNode() { | |
347 | HTNode<E> node = newNode(0, -1, TREE_START); | |
348 | // Fill the node with objects | |
349 | E object = fObjectFactory.createObject(TREE_START, TREE_START + 10); | |
350 | int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); | |
351 | fillNode(node, nbObjects - 1, TREE_START); | |
352 | ||
353 | // Add a new object | |
354 | node.closeThisNode(TREE_START + nbObjects); | |
355 | // FIXME: shouldn't this fail? | |
356 | node.add(object); | |
357 | ||
358 | } | |
359 | ||
360 | /** | |
361 | * test closing, writing and reading a leaf node | |
362 | * | |
363 | * @throws IOException | |
364 | * Exception while writing/reading the file | |
365 | */ | |
366 | @Test | |
367 | public void testCloseNode() throws IOException { | |
368 | HTNode<E> node = newNode(0, -1, TREE_START); | |
369 | // Fill the node with objects | |
370 | E object = fObjectFactory.createObject(0L, 10L); | |
371 | int nbObjects = (HtTestUtils.BLOCKSIZE - fHeaderSize) / object.getSizeOnDisk(); | |
372 | fillNode(node, nbObjects, TREE_START); | |
373 | assertEquals(nbObjects, node.getIntervals().size()); | |
374 | ||
375 | // Close the node and write it to disk | |
376 | node.closeThisNode(TREE_START + nbObjects + 1); | |
377 | write(node); | |
378 | assertTrue(node.isOnDisk()); | |
379 | ||
380 | // Read the node and make sure its data is equal to that of the original | |
381 | // node | |
382 | HTNode<E> readNode = read(0); | |
383 | assertTrue(readNode.isOnDisk()); | |
384 | assertEquals(node, readNode); | |
385 | } | |
386 | ||
387 | /** | |
388 | * Test the {@link HTNode#getNbChildren()} method | |
389 | */ | |
390 | @Test | |
391 | public void testNbChildren() { | |
392 | HTNode<E> node = newNode(0, -1, TREE_START); | |
393 | assertEquals(0, node.getNbChildren()); | |
394 | } | |
395 | ||
396 | /** | |
397 | * Test the {@link HTNode#getChild(int)} method | |
398 | */ | |
399 | @Test(expected = IndexOutOfBoundsException.class) | |
400 | public void testGetChild() { | |
401 | HTNode<E> node = newNode(0, -1, TREE_START); | |
402 | node.getChild(0); | |
403 | } | |
404 | ||
405 | /** | |
406 | * Test the {@link HTNode#getLatestChild()} method | |
407 | */ | |
408 | @Test(expected = UnsupportedOperationException.class) | |
409 | public void testGetLatestChild() { | |
410 | HTNode<E> node = newNode(0, -1, TREE_START); | |
411 | node.getLatestChild(); | |
412 | } | |
413 | ||
414 | /** | |
415 | * Test the {@link HTNode#linkNewChild(IHTNode)} method | |
416 | * | |
417 | * @throws IOException | |
418 | * Exceptiosn thrown when reading/writing | |
419 | */ | |
420 | @SuppressWarnings("unused") | |
421 | @Test(expected = UnsupportedOperationException.class) | |
422 | public void testLinkNewChild() throws IOException { | |
423 | HTNode<E> node = newNode(0, -1, TREE_START); | |
424 | HTNode<E> childNode = newNode(1, 0, TREE_START); | |
425 | node.linkNewChild(childNode); | |
426 | } | |
427 | ||
428 | } |