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.truth.Truth.assertThat; 20 import static java.util.concurrent.TimeUnit.MILLISECONDS; 21 22 import com.google.common.util.concurrent.ListenableFuture; 23 import com.google.common.util.concurrent.MoreExecutors; 24 import dagger.functional.producers.cancellation.CancellationComponent.Dependency; 25 import org.junit.Test; 26 import org.junit.runner.RunWith; 27 import org.junit.runners.JUnit4; 28 29 /** Tests cancellation of tasks in production subcomponents. */ 30 @RunWith(JUnit4.class) 31 public class ProducerSubcomponentCancellationTest { 32 33 private final ProducerTester tester = new ProducerTester(); 34 private final CancellationComponent component = 35 DaggerCancellationComponent.builder() 36 .module(new CancellationModule(tester)) 37 .dependency(new Dependency(tester)) 38 .executor(MoreExecutors.directExecutor()) 39 .build(); 40 private final CancellationSubcomponent subcomponent = 41 component.subcomponentBuilder().module(new CancellationSubcomponentModule(tester)).build(); 42 43 @Test initialState()44 public void initialState() { 45 tester.assertNoStartedNodes(); 46 } 47 48 @Test cancellingSubcomponent_doesNotCancelParent()49 public void cancellingSubcomponent_doesNotCancelParent() throws Exception { 50 ListenableFuture<String> subcomponentEntryPoint = subcomponent.subcomponentEntryPoint(); 51 52 // Subcomponent entry point depends on all leaves from the parent component and on the single 53 // leaf in the subcomponent itself, so they should all have started. 54 tester.assertStarted("leaf1", "leaf2", "leaf3", "subLeaf").only(); 55 56 assertThat(subcomponentEntryPoint.cancel(true)).isTrue(); 57 assertThat(subcomponentEntryPoint.isCancelled()).isTrue(); 58 59 // None of the tasks running in the parent were cancelled. 60 tester.assertNotCancelled("leaf1", "leaf2", "leaf3"); 61 tester.assertCancelled("subLeaf").only(); 62 63 // Finish all the parent tasks to ensure that it can still complete normally. 64 tester.complete( 65 "dependencyFuture", 66 "leaf1", 67 "leaf2", 68 "leaf3", 69 "foo", 70 "bar", 71 "baz", 72 "qux", 73 "entryPoint1", 74 "entryPoint2"); 75 76 assertThat(component.entryPoint1().get(1, MILLISECONDS)).isEqualTo("completed"); 77 assertThat(component.entryPoint2().get().get(1, MILLISECONDS)).isEqualTo("completed"); 78 } 79 80 @Test cancellingSubcomponent_preventsUnstartedNodesFromStarting()81 public void cancellingSubcomponent_preventsUnstartedNodesFromStarting() { 82 ListenableFuture<String> subcomponentEntryPoint = subcomponent.subcomponentEntryPoint(); 83 84 tester.complete("subLeaf"); 85 tester.assertNotStarted("subTask1", "subTask2"); 86 87 subcomponentEntryPoint.cancel(true); 88 89 // Complete the remaining dependencies of subTask1 and subTask2. 90 tester.complete("leaf1", "leaf2", "leaf3", "foo", "bar", "baz", "qux"); 91 92 // Since the subcomponent was cancelled, they are not started. 93 tester.assertNotStarted("subTask1", "subTask2"); 94 } 95 96 @Test cancellingProducerFromComponentDependency_inSubcomponent_cancelsUnderlyingTask()97 public void cancellingProducerFromComponentDependency_inSubcomponent_cancelsUnderlyingTask() 98 throws Exception { 99 // Request subcomponent's entry point. 100 ListenableFuture<String> subcomponentEntryPoint = subcomponent.subcomponentEntryPoint(); 101 102 // Finish all parent tasks so that the subcomponent's tasks can start. 103 tester.complete("leaf1", "leaf2", "leaf3", "foo", "bar", "baz", "qux", "subLeaf"); 104 105 tester.assertStarted("subTask1", "subTask2"); 106 tester.assertNotCancelled("subTask1", "subTask2"); 107 108 // When subTask2 runs, it cancels the dependency future. 109 // TODO(cgdecker): Is this what we want to happen? 110 // On the one hand, there's a policy of "futures from component dependencies come from outside 111 // our control and should be cancelled unconditionally". On the other hand, the dependency is 112 // coming from the parent component, and the policy is also not to cancel things belonging to 113 // the parent unless it allows that. 114 tester.assertCancelled("dependencyFuture"); 115 116 // The future it returns didn't depend directly on that future, though, so the subcomponent 117 // should be able to complete normally. 118 tester.complete("subTask1", "subTask2", "subEntryPoint"); 119 120 assertThat(subcomponentEntryPoint.get(1, MILLISECONDS)).isEqualTo("completed"); 121 } 122 } 123