BrowserStartupControllerTest.java revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser;
6
7import android.content.Context;
8import android.test.InstrumentationTestCase;
9import android.test.suitebuilder.annotation.SmallTest;
10
11import org.chromium.base.ThreadUtils;
12import org.chromium.base.library_loader.LoaderErrors;
13import org.chromium.base.library_loader.ProcessInitException;
14import org.chromium.base.test.util.AdvancedMockContext;
15
16/**
17 * Test of BrowserStartupController
18 */
19public class BrowserStartupControllerTest extends InstrumentationTestCase {
20
21    private TestBrowserStartupController mController;
22
23    private static class TestBrowserStartupController extends BrowserStartupController {
24
25        private int mStartupResult;
26        private boolean mLibraryLoadSucceeds;
27        private int mInitializedCounter = 0;
28
29        @Override
30        void prepareToStartBrowserProcess(int numRenderers) throws ProcessInitException {
31            if (!mLibraryLoadSucceeds) {
32                throw new ProcessInitException(
33                        LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED);
34            }
35        }
36
37        private TestBrowserStartupController(Context context) {
38            super(context);
39        }
40
41        @Override
42        int contentStart() {
43            mInitializedCounter++;
44            if (BrowserStartupController.browserMayStartAsynchonously()) {
45                // Post to the UI thread to emulate what would happen in a real scenario.
46                ThreadUtils.postOnUiThread(new Runnable() {
47                    @Override
48                    public void run() {
49                        BrowserStartupController.browserStartupComplete(mStartupResult);
50                    }
51                });
52            } else {
53                BrowserStartupController.browserStartupComplete(mStartupResult);
54            }
55            return mStartupResult;
56        }
57
58        private int initializedCounter() {
59            return mInitializedCounter;
60        }
61    }
62
63    private static class TestStartupCallback implements BrowserStartupController.StartupCallback {
64        private boolean mWasSuccess;
65        private boolean mWasFailure;
66        private boolean mHasStartupResult;
67        private boolean mAlreadyStarted;
68
69        @Override
70        public void onSuccess(boolean alreadyStarted) {
71            assert !mHasStartupResult;
72            mWasSuccess = true;
73            mAlreadyStarted = alreadyStarted;
74            mHasStartupResult = true;
75        }
76
77        @Override
78        public void onFailure() {
79            assert !mHasStartupResult;
80            mWasFailure = true;
81            mHasStartupResult = true;
82        }
83    }
84
85    @Override
86    protected void setUp() throws Exception {
87        super.setUp();
88        Context context = new AdvancedMockContext(getInstrumentation().getTargetContext());
89        mController = new TestBrowserStartupController(context);
90        // Setting the static singleton instance field enables more correct testing, since it is
91        // is possible to call {@link BrowserStartupController#browserStartupComplete(int)} instead
92        // of {@link BrowserStartupController#executeEnqueuedCallbacks(int, boolean)} directly.
93        BrowserStartupController.overrideInstanceForTest(mController);
94    }
95
96    @SmallTest
97    public void testSingleAsynchronousStartupRequest() {
98        mController.mStartupResult = BrowserStartupController.STARTUP_SUCCESS;
99        mController.mLibraryLoadSucceeds = true;
100        final TestStartupCallback callback = new TestStartupCallback();
101
102        // Kick off the asynchronous startup request.
103        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
104            @Override
105            public void run() {
106                try {
107                    mController.startBrowserProcessesAsync(callback);
108                } catch (Exception e) {
109                    fail("Browser should have started successfully");
110                }
111            }
112        });
113
114        assertTrue("Asynchronous mode should have been set.",
115                BrowserStartupController.browserMayStartAsynchonously());
116        assertEquals("The browser process should have been initialized one time.", 1,
117                mController.initializedCounter());
118
119        // Wait for callbacks to complete.
120        getInstrumentation().waitForIdleSync();
121
122        assertTrue("Callback should have been executed.", callback.mHasStartupResult);
123        assertTrue("Callback should have been a success.", callback.mWasSuccess);
124        assertFalse("Callback should be told that the browser process was not already started.",
125                callback.mAlreadyStarted);
126    }
127
128    @SmallTest
129    public void testMultipleAsynchronousStartupRequests() {
130        mController.mStartupResult = BrowserStartupController.STARTUP_SUCCESS;
131        mController.mLibraryLoadSucceeds = true;
132        final TestStartupCallback callback1 = new TestStartupCallback();
133        final TestStartupCallback callback2 = new TestStartupCallback();
134        final TestStartupCallback callback3 = new TestStartupCallback();
135
136        // Kick off the asynchronous startup requests.
137        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
138            @Override
139            public void run() {
140                try {
141                    mController.startBrowserProcessesAsync(callback1);
142                } catch (Exception e) {
143                    fail("Browser should have started successfully");
144                }
145            }
146        });
147        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
148            @Override
149            public void run() {
150                try {
151                    mController.startBrowserProcessesAsync(callback2);
152                } catch (Exception e) {
153                    fail("Browser should have started successfully");
154                }
155            }
156        });
157        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
158            @Override
159            public void run() {
160                mController.addStartupCompletedObserver(callback3);
161            }
162        });
163
164        assertTrue("Asynchronous mode should have been set.",
165                BrowserStartupController.browserMayStartAsynchonously());
166        assertEquals("The browser process should have been initialized one time.", 1,
167                mController.initializedCounter());
168
169        // Wait for callbacks to complete.
170        getInstrumentation().waitForIdleSync();
171
172        assertTrue("Callback 1 should have been executed.", callback1.mHasStartupResult);
173        assertTrue("Callback 1 should have been a success.", callback1.mWasSuccess);
174        assertTrue("Callback 2 should have been executed.", callback2.mHasStartupResult);
175        assertTrue("Callback 2 should have been a success.", callback2.mWasSuccess);
176        assertTrue("Callback 3 should have been executed.", callback3.mHasStartupResult);
177        assertTrue("Callback 3 should have been a success.", callback3.mWasSuccess);
178        // Some startup tasks might have been enqueued after the browser process was started, but
179        // not the first one which kicked of the startup.
180        assertFalse("Callback 1 should be told that the browser process was not already started.",
181                callback1.mAlreadyStarted);
182    }
183
184    @SmallTest
185    public void testConsecutiveAsynchronousStartupRequests() {
186        mController.mStartupResult = BrowserStartupController.STARTUP_SUCCESS;
187        mController.mLibraryLoadSucceeds = true;
188        final TestStartupCallback callback1 = new TestStartupCallback();
189        final TestStartupCallback callback2 = new TestStartupCallback();
190
191        // Kick off the asynchronous startup requests.
192        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
193            @Override
194            public void run() {
195                try {
196                    mController.startBrowserProcessesAsync(callback1);
197                } catch (Exception e) {
198                    fail("Browser should have started successfully");
199                }
200            }
201        });
202        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
203            @Override
204            public void run() {
205                mController.addStartupCompletedObserver(callback2);
206            }
207        });
208
209        assertTrue("Asynchronous mode should have been set.",
210                BrowserStartupController.browserMayStartAsynchonously());
211        assertEquals("The browser process should have been initialized one time.", 1,
212                mController.initializedCounter());
213
214        // Wait for callbacks to complete.
215        getInstrumentation().waitForIdleSync();
216
217        assertTrue("Callback 1 should have been executed.", callback1.mHasStartupResult);
218        assertTrue("Callback 1 should have been a success.", callback1.mWasSuccess);
219        assertTrue("Callback 2 should have been executed.", callback2.mHasStartupResult);
220        assertTrue("Callback 2 should have been a success.", callback2.mWasSuccess);
221
222        final TestStartupCallback callback3 = new TestStartupCallback();
223        final TestStartupCallback callback4 = new TestStartupCallback();
224
225        // Kick off more asynchronous startup requests.
226        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
227            @Override
228            public void run() {
229                try {
230                    mController.startBrowserProcessesAsync(callback3);
231                } catch (Exception e) {
232                    fail("Browser should have started successfully");
233                }
234            }
235        });
236        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
237            @Override
238            public void run() {
239                mController.addStartupCompletedObserver(callback4);
240            }
241        });
242
243        // Wait for callbacks to complete.
244        getInstrumentation().waitForIdleSync();
245
246        assertTrue("Callback 3 should have been executed.", callback3.mHasStartupResult);
247        assertTrue("Callback 3 should have been a success.", callback3.mWasSuccess);
248        assertTrue("Callback 3 should be told that the browser process was already started.",
249                callback3.mAlreadyStarted);
250        assertTrue("Callback 4 should have been executed.", callback4.mHasStartupResult);
251        assertTrue("Callback 4 should have been a success.", callback4.mWasSuccess);
252        assertTrue("Callback 4 should be told that the browser process was already started.",
253                callback4.mAlreadyStarted);
254    }
255
256    @SmallTest
257    public void testSingleFailedAsynchronousStartupRequest() {
258        mController.mStartupResult = BrowserStartupController.STARTUP_FAILURE;
259        mController.mLibraryLoadSucceeds = true;
260        final TestStartupCallback callback = new TestStartupCallback();
261
262        // Kick off the asynchronous startup request.
263        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
264            @Override
265            public void run() {
266                try {
267                    mController.startBrowserProcessesAsync(callback);
268                } catch (Exception e) {
269                    fail("Browser should have started successfully");
270                }
271            }
272        });
273
274        assertTrue("Asynchronous mode should have been set.",
275                BrowserStartupController.browserMayStartAsynchonously());
276        assertEquals("The browser process should have been initialized one time.", 1,
277                mController.initializedCounter());
278
279        // Wait for callbacks to complete.
280        getInstrumentation().waitForIdleSync();
281
282        assertTrue("Callback should have been executed.", callback.mHasStartupResult);
283        assertTrue("Callback should have been a failure.", callback.mWasFailure);
284    }
285
286    @SmallTest
287    public void testConsecutiveFailedAsynchronousStartupRequests() {
288        mController.mStartupResult = BrowserStartupController.STARTUP_FAILURE;
289        mController.mLibraryLoadSucceeds = true;
290        final TestStartupCallback callback1 = new TestStartupCallback();
291        final TestStartupCallback callback2 = new TestStartupCallback();
292
293        // Kick off the asynchronous startup requests.
294        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
295            @Override
296            public void run() {
297                try {
298                    mController.startBrowserProcessesAsync(callback1);
299                } catch (Exception e) {
300                    fail("Browser should have started successfully");
301                }
302            }
303        });
304        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
305            @Override
306            public void run() {
307                mController.addStartupCompletedObserver(callback2);
308            }
309        });
310
311        assertTrue("Asynchronous mode should have been set.",
312                BrowserStartupController.browserMayStartAsynchonously());
313        assertEquals("The browser process should have been initialized one time.", 1,
314                mController.initializedCounter());
315
316        // Wait for callbacks to complete.
317        getInstrumentation().waitForIdleSync();
318
319        assertTrue("Callback 1 should have been executed.", callback1.mHasStartupResult);
320        assertTrue("Callback 1 should have been a failure.", callback1.mWasFailure);
321        assertTrue("Callback 2 should have been executed.", callback2.mHasStartupResult);
322        assertTrue("Callback 2 should have been a failure.", callback2.mWasFailure);
323
324        final TestStartupCallback callback3 = new TestStartupCallback();
325        final TestStartupCallback callback4 = new TestStartupCallback();
326
327        // Kick off more asynchronous startup requests.
328        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
329            @Override
330            public void run() {
331                try {
332                    mController.startBrowserProcessesAsync(callback3);
333                } catch (Exception e) {
334                    fail("Browser should have started successfully");
335                }
336            }
337        });
338        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
339            @Override
340            public void run() {
341                mController.addStartupCompletedObserver(callback4);
342            }
343        });
344
345        // Wait for callbacks to complete.
346        getInstrumentation().waitForIdleSync();
347
348        assertTrue("Callback 3 should have been executed.", callback3.mHasStartupResult);
349        assertTrue("Callback 3 should have been a failure.", callback3.mWasFailure);
350        assertTrue("Callback 4 should have been executed.", callback4.mHasStartupResult);
351        assertTrue("Callback 4 should have been a failure.", callback4.mWasFailure);
352    }
353
354    @SmallTest
355    public void testSingleSynchronousRequest() {
356        mController.mStartupResult = BrowserStartupController.STARTUP_SUCCESS;
357        mController.mLibraryLoadSucceeds = true;
358        // Kick off the synchronous startup.
359        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
360            @Override
361            public void run() {
362                try {
363                    mController.startBrowserProcessesSync(1);
364                } catch (Exception e) {
365                    fail("Browser should have started successfully");
366                }
367            }
368        });
369        assertFalse("Synchronous mode should have been set",
370                BrowserStartupController.browserMayStartAsynchonously());
371
372        assertEquals("The browser process should have been initialized one time.", 1,
373                mController.initializedCounter());
374    }
375
376    @SmallTest
377    public void testAsyncThenSyncRequests() {
378        mController.mStartupResult = BrowserStartupController.STARTUP_SUCCESS;
379        mController.mLibraryLoadSucceeds = true;
380        final TestStartupCallback callback = new TestStartupCallback();
381
382        // Kick off the startups.
383        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
384            @Override
385            public void run() {
386                try {
387                    mController.startBrowserProcessesAsync(callback);
388                } catch (Exception e) {
389                    fail("Browser should have started successfully");
390                }
391                // To ensure that the async startup doesn't complete too soon we have
392                // to do both these in a since Runnable instance. This avoids the
393                // unpredictable race that happens in real situations.
394                try {
395                    mController.startBrowserProcessesSync(1);
396                } catch (Exception e) {
397                    fail("Browser should have started successfully");
398                }
399            }
400        });
401        assertFalse("Synchronous mode should have been set",
402                BrowserStartupController.browserMayStartAsynchonously());
403
404        assertEquals("The browser process should have been initialized twice.", 2,
405                mController.initializedCounter());
406
407        assertTrue("Callback should have been executed.", callback.mHasStartupResult);
408        assertTrue("Callback should have been a success.", callback.mWasSuccess);
409        assertFalse("Callback should be told that the browser process was not already started.",
410                callback.mAlreadyStarted);
411    }
412
413    @SmallTest
414    public void testSyncThenAsyncRequests() {
415        mController.mStartupResult = BrowserStartupController.STARTUP_SUCCESS;
416        mController.mLibraryLoadSucceeds = true;
417        final TestStartupCallback callback = new TestStartupCallback();
418
419        // Do a synchronous startup first.
420        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
421            @Override
422            public void run() {
423                try {
424                    mController.startBrowserProcessesSync(1);
425                } catch (Exception e) {
426                    fail("Browser should have started successfully");
427                }
428            }
429        });
430
431        assertEquals("The browser process should have been initialized once.", 1,
432                mController.initializedCounter());
433
434        assertFalse("Synchronous mode should have been set",
435                BrowserStartupController.browserMayStartAsynchonously());
436
437        // Kick off the asynchronous startup request. This should just queue the callback.
438        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
439            @Override
440            public void run() {
441                try {
442                    mController.startBrowserProcessesAsync(callback);
443                } catch (Exception e) {
444                    fail("Browser should have started successfully");
445                }
446            }
447        });
448
449        assertEquals("The browser process should not have been initialized a second time.", 1,
450                mController.initializedCounter());
451
452        // Wait for callbacks to complete.
453        getInstrumentation().waitForIdleSync();
454
455        assertTrue("Callback should have been executed.", callback.mHasStartupResult);
456        assertTrue("Callback should have been a success.", callback.mWasSuccess);
457        assertTrue("Callback should be told that the browser process was already started.",
458                callback.mAlreadyStarted);
459    }
460
461    @SmallTest
462    public void testLibraryLoadFails() {
463        mController.mLibraryLoadSucceeds = false;
464        final TestStartupCallback callback = new TestStartupCallback();
465
466        // Kick off the asynchronous startup request.
467        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
468            @Override
469            public void run() {
470                try {
471                    mController.startBrowserProcessesAsync(callback);
472                    fail("Browser should not have started successfully");
473                } catch (Exception e) {
474                    // Exception expected, ignore.
475                }
476            }
477        });
478
479        assertEquals("The browser process should not have been initialized.", 0,
480                mController.initializedCounter());
481
482        // Wait for callbacks to complete.
483        getInstrumentation().waitForIdleSync();
484    }
485
486}
487