/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.collection;
import io.vavr.*;
import io.vavr.control.Option;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.IterableAssert;
import org.assertj.core.api.ObjectAssert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.InvalidObjectException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* Tests all methods defined in {@link Tree}.
*/
public class TreeTest extends AbstractTraversableTest {
/**
* <pre><code>
* 1
* / \
* / \
* / \
* 2 3
* / \ /
* 4 5 6
* / / \
* 7 8 9
* </code></pre>
*/
private final Tree<Integer> tree = $(1, $(2, $(4, $(7)), $(5)), $(3, $(6, $(8), $(9))));
@Override
protected <T> IterableAssert<T> assertThat(Iterable<T> actual) {
return new IterableAssert<T>(actual) {
@SuppressWarnings("unchecked")
@Override
public IterableAssert<T> isEqualTo(Object expected) {
if (actual instanceof Option) {
final Option<?> opt1 = ((Option<?>) actual);
final Option<?> opt2 = (Option<?>) expected;
Assertions.assertThat(convOption(opt1)).isEqualTo(convOption(opt2));
} else if (expected instanceof Map) {
final Map<?, ?> map1 = (Map<?, ?>) actual;
final Map<?, ?> map2 = (Map<?, ?>) expected;
Assertions.assertThat(convMap(map1)).isEqualTo(convMap(map2));
} else if (expected instanceof Tree) {
assertThat(Stream.ofAll(actual)).isEqualTo(Stream.ofAll((Tree<?>) expected));
} else {
Assertions.assertThat(actual).isEqualTo((Iterable<T>) expected);
}
return this;
}
private Option<?> convOption(Option<?> option) {
return option.map(o -> (o instanceof Iterable) ? Stream.ofAll((Iterable<?>) o) : o);
}
private Map<?, ?> convMap(Map<?, ?> map) {
return map.map((k, v) -> Tuple.of(k, v instanceof Iterable ? Stream.ofAll((Iterable<?>) v) : v));
}
};
}
@Override
protected <T> ObjectAssert<T> assertThat(T actual) {
return new ObjectAssert<T>(actual) {
@Override
public ObjectAssert<T> isEqualTo(Object expected) {
if (actual instanceof Tuple2) {
final Tuple2<?, ?> t1 = (Tuple2<?, ?>) actual;
final Tuple2<?, ?> t2 = (Tuple2<?, ?>) expected;
assertThat((Iterable<?>) t1._1).isEqualTo(t2._1);
assertThat((Iterable<?>) t1._2).isEqualTo(t2._2);
return this;
} else {
return super.isEqualTo(expected);
}
}
};
}
@Override
protected <T> Collector<T, ArrayList<T>, Tree<T>> collector() {
return Tree.collector();
}
@Override
protected <T> Tree<T> empty() {
return Tree.empty();
}
@Override
protected <T> Tree.Node<T> of(T element) {
return Tree.of(element);
}
@SuppressWarnings("varargs")
@SafeVarargs
@Override
protected final <T> Tree<T> of(T... elements) {
return Tree.ofAll(List.of(elements));
}
@Override
protected <T> Tree<T> ofAll(Iterable<? extends T> elements) {
return Tree.ofAll(elements);
}
@Override
protected <T extends Comparable<? super T>> Tree<T> ofJavaStream(java.util.stream.Stream<? extends T> javaStream) {
return Tree.ofAll(javaStream);
}
@Override
protected Tree<Boolean> ofAll(boolean... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Byte> ofAll(byte... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Character> ofAll(char... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Double> ofAll(double... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Float> ofAll(float... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Integer> ofAll(int... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Long> ofAll(long... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected Tree<Short> ofAll(short... elements) {
return Tree.ofAll(List.ofAll(elements));
}
@Override
protected <T> Tree<T> tabulate(int n, Function<? super Integer, ? extends T> f) {
return Tree.tabulate(n, f);
}
@Override
protected <T> Tree<T> fill(int n, Supplier<? extends T> s) {
return Tree.fill(n, s);
}
@Override
protected boolean useIsEqualToInsteadOfIsSameAs() {
return true;
}
@Override
protected int getPeekNonNilPerformingAnAction() {
return 1;
}
@SuppressWarnings("varargs")
@SafeVarargs
protected final <T> Tree.Node<T> $(T value, Tree.Node<T>... children) {
return Tree.of(value, children);
}
// -- static narrow
@Test
public void shouldNarrowTree() {
final Tree<Double> doubles = of(1.0d);
final Tree<Number> numbers = Tree.narrow(doubles);
final boolean actual = numbers.contains(new BigDecimal("2.0"));
assertThat(actual).isFalse();
}
// -- Tree test
@Test
public void shouldInstantiateTreeBranchWithOf() {
final Tree<Integer> actual = Tree.of(1, Tree.of(2), Tree.of(3));
final Tree<Integer> expected = new Tree.Node<>(1, List.of(new Tree.Node<>(2, List.empty()), new Tree.Node<>(3, List.empty())));
assertThat(actual).isEqualTo(expected);
}
// -- Leaf test
@Test
public void shouldInstantiateTreeLeafWithOf() {
final Tree<Integer> actual = Tree.of(1);
final Tree<Integer> expected = new Tree.Node<>(1, List.empty());
assertThat(actual).isEqualTo(expected);
}
// -- Node test
@Test
public void shouldCreateANodeWithoutChildren() {
new Tree.Node<>(1, List.empty());
}
@Test(expected = InvalidObjectException.class)
public void shouldNotCallReadObjectOnNodeInstance() throws Throwable {
Serializables.callReadObject(tree);
}
// -- getValue
@Test(expected = UnsupportedOperationException.class)
public void shouldNotGetValueOfNil() {
Tree.empty().getValue();
}
@Test
public void shouldNotGetValueOfNonNil() {
assertThat(tree.get()).isEqualTo(1);
}
// -- size
@Test
public void shouldCalculateSizeOfALeaf() {
assertThat($(0).size()).isEqualTo(1);
}
@Test
public void shouldCalculateSizeOfNestedNodes() {
assertThat(tree.size()).isEqualTo(9);
}
// -- isEmpty
@Test
public void shouldIdentifyNilAsEmpty() {
assertThat(Tree.empty().isEmpty()).isTrue();
}
@Test
public void shouldIdentifyNonNilAsNotEmpty() {
assertThat(tree.isEmpty()).isFalse();
}
// -- isLeaf
@Test
public void shouldIdentifyLeafAsLeaf() {
assertThat($(0).isLeaf()).isTrue();
}
@Test
public void shouldIdentifyNonLeafAsNonLeaf() {
assertThat(tree.isLeaf()).isFalse();
}
@Test
public void shouldIdentifyNilAsNonLeaf() {
assertThat(Tree.empty().isLeaf()).isFalse();
}
// -- isBranch
@Test
public void shouldIdentifyLeafAsNonBranch() {
assertThat($(0).isBranch()).isFalse();
}
@Test
public void shouldIdentifyNonLeafAsBranch() {
assertThat(tree.isBranch()).isTrue();
}
@Test
public void shouldIdentifyNilAsNonBranch() {
assertThat(Tree.empty().isBranch()).isFalse();
}
// -- getChildren
@Test
public void shouldGetChildrenOfLeaf() {
assertThat($(0).getChildren()).isEqualTo(List.empty());
}
@Test
public void shouldGetChildrenOfBranch() {
final List<? extends Tree<Integer>> children = tree.getChildren();
assertThat(children.length()).isEqualTo(2);
assertThat(children.get(0).toLispString()).isEqualTo("(2 (4 7) 5)");
assertThat(children.get(1).toLispString()).isEqualTo("(3 (6 8 9))");
}
@Test
public void shouldIGetChildrenOfNil() {
assertThat(Tree.empty().getChildren()).isEqualTo(List.empty());
}
// -- branchCount
@Test
public void shouldCountBranchesOfNil() {
assertThat(Tree.empty().branchCount()).isEqualTo(0);
}
@Test
public void shouldCountBranchesOfNonNil() {
assertThat(tree.branchCount()).isEqualTo(5);
}
// -- leafCount
@Test
public void shouldCountLeavesOfNil() {
assertThat(Tree.empty().leafCount()).isEqualTo(0);
}
@Test
public void shouldCountLeavesOfNonNil() {
assertThat(tree.leafCount()).isEqualTo(4);
}
// -- nodeCount
@Test
public void shouldCountNodesOfNil() {
assertThat(Tree.empty().nodeCount()).isEqualTo(0);
}
@Test
public void shouldCountNodesOfNonNil() {
assertThat(tree.nodeCount()).isEqualTo(9);
}
// -- contains
@Test
public void shouldNotFindNodeInNil() {
assertThat(Tree.empty().contains(1)).isFalse();
}
@Test
public void shouldFindExistingNodeInNonNil() {
assertThat(tree.contains(5)).isTrue();
}
@Test
public void shouldNotFindNonExistingNodeInNonNil() {
assertThat(tree.contains(0)).isFalse();
}
// -- flatMap
@Test
public void shouldFlatMapEmptyTree() {
assertThat(Tree.empty().flatMap(t -> Tree.of(1))).isEqualTo(Tree.empty());
}
@Test
public void shouldFlatMapNonEmptyTree() {
final Tree.Node<Integer> testee = $(1, $(2), $(3));
final Tree<Integer> actual = testee.flatMap(i -> $(i, $(i), $(i)));
final Tree<Integer> expected = $(1, $(1), $(1), $(2, $(2), $(2)), $(3, $(3), $(3)));
assertThat(actual).isEqualTo(expected);
}
@Test
public void shouldFlatMapNonEmptyByExpandingElements() {
assertThat(of(1, 2, 3).flatMap(i -> {
if (i == 1) {
return of(1, 2, 3);
} else if (i == 2) {
return of(4, 5);
} else {
return of(6);
}
})).isEqualTo($(1, $(2), $(3), $(4, $(5)), $(6)));
}
@Test
public void shouldFlatMapNonEmptyInTheRightOrder() {
final AtomicInteger seq = new AtomicInteger(0);
final Tree<Integer> actualInts = $(0, $(1), $(2))
.flatMap(ignored -> of(seq.getAndIncrement(), seq.getAndIncrement()));
final Tree<Integer> expectedInts = $(0, $(1), $(2, $(3)), $(4, $(5)));
assertThat(actualInts).isEqualTo(expectedInts);
}
// -- iterator
@Override
@Test
public void shouldNotHasNextWhenNilIterator() {
assertThat(Tree.empty().iterator().hasNext()).isFalse();
}
@Override
@Test(expected = NoSuchElementException.class)
public void shouldThrowOnNextWhenNilIterator() {
Tree.empty().iterator().next();
}
@Override
@Test
public void shouldIterateFirstElementOfNonNil() {
assertThat(tree.iterator().next()).isEqualTo(1);
}
@Override
@Test
public void shouldFullyIterateNonNil() {
final int length = List
.of(1, 2, 4, 7, 5, 3, 6, 8, 9)
.zip(tree)
.filter(t -> Objects.equals(t._1, t._2))
.length();
assertThat(length).isEqualTo(9);
}
// -- map
@Test
public void shouldMapEmpty() {
assertThat(Tree.empty().map(i -> i)).isEqualTo(Tree.empty());
}
@Test
public void shouldMapTree() {
assertThat(tree.map(i -> (char) (i + 64)).toLispString()).isEqualTo("(A (B (D G) E) (C (F H I)))");
}
// -- replace
@Test
public void shouldReplaceNullInEmpty() {
assertThat(Tree.empty().replace(null, null)).isEmpty();
}
@Test
public void shouldReplaceFirstOccurrenceUsingDepthFirstSearchInNonEmptyTree() {
// 1 1
// / \ -> / \
// 2 3 99 3
final Tree<Integer> testee = Tree.of(1, Tree.of(2), Tree.of(3));
final Tree<Integer> actual = testee.replace(2, 99);
final Tree<Integer> expected = Tree.of(1, Tree.of(99), Tree.of(3));
assertThat(actual).isEqualTo(expected);
}
@Test
public void shouldNotReplaceAnyElementIfThereIsNoOccurrenceInNonEmptyTree() {
final Tree<Integer> testee = Tree.of(1, Tree.of(2), Tree.of(3));
final Tree<Integer> actual = testee.replace(4, 99);
assertThat(actual).isEqualTo(testee);
}
// -- values()
@Test
public void shouldTraverseValuesOfEmptyTree() {
assertThat(Tree.empty().values()).isEqualTo(empty());
}
// -- values(Order)
@Test
public void shouldTraverseValuesUsingPreOrder() {
assertThat(tree.values(Tree.Order.PRE_ORDER)).isEqualTo(Stream.of(1, 2, 4, 7, 5, 3, 6, 8, 9));
}
@Test
public void shouldTraverseValuesUsingInOrder() {
assertThat(tree.values(Tree.Order.IN_ORDER)).isEqualTo(Stream.of(7, 4, 2, 5, 1, 8, 6, 9, 3));
}
@Test
public void shouldTraverseValuesUsingPostOrder() {
assertThat(tree.values(Tree.Order.POST_ORDER)).isEqualTo(Stream.of(7, 4, 5, 2, 8, 9, 6, 3, 1));
}
@Test
public void shouldTraverseValuesUsingLevelOrder() {
assertThat(tree.values(Tree.Order.LEVEL_ORDER)).isEqualTo(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9));
}
// -- unzip
@Test
public void shouldUnzipEmptyTree() {
assertThat(Tree.empty().unzip(t -> Tuple.of(t, t))).isEqualTo(Tuple.of(Tree.empty(), Tree.empty()));
}
@Test
public void shouldUnzipNonEmptyTree() {
final Tree<Integer> testee = $(1, $(2), $(3));
final Tuple2<Tree<Integer>, Tree<Integer>> actual = testee.unzip(i -> Tuple.of(i, -i));
final Tuple2<Tree<Integer>, Tree<Integer>> expected = Tuple.of($(1, $(2), $(3)), $(-1, $(-2), $(-3)));
assertThat(actual).isEqualTo(expected);
}
@Test
public void shouldUnzip3EmptyTree() {
assertThat(Tree.empty().unzip3(t -> Tuple.of(t, t, t))).isEqualTo(Tuple.of(Tree.empty(), Tree.empty(), Tree.empty()));
}
@Test
public void shouldUnzip3NonEmptyTree() {
final Tree<Integer> testee = $(1, $(2), $(3));
final Tuple3<Tree<Integer>, Tree<Integer>, Tree<Integer>> actual = testee.unzip3(i -> Tuple.of(i, -i, -i - 1));
final Tuple3<Tree<Integer>, Tree<Integer>, Tree<Integer>> expected = Tuple.of($(1, $(2), $(3)), $(-1, $(-2), $(-3)), $(-2, $(-3), $(-4)));
assertThat(actual).isEqualTo(expected);
}
// equals
@SuppressWarnings("EqualsWithItself")
@Test
public void shouldBeAwareThatTwoTreesOfSameInstanceAreEqual() {
// DEV_NOTE: intentionally not called `assertThat(Tree.empty()).isEqualTo(Tree.empty())`
assertThat(Tree.empty().equals(Tree.empty())).isTrue();
}
@Test
public void shouldBeAwareOfTwoDifferentEqualTrees() {
assertThat($(0).equals($(0))).isTrue();
}
@Test
public void shouldBeAwareThatTreeNotEqualsObject() {
assertThat($(0)).isNotEqualTo(new Object());
}
// hashCode
@Test
public void shouldBeAwareThatHashCodeOfEmptyIsOne() {
assertThat(Tree.empty().hashCode()).isEqualTo(1);
}
@Test
public void shouldBeAwareThatHashCodeOfLeafIsGreaterThanOne() {
assertThat($(0).hashCode()).isGreaterThan(1);
}
// -- transform()
@Test
public void shouldTransform() {
final String transformed = $(42, $(2), $(3)).transform(v -> String.valueOf(v.get()));
assertThat(transformed).isEqualTo("42");
}
// toString
@Test
public void shouldReturnStringRepresentationOfEmpty() {
assertThat(Tree.empty().toString()).isEqualTo("Tree()");
}
@Test
public void shouldReturnLispStringRepresentationOfNode() {
assertThat(tree.toString()).isEqualTo("Tree(1, 2, 4, 7, 5, 3, 6, 8, 9)");
}
// -- toLispString
@Test
public void shouldConvertEmptyToLispString() {
assertThat(Tree.empty().toLispString()).isEqualTo("()");
}
@Test
public void shouldConvertNonEmptyToLispString() {
assertThat(tree.toLispString()).isEqualTo("(1 (2 (4 7) 5) (3 (6 8 9)))");
}
// draw
@Test
public void shouldReturnDrawStringOfEmpty() {
assertThat(Tree.empty().draw()).isEqualTo("▣");
}
@Test
public void shouldReturnDrawStringOfNode() {
assertThat(tree.draw()).isEqualTo("1\n" +
"├──2\n" +
"│ ├──4\n" +
"│ │ └──7\n" +
"│ └──5\n" +
"└──3\n" +
" └──6\n" +
" ├──8\n" +
" └──9");
}
// -- serialization
@Test
public void shouldSerializeDeserializeComplexTree() {
final Object actual = Serializables.deserialize(Serializables.serialize(tree));
assertThat(actual).isEqualTo(tree);
}
// -- toVector
@Test
public void shouldReturnSelfOnConvertToTree() {
final Value<Integer> value = of(1, 2, 3);
assertThat(value.toTree()).isSameAs(value);
}
// ~~~~~~~ DISABLED TESTS ~~~~~~~
// -- distinctBy(Comparator)
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenDistinctByComparatorEmptyTraversable() {
// TODO: remove this overridden method with #1826
}
// -- distinctBy(Function)
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenDistinctByFunctionEmptyTraversable() {
// TODO: remove this overridden method with #1826
}
// -- drop
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenDropZeroCount() {
// Tree.drop() returns a Seq
}
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenDropNegativeCount() {
// Tree.drop() returns a Seq
}
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenEmptyDropOne() {
// Tree.drop() returns a Seq
}
// -- dropRight
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenDropRightZeroCount() {
// Tree.dropRight() returns a Seq
}
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenDropRightNegativeCount() {
// Tree.dropRight() returns a Seq
}
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenEmptyDropRightOne() {
// Tree.dropRight() returns a Seq
}
// -- dropUntil
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenEmptyDropUntil() {
// Tree.dropUntil returns a Seq
}
// -- dropWhile
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenEmptyDropWhile() {
// Tree.dropWhile returns a Seq
}
// -- filter
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenFilteringEmptyTraversable() {
// TODO: remove this overridden method with #1826
}
// -- take
@Ignore
@Override
@Test
public void shouldReturnSameInstanceIfTakeAll() {
// Tree.take returns Seq
}
// -- takeRight
@Ignore
@Override
@Test
public void shouldReturnSameInstanceIfTakeRightAll() {
// Tree.takeRight returns Seq
}
// -- takeUntil
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenEmptyTakeUntil() {
// Tree.takeUntil() returns a Seq
}
// -- takeWhile
@Ignore
@Override
@Test
public void shouldReturnSameInstanceWhenEmptyTakeWhile() {
// Tree.takeWhile() returns a Seq
}
// -- spliterator
@Test
public void shouldHaveOrderedSpliterator() {
assertThat(of(1, 2, 3).spliterator().hasCharacteristics(Spliterator.ORDERED)).isTrue();
}
@Test
public void shouldNotHaveSortedSpliterator() {
assertThat(of(1, 2, 3).spliterator().hasCharacteristics(Spliterator.SORTED)).isFalse();
}
@Test
public void shouldHaveSizedSpliterator() {
assertThat(of(1, 2, 3).spliterator().hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED)).isTrue();
}
@Test
public void shouldNotHaveDistinctSpliterator() {
assertThat(of(1, 2, 3).spliterator().hasCharacteristics(Spliterator.DISTINCT)).isFalse();
}
@Test
public void shouldReturnSizeWhenSpliterator() {
assertThat(of(1, 2, 3).spliterator().getExactSizeIfKnown()).isEqualTo(3);
}
// -- isSequential()
@Test
public void shouldReturnTrueWhenIsSequentialCalled() {
assertThat(of(1, 2, 3).isSequential()).isTrue();
}
}