summaryrefslogtreecommitdiffstats
path: root/vespajlib/src/test/java/com/yahoo/yolean
diff options
context:
space:
mode:
Diffstat (limited to 'vespajlib/src/test/java/com/yahoo/yolean')
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java81
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java106
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java101
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java146
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java18
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java231
-rw-r--r--vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java21
7 files changed, 704 insertions, 0 deletions
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java
new file mode 100644
index 00000000000..53cf3efe363
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java
@@ -0,0 +1,81 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.NoSuchFileException;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author bratseth
+ */
+public class ExceptionsTestCase {
+
+ @Test
+ public void testFindCause() {
+ IllegalArgumentException e1 = new IllegalArgumentException();
+ IllegalStateException e2 = new IllegalStateException(e1);
+ RuntimeException e3 = new RuntimeException(e2);
+
+ assertEquals(Optional.of(e3), Exceptions.findCause(e3, RuntimeException.class));
+ assertEquals(Optional.of(e1), Exceptions.findCause(e3, IllegalArgumentException.class));
+ assertEquals(Optional.empty(), Exceptions.findCause(e3, NumberFormatException.class));
+
+ assertEquals(Optional.of(e2), Exceptions.findCause(e2, RuntimeException.class));
+ }
+
+ @Test
+ public void testToMessageStrings() {
+ assertEquals("Blah",Exceptions.toMessageString(new Exception("Blah")));
+ assertEquals("Blah", Exceptions.toMessageString(new Exception(new Exception("Blah"))));
+ assertEquals("Exception",Exceptions.toMessageString(new Exception()));
+ assertEquals("Foo: Blah",Exceptions.toMessageString(new Exception("Foo",new Exception(new IllegalArgumentException("Blah")))));
+ assertEquals("Foo",Exceptions.toMessageString(new Exception("Foo",new Exception("Foo"))));
+ assertEquals("Foo: Exception",Exceptions.toMessageString(new Exception("Foo",new Exception())));
+ assertEquals("Foo",Exceptions.toMessageString(new Exception(new Exception("Foo"))));
+ }
+
+ @Test
+ public void testUnchecks() {
+ try {
+ Exceptions.uncheck(this::throwNoSuchFileException);
+ } catch (UncheckedIOException e) {
+ assertEquals("filename", e.getCause().getMessage());
+ }
+
+ try {
+ Exceptions.uncheck(this::throwNoSuchFileException, "additional %s", "info");
+ } catch (UncheckedIOException e) {
+ assertEquals("additional info", e.getMessage());
+ }
+
+ try {
+ int i = Exceptions.uncheck(this::throwNoSuchFileExceptionSupplier);
+ } catch (UncheckedIOException e) {
+ assertEquals("filename", e.getCause().getMessage());
+ }
+
+ try {
+ int i = Exceptions.uncheck(this::throwNoSuchFileExceptionSupplier, "additional %s", "info");
+ } catch (UncheckedIOException e) {
+ assertEquals("additional info", e.getMessage());
+ }
+
+ Exceptions.uncheckAndIgnore(this::throwNoSuchFileException, NoSuchFileException.class);
+ assertNull(Exceptions.uncheckAndIgnore(this::throwNoSuchFileExceptionSupplier, NoSuchFileException.class));
+ }
+
+ private void throwNoSuchFileException() throws IOException {
+ throw new NoSuchFileException("filename");
+ }
+
+ private int throwNoSuchFileExceptionSupplier() throws IOException {
+ throw new NoSuchFileException("filename");
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java
new file mode 100644
index 00000000000..3f2526172a9
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java
@@ -0,0 +1,106 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author baldersheim
+ * @since 5.2
+ */
+public class CopyOnWriteHashMapTest {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ assertEquals(0, map.size());
+ assertEquals(true, map.isEmpty());
+ assertEquals(false, map.containsKey("fooKey"));
+ assertEquals(false, map.containsValue("fooVal"));
+ assertNull(map.get("fooKey"));
+ assertNull(map.remove("fooKey"));
+ assertEquals(0, map.keySet().size());
+ assertEquals(0, map.entrySet().size());
+ assertEquals(0, map.values().size());
+
+ map.put("fooKey", "fooVal");
+ assertEquals(1, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(true, map.containsKey("fooKey"));
+ assertEquals(true, map.containsValue("fooVal"));
+ assertEquals("fooVal", map.get("fooKey"));
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+
+ map.put("barKey", "barVal");
+ assertEquals(2, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(true, map.containsKey("fooKey"));
+ assertEquals(true, map.containsKey("barKey"));
+ assertEquals(true, map.containsValue("fooVal"));
+ assertEquals(true, map.containsValue("barVal"));
+ assertEquals("fooVal", map.get("fooKey"));
+ assertEquals("barVal", map.get("barKey"));
+ assertEquals(2, map.keySet().size());
+ assertEquals(2, map.entrySet().size());
+ assertEquals(2, map.values().size());
+
+ assertEquals("fooVal", map.remove("fooKey"));
+ assertEquals(1, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(false, map.containsKey("fooKey"));
+ assertEquals(true, map.containsKey("barKey"));
+ assertEquals(false, map.containsValue("fooVal"));
+ assertEquals(true, map.containsValue("barVal"));
+ assertNull(map.get("fooKey"));
+ assertEquals("barVal", map.get("barKey"));
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+ }
+
+ @Test
+ public void requireThatEntrySetDoesNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ Map.Entry<String, String> entry = it.next();
+ assertEquals("fooKey", entry.getKey());
+ assertEquals("fooVal", entry.getValue());
+ }
+
+ @Test
+ public void requireThatKeySetDoesNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<String> it = map.keySet().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ assertEquals("fooKey", it.next());
+ }
+
+ @Test
+ public void requireThatValuesDoNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<String> it = map.values().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ assertEquals("fooVal", it.next());
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java
new file mode 100644
index 00000000000..7f2f49c75f2
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/MemoizedTest.java
@@ -0,0 +1,101 @@
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+/**
+ * @author jonmv
+ */
+public class MemoizedTest {
+
+ final Phaser phaser = new Phaser();
+ final int threads = 128;
+
+ @Test
+ public void test() throws ExecutionException, InterruptedException {
+ var lazy = new Memoized<>(new OnceSupplier(), OnceCloseable::close);
+ phaser.register(); // test thread
+ phaser.register(); // whoever calls the factory
+
+ Phaser latch = new Phaser(threads + 1);
+ ExecutorService executor = Executors.newFixedThreadPool(threads);
+ List<Future<?>> futures = new ArrayList<>();
+ for (int i = 0; i < 128; i++) {
+ futures.add(executor.submit(() -> {
+ latch.arriveAndAwaitAdvance();
+ lazy.get().rendezvous();
+ while (true) lazy.get();
+ }));
+ }
+
+ // All threads waiting for latch, will race to factory
+ latch.arriveAndAwaitAdvance();
+
+ // One thread waiting in factory, the others are blocked, will go to rendezvous
+ phaser.arriveAndAwaitAdvance();
+
+ // All threads waiting in rendezvous, will repeatedly get until failure
+ phaser.arriveAndAwaitAdvance();
+
+ // Unsynchronized close should be detected by all threads
+ lazy.close();
+
+ // Close should carry through only once
+ lazy.close();
+
+ assertEquals("already closed",
+ assertThrows(IllegalStateException.class, lazy::get).getMessage());
+
+ for (Future<?> future : futures)
+ assertEquals("java.lang.IllegalStateException: already closed",
+ assertThrows(ExecutionException.class, future::get).getMessage());
+
+ executor.shutdown();
+ }
+
+ @Test
+ public void closeBeforeFirstGet() throws Exception {
+ OnceSupplier supplier = new OnceSupplier();
+ Memoized<OnceCloseable, ?> lazy = Memoized.of(supplier);
+ lazy.close();
+ assertEquals("already closed",
+ assertThrows(IllegalStateException.class, lazy::get).getMessage());
+ lazy.close();
+ assertFalse(supplier.initialized.get());
+ }
+
+ class OnceSupplier implements Supplier<OnceCloseable> {
+ final AtomicBoolean initialized = new AtomicBoolean();
+ @Override public OnceCloseable get() {
+ phaser.arriveAndAwaitAdvance();
+ if ( ! initialized.compareAndSet(false, true)) fail("initialized more than once");
+ phaser.bulkRegister(threads - 1); // register all the threads who didn't get the factory
+ return new OnceCloseable();
+ }
+ }
+
+ class OnceCloseable implements AutoCloseable {
+ final AtomicBoolean closed = new AtomicBoolean();
+ @Override public void close() {
+ if ( ! closed.compareAndSet(false, true)) fail("closed more than once");
+ }
+ void rendezvous() {
+ phaser.arriveAndAwaitAdvance();
+ }
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java
new file mode 100644
index 00000000000..c2edaf1fb00
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java
@@ -0,0 +1,146 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ */
+public class ThreadRobustListTestCase {
+
+ private final static int NUM_THREADS = 64;
+ private final static int NUM_ITEMS_TO_WRITE = 1000000;
+ private final static int NUM_TIMES_TO_READ = 10;
+
+ @Test
+ public void requireThatListIsThreadRobust() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(NUM_THREADS);
+ final ThreadRobustList<Integer> sharedList = new ThreadRobustList<>();
+
+ List<Callable<Boolean>> tasks = new ArrayList<>(NUM_THREADS);
+ tasks.add(new WriterTask(latch, sharedList));
+ for (int i = 1; i < NUM_THREADS; ++i) {
+ tasks.add(new ReaderTask(latch, sharedList));
+ }
+ for (Future<Boolean> result : Executors.newFixedThreadPool(NUM_THREADS).invokeAll(tasks)) {
+ assertTrue(result.get(60, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ assertTrue(lst.isEmpty());
+ assertFalse(lst.iterator().hasNext());
+
+ Object foo = new Object();
+ lst.add(foo);
+ assertFalse(lst.isEmpty());
+ Iterator<Object> it = lst.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertFalse(it.hasNext());
+
+ Object bar = new Object();
+ lst.add(bar);
+ assertFalse(lst.isEmpty());
+ assertNotNull(it = lst.iterator());
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatIteratorNextThrowsNoSuchElementExceptionWhenDone() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ Iterator<Object> it = lst.iterator();
+ assertFalse(it.hasNext());
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatIteratorRemoveIsNotSupported() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ Object obj = new Object();
+ lst.add(obj);
+ Iterator<Object> it = lst.iterator();
+ assertTrue(it.hasNext());
+ assertSame(obj, it.next());
+ try {
+ it.remove();
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ private static class WriterTask implements Callable<Boolean> {
+
+ final CountDownLatch latch;
+ final ThreadRobustList<Integer> sharedList;
+
+ WriterTask(CountDownLatch latch, ThreadRobustList<Integer> sharedList) {
+ this.latch = latch;
+ this.sharedList = sharedList;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ latch.countDown();
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ for (int i = 0; i < NUM_ITEMS_TO_WRITE; ++i) {
+ sharedList.add(i);
+ }
+ return true;
+ }
+ }
+
+ private static class ReaderTask implements Callable<Boolean> {
+
+ final CountDownLatch latch;
+ final ThreadRobustList<Integer> sharedList;
+
+ ReaderTask(CountDownLatch latch, ThreadRobustList<Integer> sharedList) {
+ this.latch = latch;
+ this.sharedList = sharedList;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ latch.countDown();
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ for (int i = 0; i < NUM_TIMES_TO_READ; ++i) {
+ Iterator<Integer> it = sharedList.iterator();
+ for (int j = 0; it.hasNext(); ++j) {
+ assertEquals(j, it.next().intValue());
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java
new file mode 100644
index 00000000000..66a27235088
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/system/CatchSignalsTestCase.java
@@ -0,0 +1,18 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.system;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author arnej27959
+ */
+public class CatchSignalsTestCase {
+
+ @Test
+ public void testThatSetupCompiles() {
+ CatchSignals.setup(new AtomicBoolean(false));
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java
new file mode 100644
index 00000000000..3019b646867
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java
@@ -0,0 +1,231 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author bratseth
+ */
+public class TraceNodeTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TraceNode node = new TraceNode(null, 6);
+ assertNull(node.payload());
+ assertEquals(6, node.timestamp());
+ assertFalse(node.children().iterator().hasNext());
+ assertFalse(node.descendants(Object.class).iterator().hasNext());
+ assertTrue(node.isRoot());
+ assertNull(node.parent());
+ assertSame(node, node.root());
+ }
+
+ @Test
+ public void requireThatToStringIsReadable() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 1))
+ .add(new TraceNode("b", 2)
+ .add(new TraceNode("c", 3)));
+ assertEquals("[ [ a b [ c ] ] ]", trace.toString());
+ }
+
+ @Test
+ public void requireThatPayloadMayBeNull() {
+ TraceNode node = new TraceNode(null, 6);
+ assertNull(node.payload());
+ }
+
+ @Test
+ public void requireThatRootNodesCanBeAdded() {
+ TraceNode parent = new TraceNode(null, 1);
+
+ TraceNode foo = new TraceNode(null, 2);
+ parent.add(foo);
+ assertSame(parent, foo.parent());
+
+ TraceNode bar = new TraceNode(null, 3);
+ parent.add(bar);
+ assertSame(parent, bar.parent());
+
+ Iterator<TraceNode> children = parent.children().iterator();
+ assertTrue(children.hasNext());
+ assertSame(foo, children.next());
+ assertTrue(children.hasNext());
+ assertSame(bar, children.next());
+ assertFalse(children.hasNext());
+
+ Iterator<Object> payloads = parent.descendants(Object.class).iterator();
+ assertFalse(payloads.hasNext());
+ }
+
+ @Test
+ public void requireThatNonRootNodeCanNotBeAdded() {
+ TraceNode foo = new TraceNode(null, 0);
+ TraceNode bar = new TraceNode(null, 0);
+ TraceNode baz = new TraceNode(null, 0);
+ bar.add(baz);
+ try {
+ foo.add(baz);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ assertSame(bar, baz.parent());
+ assertTrue(bar.children().iterator().hasNext());
+ assertFalse(foo.children().iterator().hasNext());
+ }
+
+ @Test
+ public void requireThatChildrenIsNeverNull() {
+ assertNotNull(new TraceNode(null, 69).children());
+ }
+
+ @Test
+ public void requireThatDescendantsIsNeverNull() {
+ assertNotNull(new TraceNode(null, 69).descendants(Object.class));
+ }
+
+ @Test
+ public void requireThatDescendantsOrderIsDepthFirstPrefix() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode("e", 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ Iterator<String> it = trace.descendants(String.class).iterator();
+ assertTrue(it.hasNext());
+ assertEquals("a", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("b", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("c", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("d", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("e", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("f", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("g", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatDescendantsFilterPayloads() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode(69, 0))
+ .add(new TraceNode("b", 0)
+ .add(new TraceNode("c", 0))
+ .add(new TraceNode(new Object(), 0))))
+ .add(new TraceNode("d", 0)
+ .add(new TraceNode("e", 0)));
+
+ Iterator<String> it = trace.descendants(String.class).iterator();
+ assertTrue(it.hasNext());
+ assertEquals("a", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("b", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("c", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("d", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("e", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatVisitorOrderIsDepthFirstPrefix() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode(3, 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ final List<Object> payloads = new ArrayList<>();
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ payloads.add(node.payload());
+ }
+ });
+ assertEquals(Arrays.<Object>asList(null, "a", "b", "c", "d", 3, "f", "g"),
+ payloads);
+ }
+
+ @Test
+ public void requireThatVisitorDoesNotEnterOrLeaveNodesThatHaveNoChildren() {
+ TraceNode trace = new TraceNode(null, 0);
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ fail();
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ fail();
+ }
+ });
+ }
+
+ @Test
+ public void requireThatVisitorEntersAndLeavesNodesThatHaveChildren() {
+ TraceNode trace = new TraceNode("", 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode("e", 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ final StringBuilder out = new StringBuilder();
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ out.append(node.payload());
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ out.append("[");
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ out.append("]");
+ }
+ });
+ assertEquals("[a[bc[de]]f[g]]", out.toString());
+ }
+}
diff --git a/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java
new file mode 100644
index 00000000000..4eaa5b0241e
--- /dev/null
+++ b/vespajlib/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java
@@ -0,0 +1,21 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import org.junit.Test;
+
+/**
+ * @author Simon Thoresen Hult
+ */
+public class TraceVisitorTestCase {
+
+ @Test
+ public void requireThatTraceVisitorCompilesWithOnlyVisitImplemented() {
+ new TraceNode(null, 0).accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+
+ }
+ });
+ }
+}