You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
6.3 KiB

/*
* Copyright (C) 2018 The Dagger Authors.
*
* Licensed 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 dagger.functional.producers.cancellation;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
/**
* Helper for testing producers.
*
* <p>Maintains a set of nodes (futures mapped to names) representing the results of different
* producer nodes and allows those nodes to be "started" (when returned from a producer method),
* completed, and cancelled, as well as to be queried for their state. Additionally, provides
* assertions about the state of nodes.
*/
final class ProducerTester {
private final Map<String, TestFuture> futures = new HashMap<>();
/** Starts the given node. */
ListenableFuture<String> start(String node) {
return getOrCreate(node).start();
}
private TestFuture getOrCreate(String node) {
TestFuture result = futures.get(node);
if (result == null) {
result = new TestFuture(node);
futures.put(node, result);
}
return result;
}
/** Returns whether or not the given node has been started. */
boolean isStarted(String node) {
return futures.containsKey(node) && futures.get(node).isStarted();
}
/** Completes of the given nodes. */
void complete(String... nodes) {
for (String node : nodes) {
getOrCreate(node).complete();
}
}
/** Returns whether or not the given node has been cancelled. */
boolean isCancelled(String node) {
TestFuture future = futures.get(node);
return future != null && future.isCancelled();
}
/** Asserts that the given nodes have been started. */
Only assertStarted(String... nodes) {
return assertAboutNodes(STARTED, nodes);
}
/** Asserts that the given nodes have been cancelled. */
Only assertCancelled(String... nodes) {
return assertAboutNodes(CANCELLED, nodes);
}
/** Asserts that the given nodes have not been started. */
Only assertNotStarted(String... nodes) {
return assertAboutNodes(not(STARTED), nodes);
}
/** Asserts that the given nodes have not been cancelled. */
Only assertNotCancelled(String... nodes) {
return assertAboutNodes(not(CANCELLED), nodes);
}
/** Asserts that no nodes in this tester have been started. */
void assertNoStartedNodes() {
for (TestFuture future : futures.values()) {
assertWithMessage("%s is started", future).that(future.isStarted()).isFalse();
}
}
private Only assertAboutNodes(Predicate<? super TestFuture> assertion, String... nodes) {
ImmutableSet.Builder<TestFuture> builder = ImmutableSet.builder();
for (String node : nodes) {
TestFuture future = getOrCreate(node);
assertWithMessage("%s is %s", future, assertion).that(assertion.test(future)).isTrue();
builder.add(future);
}
return new Only(builder.build(), assertion);
}
/**
* Fluent class for making a previous assertion more strict by specifying that whatever was
* asserted should be true only for the specified nodes and not for any others.
*/
final class Only {
private final ImmutableSet<TestFuture> expected;
private final Predicate<? super TestFuture> assertion;
Only(ImmutableSet<TestFuture> expected, Predicate<? super TestFuture> assertion) {
this.expected = checkNotNull(expected);
this.assertion = checkNotNull(assertion);
}
/**
* Asserts that the previous assertion was not true for any node other than those that were
* specified.
*/
void only() {
for (TestFuture future : futures.values()) {
if (!expected.contains(future)) {
assertWithMessage("%s is %s", future, assertion).that(assertion.test(future)).isFalse();
}
}
}
}
/**
* A simple future for testing that can be marked as having been started and which can be
* completed with a result.
*/
private static final class TestFuture extends AbstractFuture<String> {
private final String name;
private volatile boolean started;
private TestFuture(String name) {
this.name = checkNotNull(name);
}
/** Marks this future as having been started and returns it. */
TestFuture start() {
this.started = true;
return this;
}
/** Returns whether or not this future's task was started. */
boolean isStarted() {
return started;
}
/** Completes this future's task by setting a value for it. */
public void complete() {
super.set("completed");
}
@Override
public String toString() {
return name;
}
}
private static final Predicate<TestFuture> STARTED =
new Predicate<TestFuture>() {
@Override
public boolean test(TestFuture future) {
return future.isStarted();
}
@Override
public String toString() {
return "started";
}
};
private static final Predicate<TestFuture> CANCELLED =
new Predicate<TestFuture>() {
@Override
public boolean test(TestFuture future) {
return future.isCancelled();
}
@Override
public String toString() {
return "cancelled";
}
};
/** Version of Predicates.not with a toString() that's nicer for our assertion error messages. */
private static <T> Predicate<T> not(final Predicate<T> predicate) {
return new Predicate<T>() {
@Override
public boolean test(T input) {
return !predicate.test(input);
}
@Override
public String toString() {
return "not " + predicate;
}
};
}
}