16
16
import static com .google .common .truth .Truth .assertThat ;
17
17
import static com .google .firebase .appdistribution .impl .TestUtils .assertTaskFailure ;
18
18
import static com .google .firebase .appdistribution .impl .TestUtils .awaitAsyncOperations ;
19
+ import static com .google .firebase .appdistribution .impl .TestUtils .awaitCondition ;
19
20
import static org .junit .Assert .assertEquals ;
21
+ import static org .mockito .ArgumentMatchers .anyString ;
22
+ import static org .mockito .Mockito .times ;
23
+ import static org .mockito .Mockito .verify ;
20
24
import static org .mockito .Mockito .when ;
21
25
import static org .robolectric .Shadows .shadowOf ;
22
26
import static org .robolectric .annotation .LooperMode .Mode .PAUSED ;
23
27
24
28
import android .app .Activity ;
25
29
import android .net .Uri ;
26
30
import com .google .firebase .FirebaseApp ;
31
+ import com .google .firebase .annotations .concurrent .Blocking ;
32
+ import com .google .firebase .annotations .concurrent .Lightweight ;
27
33
import com .google .firebase .appdistribution .BinaryType ;
28
34
import com .google .firebase .appdistribution .FirebaseAppDistributionException ;
29
35
import com .google .firebase .appdistribution .FirebaseAppDistributionException .Status ;
30
36
import com .google .firebase .appdistribution .UpdateProgress ;
31
37
import com .google .firebase .appdistribution .UpdateStatus ;
32
38
import com .google .firebase .appdistribution .UpdateTask ;
39
+ import com .google .firebase .concurrent .FirebaseExecutors ;
40
+ import com .google .firebase .concurrent .TestOnlyExecutors ;
33
41
import java .io .ByteArrayInputStream ;
34
42
import java .io .IOException ;
43
+ import java .util .ArrayList ;
35
44
import java .util .List ;
45
+ import java .util .concurrent .CountDownLatch ;
46
+ import java .util .concurrent .ExecutionException ;
36
47
import java .util .concurrent .ExecutorService ;
37
- import java .util .concurrent .Executors ;
38
48
import javax .net .ssl .HttpsURLConnection ;
39
49
import org .junit .Before ;
40
50
import org .junit .Test ;
@@ -62,9 +72,11 @@ public class AabUpdaterTest {
62
72
.setDownloadUrl ("https://test-url" )
63
73
.build ();
64
74
75
+ @ Blocking private final ExecutorService blockingExecutor = TestOnlyExecutors .blocking ();
76
+ @ Lightweight private final ExecutorService lightweightExecutor = TestOnlyExecutors .lite ();
77
+
65
78
private AabUpdater aabUpdater ;
66
79
private ShadowActivity shadowActivity ;
67
- private ExecutorService testExecutor ;
68
80
@ Mock private HttpsURLConnection mockHttpsUrlConnection ;
69
81
@ Mock private HttpsUrlConnectionFactory mockHttpsUrlConnectionFactory ;
70
82
@ Mock private FirebaseAppDistributionLifecycleNotifier mockLifecycleNotifier ;
@@ -78,7 +90,6 @@ public void setup() throws IOException, FirebaseAppDistributionException {
78
90
79
91
FirebaseApp .clearInstancesForTest ();
80
92
81
- testExecutor = Executors .newSingleThreadExecutor ();
82
93
activity = Robolectric .buildActivity (TestActivity .class ).create ().get ();
83
94
shadowActivity = shadowOf (activity );
84
95
@@ -90,7 +101,11 @@ public void setup() throws IOException, FirebaseAppDistributionException {
90
101
91
102
aabUpdater =
92
103
Mockito .spy (
93
- new AabUpdater (mockLifecycleNotifier , mockHttpsUrlConnectionFactory , testExecutor ));
104
+ new AabUpdater (
105
+ mockLifecycleNotifier ,
106
+ mockHttpsUrlConnectionFactory ,
107
+ blockingExecutor ,
108
+ lightweightExecutor ));
94
109
95
110
TestUtils .mockForegroundActivity (mockLifecycleNotifier , activity );
96
111
}
@@ -102,7 +117,8 @@ public void updateAppTask_whenOpenConnectionFails_setsNetworkFailure()
102
117
when (mockHttpsUrlConnectionFactory .openConnection (TEST_URL )).thenThrow (caughtException );
103
118
104
119
UpdateTask updateTask = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
105
- awaitAsyncOperations (testExecutor );
120
+ awaitAsyncOperations (blockingExecutor );
121
+ awaitAsyncOperations (lightweightExecutor );
106
122
107
123
assertTaskFailure (
108
124
updateTask , Status .NETWORK_FAILURE , "Failed to open connection" , caughtException );
@@ -114,7 +130,8 @@ public void updateAppTask_isNotRedirectResponse_setsDownloadFailure()
114
130
when (mockHttpsUrlConnection .getResponseCode ()).thenReturn (200 );
115
131
116
132
UpdateTask updateTask = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
117
- awaitAsyncOperations (testExecutor );
133
+ awaitAsyncOperations (blockingExecutor );
134
+ awaitAsyncOperations (lightweightExecutor );
118
135
119
136
assertTaskFailure (updateTask , Status .DOWNLOAD_FAILURE , "Expected redirect" );
120
137
}
@@ -125,7 +142,8 @@ public void updateAppTask_missingLocationHeader_setsDownloadFailure()
125
142
when (mockHttpsUrlConnection .getHeaderField ("Location" )).thenReturn (null );
126
143
127
144
UpdateTask updateTask = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
128
- awaitAsyncOperations (testExecutor );
145
+ awaitAsyncOperations (blockingExecutor );
146
+ awaitAsyncOperations (lightweightExecutor );
129
147
130
148
assertTaskFailure (updateTask , Status .DOWNLOAD_FAILURE , "No Location header" );
131
149
}
@@ -135,18 +153,26 @@ public void updateAppTask_emptyLocationHeader_setsDownloadFailure() throws Inter
135
153
when (mockHttpsUrlConnection .getHeaderField ("Location" )).thenReturn ("" );
136
154
137
155
UpdateTask updateTask = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
138
- awaitAsyncOperations (testExecutor );
156
+ awaitAsyncOperations (blockingExecutor );
157
+ awaitAsyncOperations (lightweightExecutor );
139
158
140
159
assertTaskFailure (updateTask , Status .DOWNLOAD_FAILURE , "Empty Location header" );
141
160
}
142
161
143
162
@ Test
144
163
public void updateAppTask_whenAabReleaseAvailable_redirectsToPlay () throws Exception {
145
- TestOnProgressListener listener = TestOnProgressListener .withExpectedCount (1 );
164
+ // Block thread actually making the request on a latch, which gives us time to add listeners to
165
+ // the returned UpdateTask in time to get all the progress updates
166
+ CountDownLatch countDownLatch = mockOpenConnectionWithLatch ();
167
+
168
+ // Start update
146
169
UpdateTask updateTask = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
147
- updateTask .addOnProgressListener (testExecutor , listener );
148
170
149
- List <UpdateProgress > progressEvents = listener .await ();
171
+ // Listen for progress events
172
+ List <UpdateProgress > progressEvents = new ArrayList <>();
173
+ updateTask .addOnProgressListener (FirebaseExecutors .directExecutor (), progressEvents ::add );
174
+ countDownLatch .countDown ();
175
+ awaitCondition (() -> progressEvents .size () == 1 );
150
176
151
177
// Task is not completed in this case, because app is expected to terminate during update
152
178
assertThat (shadowActivity .getNextStartedActivity ().getData ())
@@ -164,7 +190,8 @@ public void updateAppTask_whenAabReleaseAvailable_redirectsToPlay() throws Excep
164
190
@ Test
165
191
public void updateAppTask_onAppResume_setsUpdateCancelled () throws InterruptedException {
166
192
UpdateTask updateTask = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
167
- awaitAsyncOperations (testExecutor );
193
+ awaitAsyncOperations (blockingExecutor );
194
+ awaitAsyncOperations (lightweightExecutor );
168
195
aabUpdater .onActivityStarted (activity );
169
196
170
197
FirebaseAppDistributionException exception =
@@ -175,10 +202,41 @@ public void updateAppTask_onAppResume_setsUpdateCancelled() throws InterruptedEx
175
202
}
176
203
177
204
@ Test
178
- public void updateApp_whenCalledMultipleTimesWithAAB_returnsSameUpdateTask () {
205
+ public void updateApp_whenCalledMultipleTimesWithAAB_onlyMakesOneRequest ()
206
+ throws IOException , FirebaseAppDistributionException , ExecutionException ,
207
+ InterruptedException {
208
+ // Block thread actually making the request on a latch, which gives us time to add listeners to
209
+ // the returned UpdateTask in time to get all the progress updates
210
+ CountDownLatch countDownLatch = mockOpenConnectionWithLatch ();
211
+
212
+ // Start update twice
179
213
UpdateTask updateTask1 = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
180
214
UpdateTask updateTask2 = aabUpdater .updateAab (TEST_RELEASE_NEWER_AAB_INTERNAL );
181
215
182
- assertEquals (updateTask1 , updateTask2 );
216
+ // Listen for progress events
217
+ List <UpdateProgress > progressEvents1 = new ArrayList <>();
218
+ updateTask1 .addOnProgressListener (FirebaseExecutors .directExecutor (), progressEvents1 ::add );
219
+ List <UpdateProgress > progressEvents2 = new ArrayList <>();
220
+ updateTask2 .addOnProgressListener (FirebaseExecutors .directExecutor (), progressEvents2 ::add );
221
+ countDownLatch .countDown ();
222
+ awaitCondition (() -> progressEvents1 .size () == 1 );
223
+ awaitCondition (() -> progressEvents2 .size () == 1 );
224
+
225
+ verify (mockHttpsUrlConnectionFactory , times (1 )).openConnection (anyString ());
226
+ }
227
+
228
+ private CountDownLatch mockOpenConnectionWithLatch () throws IOException {
229
+ CountDownLatch countDownLatch = new CountDownLatch (1 );
230
+ when (mockHttpsUrlConnectionFactory .openConnection (TEST_URL ))
231
+ .thenAnswer (
232
+ invocation -> {
233
+ try {
234
+ countDownLatch .await ();
235
+ } catch (InterruptedException e ) {
236
+ throw new AssertionError ("Interrupted while waiting in mock" );
237
+ }
238
+ return mockHttpsUrlConnection ;
239
+ });
240
+ return countDownLatch ;
183
241
}
184
242
}
0 commit comments