• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal.util;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static org.junit.Assert.assertThrows;
20 
21 import com.google.android.libraries.mobiledatadownload.internal.util.FuturesUtil.SequentialFutureChain;
22 import com.google.common.base.Function;
23 import com.google.common.util.concurrent.Futures;
24 import com.google.common.util.concurrent.ListenableFuture;
25 import com.google.common.util.concurrent.MoreExecutors;
26 import java.io.IOException;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.Executor;
29 import java.util.concurrent.Executors;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 import org.robolectric.RobolectricTestRunner;
34 
35 @RunWith(RobolectricTestRunner.class)
36 public final class FuturesUtilTest {
37 
38   private static final Executor SEQUENTIAL_EXECUTOR =
39       MoreExecutors.newSequentialExecutor(Executors.newCachedThreadPool());
40 
41   private FuturesUtil futuresUtil;
42 
43   @Before
setUp()44   public void setUp() {
45     futuresUtil = new FuturesUtil(SEQUENTIAL_EXECUTOR);
46   }
47 
48   @Test
chain_preservesOrder()49   public void chain_preservesOrder() throws ExecutionException, InterruptedException {
50     SequentialFutureChain<String> chain = futuresUtil.newSequentialChain("");
51     for (int i = 0; i < 10; i++) {
52       // Variables captured in lambdas must be effectively final.
53       int index = i;
54       chain.chainAsync(str -> Futures.immediateFuture(str + index));
55     }
56     String result = chain.start().get();
57     assertThat(result).isEqualTo("0123456789");
58   }
59 
60   @Test
chain_mixedOperationTypes_preservesOrder()61   public void chain_mixedOperationTypes_preservesOrder()
62       throws ExecutionException, InterruptedException {
63     String result =
64         futuresUtil
65             .newSequentialChain("")
66             .chainAsync(str -> Futures.immediateFuture(str + 0))
67             .chain(str -> str + 1)
68             .chainAsync(str -> Futures.immediateFuture(str + 2))
69             .chain(str -> str + 3)
70             .chain(str -> str + 4)
71             .chainAsync(str -> Futures.immediateFuture(str + 5))
72             .start()
73             .get();
74     assertThat(result).isEqualTo("012345");
75   }
76 
77   @Test
chain_preservesOrder_sideEffects()78   public void chain_preservesOrder_sideEffects() throws ExecutionException, InterruptedException {
79     StringBuilder sb = new StringBuilder("");
80     SequentialFutureChain<Void> chain = futuresUtil.newSequentialChain();
81     for (int i = 0; i < 10; i++) {
82       chain.chainAsync(appendIntAsync(sb, i));
83     }
84     chain.start().get();
85     assertThat(sb.toString()).isEqualTo("0123456789");
86   }
87 
88   @Test
chain_mixedOperationTypes_preservesOrder_sideEffects()89   public void chain_mixedOperationTypes_preservesOrder_sideEffects()
90       throws ExecutionException, InterruptedException {
91     StringBuilder sb = new StringBuilder("");
92     futuresUtil
93         .newSequentialChain()
94         .chain(appendIntDirect(sb, 0))
95         .chainAsync(appendIntAsync(sb, 1))
96         .chain(appendIntDirect(sb, 2))
97         .chainAsync(appendIntAsync(sb, 3))
98         .chainAsync(appendIntAsync(sb, 4))
99         .chain(appendIntDirect(sb, 5))
100         .start()
101         .get();
102     assertThat(sb.toString()).isEqualTo("012345");
103   }
104 
105   @Test
chain_noOp_preservesInitialState()106   public void chain_noOp_preservesInitialState() throws ExecutionException, InterruptedException {
107     StringBuilder sb = new StringBuilder("Initial state.");
108     SequentialFutureChain<Void> chain = futuresUtil.newSequentialChain();
109     for (int i = 0; i < 0; i++) {
110       chain.chainAsync(appendIntAsync(sb, i));
111     }
112     chain.start().get();
113     assertThat(sb.toString()).isEqualTo("Initial state.");
114   }
115 
116   @Test
chain_propagatesFailure()117   public void chain_propagatesFailure() {
118     StringBuilder sb = new StringBuilder("");
119     SequentialFutureChain<Void> chain = futuresUtil.newSequentialChain();
120     chain
121         .chainAsync(appendIntAsync(sb, 0))
122         .chainAsync(appendIntAsync(sb, 1))
123         .chainAsync(voidArg -> Futures.immediateFailedFuture(new IOException("The error message.")))
124         .chainAsync(appendIntAsync(sb, 2))
125         .chainAsync(appendIntAsync(sb, 3));
126     ExecutionException ex = assertThrows(ExecutionException.class, chain.start()::get);
127     assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
128     assertThat(ex).hasMessageThat().contains("The error message.");
129   }
130 
131   @Test
chain_mixedOperationTypes_propagatesFailure()132   public void chain_mixedOperationTypes_propagatesFailure() {
133     StringBuilder sb = new StringBuilder("");
134     SequentialFutureChain<Void> chain = futuresUtil.newSequentialChain();
135     chain
136         .chainAsync(appendIntAsync(sb, 0))
137         .chain(appendIntDirect(sb, 1))
138         .chain(
139             voidArg -> {
140               throw new RuntimeException("The error message.");
141             })
142         .chain(appendIntDirect(sb, 2))
143         .chainAsync(appendIntAsync(sb, 3));
144     ExecutionException ex = assertThrows(ExecutionException.class, chain.start()::get);
145     assertThat(ex).hasCauseThat().isInstanceOf(RuntimeException.class);
146     assertThat(ex).hasMessageThat().contains("The error message.");
147   }
148 
appendIntDirect(StringBuilder sb, int i)149   private static Function<Void, Void> appendIntDirect(StringBuilder sb, int i) {
150     return voidArg -> {
151       sb.append(i);
152       return null;
153     };
154   }
155 
appendIntAsync(StringBuilder sb, int i)156   private static Function<Void, ListenableFuture<Void>> appendIntAsync(StringBuilder sb, int i) {
157     return voidArg -> {
158       sb.append(i);
159       return Futures.immediateFuture(null);
160     };
161   }
162 }
163