• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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