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