1 /* 2 * Copyright (C) 2018 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.functional.producers.cancellation; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import com.google.common.collect.ImmutableSet; 23 import com.google.common.util.concurrent.AbstractFuture; 24 import com.google.common.util.concurrent.ListenableFuture; 25 import java.util.HashMap; 26 import java.util.Map; 27 import java.util.function.Predicate; 28 29 /** 30 * Helper for testing producers. 31 * 32 * <p>Maintains a set of nodes (futures mapped to names) representing the results of different 33 * producer nodes and allows those nodes to be "started" (when returned from a producer method), 34 * completed, and cancelled, as well as to be queried for their state. Additionally, provides 35 * assertions about the state of nodes. 36 */ 37 final class ProducerTester { 38 39 private final Map<String, TestFuture> futures = new HashMap<>(); 40 41 /** Starts the given node. */ start(String node)42 ListenableFuture<String> start(String node) { 43 return getOrCreate(node).start(); 44 } 45 getOrCreate(String node)46 private TestFuture getOrCreate(String node) { 47 TestFuture result = futures.get(node); 48 if (result == null) { 49 result = new TestFuture(node); 50 futures.put(node, result); 51 } 52 return result; 53 } 54 55 /** Returns whether or not the given node has been started. */ isStarted(String node)56 boolean isStarted(String node) { 57 return futures.containsKey(node) && futures.get(node).isStarted(); 58 } 59 60 /** Completes of the given nodes. */ complete(String... nodes)61 void complete(String... nodes) { 62 for (String node : nodes) { 63 getOrCreate(node).complete(); 64 } 65 } 66 67 /** Returns whether or not the given node has been cancelled. */ isCancelled(String node)68 boolean isCancelled(String node) { 69 TestFuture future = futures.get(node); 70 return future != null && future.isCancelled(); 71 } 72 73 /** Asserts that the given nodes have been started. */ assertStarted(String... nodes)74 Only assertStarted(String... nodes) { 75 return assertAboutNodes(STARTED, nodes); 76 } 77 78 /** Asserts that the given nodes have been cancelled. */ assertCancelled(String... nodes)79 Only assertCancelled(String... nodes) { 80 return assertAboutNodes(CANCELLED, nodes); 81 } 82 83 /** Asserts that the given nodes have not been started. */ assertNotStarted(String... nodes)84 Only assertNotStarted(String... nodes) { 85 return assertAboutNodes(not(STARTED), nodes); 86 } 87 88 /** Asserts that the given nodes have not been cancelled. */ assertNotCancelled(String... nodes)89 Only assertNotCancelled(String... nodes) { 90 return assertAboutNodes(not(CANCELLED), nodes); 91 } 92 93 /** Asserts that no nodes in this tester have been started. */ assertNoStartedNodes()94 void assertNoStartedNodes() { 95 for (TestFuture future : futures.values()) { 96 assertWithMessage("%s is started", future).that(future.isStarted()).isFalse(); 97 } 98 } 99 assertAboutNodes(Predicate<? super TestFuture> assertion, String... nodes)100 private Only assertAboutNodes(Predicate<? super TestFuture> assertion, String... nodes) { 101 ImmutableSet.Builder<TestFuture> builder = ImmutableSet.builder(); 102 for (String node : nodes) { 103 TestFuture future = getOrCreate(node); 104 assertWithMessage("%s is %s", future, assertion).that(assertion.test(future)).isTrue(); 105 builder.add(future); 106 } 107 return new Only(builder.build(), assertion); 108 } 109 110 /** 111 * Fluent class for making a previous assertion more strict by specifying that whatever was 112 * asserted should be true only for the specified nodes and not for any others. 113 */ 114 final class Only { 115 116 private final ImmutableSet<TestFuture> expected; 117 private final Predicate<? super TestFuture> assertion; 118 Only(ImmutableSet<TestFuture> expected, Predicate<? super TestFuture> assertion)119 Only(ImmutableSet<TestFuture> expected, Predicate<? super TestFuture> assertion) { 120 this.expected = checkNotNull(expected); 121 this.assertion = checkNotNull(assertion); 122 } 123 124 /** 125 * Asserts that the previous assertion was not true for any node other than those that were 126 * specified. 127 */ only()128 void only() { 129 for (TestFuture future : futures.values()) { 130 if (!expected.contains(future)) { 131 assertWithMessage("%s is %s", future, assertion).that(assertion.test(future)).isFalse(); 132 } 133 } 134 } 135 } 136 137 /** 138 * A simple future for testing that can be marked as having been started and which can be 139 * completed with a result. 140 */ 141 private static final class TestFuture extends AbstractFuture<String> { 142 143 private final String name; 144 private volatile boolean started; 145 TestFuture(String name)146 private TestFuture(String name) { 147 this.name = checkNotNull(name); 148 } 149 150 /** Marks this future as having been started and returns it. */ start()151 TestFuture start() { 152 this.started = true; 153 return this; 154 } 155 156 /** Returns whether or not this future's task was started. */ isStarted()157 boolean isStarted() { 158 return started; 159 } 160 161 /** Completes this future's task by setting a value for it. */ complete()162 public void complete() { 163 super.set("completed"); 164 } 165 166 @Override toString()167 public String toString() { 168 return name; 169 } 170 } 171 172 private static final Predicate<TestFuture> STARTED = 173 new Predicate<TestFuture>() { 174 @Override 175 public boolean test(TestFuture future) { 176 return future.isStarted(); 177 } 178 179 @Override 180 public String toString() { 181 return "started"; 182 } 183 }; 184 185 private static final Predicate<TestFuture> CANCELLED = 186 new Predicate<TestFuture>() { 187 @Override 188 public boolean test(TestFuture future) { 189 return future.isCancelled(); 190 } 191 192 @Override 193 public String toString() { 194 return "cancelled"; 195 } 196 }; 197 198 /** Version of Predicates.not with a toString() that's nicer for our assertion error messages. */ not(final Predicate<T> predicate)199 private static <T> Predicate<T> not(final Predicate<T> predicate) { 200 return new Predicate<T>() { 201 @Override 202 public boolean test(T input) { 203 return !predicate.test(input); 204 } 205 206 @Override 207 public String toString() { 208 return "not " + predicate; 209 } 210 }; 211 } 212 } 213