blob: 20ad1f4287e30855abae239d01ed67c730051899 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.io.tries;
import java.io.IOException;
import java.util.Arrays;
import org.junit.After;
import org.junit.Test;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.mockito.Mockito;
import org.quicktheories.api.Pair;
import org.quicktheories.generators.Generate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.quicktheories.QuickTheory.qt;
import static org.quicktheories.generators.SourceDSL.longs;
@SuppressWarnings("unchecked")
public class TrieNodeTest
{
private final SerializationNode<Integer> sn = Mockito.mock(SerializationNode.class);
private final DataOutputBuffer out = new DataOutputBuffer();
@After
public void after()
{
reset(sn);
out.clear();
}
@Test
public void testTypeFor0Children()
{
when(sn.childCount()).thenReturn(0);
assertSame(TrieNode.Types.PAYLOAD_ONLY, TrieNode.typeFor(sn, 0));
}
@Test
public void testTypeFor1ChildNoPayload()
{
when(sn.childCount()).thenReturn(1);
when(sn.payload()).thenReturn(null);
qt().forAll(Generate.pick(Arrays.asList(
Pair.of(Ranges.BITS_4, TrieNode.Types.SINGLE_NOPAYLOAD_4),
Pair.of(Ranges.BITS_8, TrieNode.Types.SINGLE_8),
Pair.of(Ranges.BITS_12, TrieNode.Types.SINGLE_NOPAYLOAD_12),
Pair.of(Ranges.BITS_16, TrieNode.Types.SINGLE_16),
Pair.of(Ranges.BITS_24, TrieNode.Types.DENSE_24),
Pair.of(Ranges.BITS_32, TrieNode.Types.DENSE_32),
Pair.of(Ranges.BITS_40, TrieNode.Types.DENSE_40),
Pair.of(Ranges.BITS_GT40, TrieNode.Types.LONG_DENSE)
)).flatMap(p -> longs().between(p._1.min, p._1.max).map(v -> Pair.of(v, p._2)))).check(p -> {
when(sn.maxPositionDelta(0)).thenReturn(-p._1);
return p._2 == TrieNode.typeFor(sn, 0);
});
}
@Test
public void testTypeFor1ChildAndPayload()
{
when(sn.childCount()).thenReturn(1);
when(sn.payload()).thenReturn(1);
qt().forAll(Generate.pick(Arrays.asList(
Pair.of(Ranges.BITS_4, TrieNode.Types.SINGLE_8),
Pair.of(Ranges.BITS_8, TrieNode.Types.SINGLE_8),
Pair.of(Ranges.BITS_12, TrieNode.Types.SINGLE_16),
Pair.of(Ranges.BITS_16, TrieNode.Types.SINGLE_16),
Pair.of(Ranges.BITS_24, TrieNode.Types.DENSE_24),
Pair.of(Ranges.BITS_32, TrieNode.Types.DENSE_32),
Pair.of(Ranges.BITS_40, TrieNode.Types.DENSE_40),
Pair.of(Ranges.BITS_GT40, TrieNode.Types.LONG_DENSE)
)).flatMap(p -> longs().between(p._1.min, p._1.max).map(v -> Pair.of(v, p._2)))).check(p -> {
when(sn.maxPositionDelta(0)).thenReturn(-p._1);
return p._2 == TrieNode.typeFor(sn, 0);
});
}
@Test
public void testTypeForMoreChildrenAndNoPayload()
{
when(sn.childCount()).thenReturn(2);
when(sn.payload()).thenReturn(null);
qt().forAll(Generate.pick(Arrays.asList(
Pair.of(Ranges.BITS_4, TrieNode.Types.DENSE_12),
Pair.of(Ranges.BITS_8, TrieNode.Types.DENSE_12),
Pair.of(Ranges.BITS_12, TrieNode.Types.DENSE_12),
Pair.of(Ranges.BITS_16, TrieNode.Types.DENSE_16),
Pair.of(Ranges.BITS_24, TrieNode.Types.DENSE_24),
Pair.of(Ranges.BITS_32, TrieNode.Types.DENSE_32),
Pair.of(Ranges.BITS_40, TrieNode.Types.DENSE_40),
Pair.of(Ranges.BITS_GT40, TrieNode.Types.LONG_DENSE)
)).flatMap(p -> longs().between(p._1.min, p._1.max).map(v -> Pair.of(v, p._2)))).check(p -> {
when(sn.maxPositionDelta(0)).thenReturn(-p._1);
return p._2 == TrieNode.typeFor(sn, 0);
});
}
@Test
public void testPayloadOnlyNode() throws IOException
{
TrieNode.Types.PAYLOAD_ONLY.serialize(out, null, 1 | 4, 0);
out.flush();
TrieNode node = TrieNode.at(out.asNewBuffer(), 0);
assertEquals(TrieNode.Types.PAYLOAD_ONLY, node);
assertEquals(1 | 4, node.payloadFlags(out.asNewBuffer(), 0));
assertEquals(1, node.payloadPosition(out.asNewBuffer(), 0));
assertEquals(1, node.sizeofNode(null));
assertEquals(-1, node.search(null, 0, 0));
assertEquals(Integer.MAX_VALUE, node.transitionByte(null, 0, 0));
assertEquals(123, node.lesserTransition(null, 0, 0, 0, 123));
assertEquals(123, node.greaterTransition(null, 0, 0, 0, 123));
assertEquals(-1, node.lastTransition(null, 0, 0));
assertEquals(-1, node.transition(null, 0, 0, 0));
assertEquals(0, node.transitionRange(null, 0));
assertEquals(0, node.transitionDelta(null, 0, 0));
}
private void prepareSingleNode(long delta)
{
when(sn.childCount()).thenReturn(1);
when(sn.transition(0)).thenReturn(123);
when(sn.serializedPositionDelta(0, 0)).thenReturn(delta);
}
private void singleNodeAssertions(TrieNode node, int payloadFlags, int size, long pos)
{
assertEquals(payloadFlags, node.payloadFlags(out.asNewBuffer(), 0));
assertEquals(size, node.sizeofNode(null));
assertEquals(0, node.search(out.asNewBuffer(), 0, 123));
assertEquals(-1, node.search(out.asNewBuffer(), 0, 122));
assertEquals(-2, node.search(out.asNewBuffer(), 0, 124));
assertEquals(123, node.transitionByte(out.asNewBuffer(), 0, 0));
assertEquals(Integer.MAX_VALUE, node.transitionByte(out.asNewBuffer(), 0, 1));
assertEquals(100, node.lesserTransition(out.asNewBuffer(), 0, 100 - pos, -2, 123));
assertEquals(234, node.greaterTransition(null, 0, 0, 0, 234));
assertEquals(234, node.greaterTransition(out.asNewBuffer(), 0, 100 - pos, -2, 234));
assertEquals(100, node.greaterTransition(out.asNewBuffer(), 0, 100 - pos, -1, 234));
assertEquals(234, node.greaterTransition(out.asNewBuffer(), 0, 100 - pos, 0, 234));
assertEquals(100, node.lastTransition(out.asNewBuffer(), 0, 100 - pos));
assertEquals(100, node.transition(out.asNewBuffer(), 0, 100 - pos, 0));
assertEquals(1, node.transitionRange(out.asNewBuffer(), 0));
assertEquals(pos, node.transitionDelta(out.asNewBuffer(), 0, 0));
}
@Test
public void testSingle16Node() throws IOException
{
prepareSingleNode(-43210L);
TrieNode.Types.SINGLE_16.serialize(out, sn, 1 | 4, 0);
TrieNode node = TrieNode.at(out.asNewBuffer(), 0);
assertEquals(TrieNode.Types.SINGLE_16, node);
singleNodeAssertions(node, 1 | 4, 4, -43210);
}
@Test
public void testSingleNoPayload4Node() throws IOException
{
prepareSingleNode(-7L);
TrieNode.Types.SINGLE_NOPAYLOAD_4.serialize(out, sn, 0, 0);
TrieNode node = TrieNode.at(out.asNewBuffer(), 0);
assertEquals(TrieNode.Types.SINGLE_NOPAYLOAD_4, node);
singleNodeAssertions(node, 0, 2, -7);
}
@Test
public void testSingleNoPayload12Node() throws IOException
{
prepareSingleNode(-1234L);
TrieNode.Types.SINGLE_NOPAYLOAD_12.serialize(out, sn, 0, 0);
TrieNode node = TrieNode.at(out.asNewBuffer(), 0);
assertEquals(TrieNode.Types.SINGLE_NOPAYLOAD_12, node);
singleNodeAssertions(node, 0, 3, -1234L);
}
private void prepareSparseNode(long delta) throws IOException
{
out.write(new byte[6]);
when(sn.childCount()).thenReturn(3);
when(sn.transition(0)).thenReturn(10);
when(sn.transition(1)).thenReturn(20);
when(sn.transition(2)).thenReturn(30);
when(sn.serializedPositionDelta(0, 6)).thenReturn(delta);
when(sn.serializedPositionDelta(1, 6)).thenReturn(delta + 2);
when(sn.serializedPositionDelta(2, 6)).thenReturn(delta + 4);
}
private void sparseOrDenseNodeAssertions(TrieNode node, int payloadFlags, int size, long pos)
{
assertEquals(size, node.sizeofNode(sn));
assertEquals(payloadFlags, node.payloadFlags(out.asNewBuffer(), 6));
assertEquals(3, node.transitionRange(out.asNewBuffer(), 6));
assertEquals(6 + size, node.payloadPosition(out.asNewBuffer(), 6));
assertEquals(Integer.MAX_VALUE, node.transitionByte(out.asNewBuffer(), 6, 3));
assertEquals(10, node.lesserTransition(out.asNewBuffer(), 6, 10 - pos, 1, 123));
assertEquals(14, node.lesserTransition(out.asNewBuffer(), 6, 10 - pos, -4, 123));
assertEquals(14, node.greaterTransition(out.asNewBuffer(), 6, 10 - pos, 1, 234));
assertEquals(234, node.greaterTransition(out.asNewBuffer(), 6, 10 - pos, 2, 234));
assertEquals(10, node.greaterTransition(out.asNewBuffer(), 6, 10 - pos, -1, 234));
assertEquals(14, node.lastTransition(out.asNewBuffer(), 6, 10 - pos));
assertEquals(pos, node.transitionDelta(out.asNewBuffer(), 6, 0));
assertEquals(pos + 2, node.transitionDelta(out.asNewBuffer(), 6, 1));
assertEquals(pos + 4, node.transitionDelta(out.asNewBuffer(), 6, 2));
assertEquals(10, node.transition(out.asNewBuffer(), 6, 10 - pos, 0));
assertEquals(12, node.transition(out.asNewBuffer(), 6, 10 - pos, 1));
assertEquals(14, node.transition(out.asNewBuffer(), 6, 10 - pos, 2));
}
private void sparseNodeAssertions(TrieNode node, int payloadFlags, int size, long pos)
{
sparseOrDenseNodeAssertions(node, payloadFlags, size, pos);
assertEquals(-1, node.search(out.asNewBuffer(), 6, 5));
assertEquals(0, node.search(out.asNewBuffer(), 6, 10));
assertEquals(-2, node.search(out.asNewBuffer(), 6, 15));
assertEquals(-4, node.search(out.asNewBuffer(), 6, 35));
assertEquals(10, node.transitionByte(out.asNewBuffer(), 6, 0));
assertEquals(20, node.transitionByte(out.asNewBuffer(), 6, 1));
assertEquals(30, node.transitionByte(out.asNewBuffer(), 6, 2));
}
@Test
public void testSparse16Node() throws IOException
{
prepareSparseNode(-43210L);
TrieNode.Types.SPARSE_16.serialize(out, sn, 1 | 4, 6);
TrieNode node = TrieNode.at(out.asNewBuffer(), 6);
assertEquals(TrieNode.Types.SPARSE_16, node);
sparseNodeAssertions(node, 1 | 4, 11, -43210L);
}
@Test
public void testSparse12Node() throws IOException
{
prepareSparseNode(-1234L);
TrieNode.Types.SPARSE_12.serialize(out, sn, 1 | 4, 6);
TrieNode node = TrieNode.at(out.asNewBuffer(), 6);
assertEquals(TrieNode.Types.SPARSE_12, node);
sparseNodeAssertions(node, 1 | 4, 10, -1234L);
}
private void prepareDenseNode(long delta) throws IOException
{
out.write(new byte[6]);
when(sn.childCount()).thenReturn(3);
when(sn.transition(0)).thenReturn(11);
when(sn.transition(1)).thenReturn(12);
when(sn.transition(2)).thenReturn(13);
when(sn.serializedPositionDelta(0, 6)).thenReturn(delta);
when(sn.serializedPositionDelta(1, 6)).thenReturn(delta + 2);
when(sn.serializedPositionDelta(2, 6)).thenReturn(delta + 4);
}
private void denseNodeAssertions(TrieNode node, int payload, int size, long pos)
{
sparseOrDenseNodeAssertions(node, payload, size, pos);
assertEquals(-1, node.search(out.asNewBuffer(), 6, 10));
assertEquals(0, node.search(out.asNewBuffer(), 6, 11));
assertEquals(-4, node.search(out.asNewBuffer(), 6, 14));
assertEquals(11, node.transitionByte(out.asNewBuffer(), 6, 0));
assertEquals(12, node.transitionByte(out.asNewBuffer(), 6, 1));
assertEquals(13, node.transitionByte(out.asNewBuffer(), 6, 2));
}
@Test
public void testDense16Node() throws IOException
{
prepareDenseNode(-43210L);
TrieNode.Types.DENSE_16.serialize(out, sn, 1 | 4, 6);
TrieNode node = TrieNode.at(out.asNewBuffer(), 6);
assertEquals(TrieNode.Types.DENSE_16, node);
denseNodeAssertions(node, 1 | 4, 9, -43210L);
}
@Test
public void testDense12Node() throws IOException
{
prepareDenseNode(-1234L);
TrieNode.Types.DENSE_12.serialize(out, sn, 1 | 4, 6);
TrieNode node = TrieNode.at(out.asNewBuffer(), 6);
assertEquals(TrieNode.Types.DENSE_12, node);
denseNodeAssertions(node, 1 | 4, 8, -1234L);
}
@Test
public void testLongDenseNode() throws IOException
{
prepareDenseNode(-0x7ffffffffffffffL);
TrieNode.Types.LONG_DENSE.serialize(out, sn, 1 | 4, 6);
TrieNode node = TrieNode.at(out.asNewBuffer(), 6);
assertEquals(TrieNode.Types.LONG_DENSE, node);
denseNodeAssertions(node, 1 | 4, 27, -0x7ffffffffffffffL);
}
private enum Ranges
{
BITS_4(0x1L, 0xfL),
BITS_8(0x10L, 0xffL),
BITS_12(0x100L, 0xfffL),
BITS_16(0x1000L, 0xffffL),
BITS_24(0x100000L, 0xffffffL),
BITS_32(0x10000000L, 0xffffffffL),
BITS_40(0x1000000000L, 0xffffffffffL),
BITS_GT40(0x100000000000L, 0x7fffffffffffffffL);
private final long min, max;
Ranges(long min, long max)
{
this.min = min;
this.max = max;
}
}
}