// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omaha;
import android.content.SharedPreferences;
import androidx.annotation.IntDef;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.FakeTimeTestRule;
import org.chromium.base.FeatureList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.omaha.MockRequestGenerator.DeviceType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Tests for the {@link OmahaClient}.
* Tests override the original OmahaClient's functions with the MockOmahaClient, which
* provides a way to hook into functions to return values that would normally be provided by the
* system, such as whether Chrome was installed through the system image.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Batch(Batch.UNIT_TESTS)
@Config(manifest = Config.NONE)
public class OmahaBaseTest {
private static class TimestampPair {
public long timestampNextRequest;
public long timestampNextPost;
public TimestampPair(long timestampNextRequest, long timestampNextPost) {
this.timestampNextRequest = timestampNextRequest;
this.timestampNextPost = timestampNextPost;
}
}
private static class MockOmahaDelegate extends OmahaDelegate {
private final List<Integer> mPostResults = new ArrayList<Integer>();
private final List<Boolean> mGenerateAndPostRequestResults = new ArrayList<Boolean>();
private final boolean mIsOnTablet;
private final boolean mIsInForeground;
private final boolean mIsInSystemImage;
private final ExponentialBackoffScheduler mScheduler;
private MockRequestGenerator mMockGenerator;
private int mNumUUIDsGenerated;
private long mNextScheduledTimestamp = -1;
private boolean mInstallEventWasSent;
private TimestampPair mTimestampsOnRegisterNewRequest;
private TimestampPair mTimestampsOnSaveState;
MockOmahaDelegate(DeviceType deviceType, @InstallSource int installSource) {
mIsOnTablet = deviceType == DeviceType.TABLET;
mIsInForeground = true;
mIsInSystemImage = installSource == InstallSource.SYSTEM_IMAGE;
mScheduler =
new ExponentialBackoffScheduler(
OmahaPrefUtils.PREF_PACKAGE,
OmahaBase.MS_POST_BASE_DELAY,
OmahaBase.MS_POST_MAX_DELAY);
}
@Override
protected RequestGenerator createRequestGenerator() {
mMockGenerator =
new MockRequestGenerator(mIsOnTablet ? DeviceType.TABLET : DeviceType.HANDSET);
return mMockGenerator;
}
@Override
public boolean isInSystemImage() {
return mIsInSystemImage;
}
@Override
ExponentialBackoffScheduler getScheduler() {
return mScheduler;
}
@Override
protected String generateUUID() {
mNumUUIDsGenerated += 1;
return "UUID" + mNumUUIDsGenerated;
}
@Override
protected boolean isChromeBeingUsed() {
return mIsInForeground;
}
@Override
void scheduleService(long currentTimestampMs, long nextTimestampMs) {
mNextScheduledTimestamp = nextTimestampMs;
}
@Override
void onHandlePostRequestDone(int result, boolean installEventWasSent) {
mPostResults.add(result);
mInstallEventWasSent = installEventWasSent;
}
@Override
void onRegisterNewRequestDone(long nextRequestTimestamp, long nextPostTimestamp) {
mTimestampsOnRegisterNewRequest =
new TimestampPair(nextRequestTimestamp, nextPostTimestamp);
}
@Override
void onGenerateAndPostRequestDone(boolean result) {
mGenerateAndPostRequestResults.add(result);
}
@Override
void onSaveStateDone(long nextRequestTimestamp, long nextPostTimestamp) {
mTimestampsOnSaveState = new TimestampPair(nextRequestTimestamp, nextPostTimestamp);
}
}
private static class ClosableThreadAssertsDisabler implements AutoCloseable {
ClosableThreadAssertsDisabler() {
ThreadUtils.setThreadAssertsDisabledForTesting(true);
}
@Override
public void close() throws Exception {}
}
@IntDef({InstallSource.SYSTEM_IMAGE, InstallSource.ORGANIC})
@Retention(RetentionPolicy.SOURCE)
private @interface InstallSource {
int SYSTEM_IMAGE = 0;
int ORGANIC = 1;
}
@IntDef({ServerResponse.SUCCESS, ServerResponse.FAILURE})
@Retention(RetentionPolicy.SOURCE)
private @interface ServerResponse {
int SUCCESS = 0;
int FAILURE = 1;
}
@IntDef({ConnectionStatus.RESPONDS, ConnectionStatus.TIMES_OUT})
@Retention(RetentionPolicy.SOURCE)
private @interface ConnectionStatus {
int RESPONDS = 0;
int TIMES_OUT = 1;
}
private MockOmahaDelegate mDelegate;
private MockOmahaBase mOmahaBase;
@Rule public FakeTimeTestRule mFakeTimeRule = new FakeTimeTestRule();
private MockOmahaBase createOmahaBase() {
return createOmahaBase(
ServerResponse.SUCCESS, ConnectionStatus.RESPONDS, DeviceType.HANDSET);
}
private MockOmahaBase createOmahaBase(
@ServerResponse int response, @ConnectionStatus int status, DeviceType deviceType) {
MockOmahaBase omahaClient = new MockOmahaBase(mDelegate, response, status, deviceType);
return omahaClient;
}
@Before
public void setUp() {
mDelegate = new MockOmahaDelegate(DeviceType.HANDSET, InstallSource.ORGANIC);
}
@After
public void tearDown() {
FeatureList.setTestValues(null);
OmahaBase.setIsDisabledForTesting(true);
}
private class MockOmahaBase extends OmahaBase {
private final LinkedList<MockConnection> mMockConnections = new LinkedList<>();
private final boolean mSendValidResponse;
private final boolean mConnectionTimesOut;
private final boolean mIsOnTablet;
private String mUpdateVersion;
private String mInstalledVersion;
public MockOmahaBase(
OmahaDelegate delegate,
@ServerResponse int serverResponse,
@ConnectionStatus int connectionStatus,
DeviceType deviceType) {
super(delegate);
mSendValidResponse = serverResponse == ServerResponse.SUCCESS;
mConnectionTimesOut = connectionStatus == ConnectionStatus.TIMES_OUT;
mIsOnTablet = deviceType == DeviceType.TABLET;
mUpdateVersion = "1.2.3.4";
mInstalledVersion = "1.2.3.4";
}
/** Gets the number of MockConnections created. */
public int getNumConnectionsMade() {
return mMockConnections.size();
}
/** Returns a particular connection. */
public MockConnection getConnection(int index) {
return mMockConnections.get(index);
}
/** Returns the last MockPingConection used to simulate communication with the server. */
public MockConnection getLastConnection() {
return mMockConnections.getLast();
}
public boolean isSendInstallEvent() {
return mSendInstallEvent;
}
public void setSendInstallEvent(boolean state) {
mSendInstallEvent = state;
}
public void setUpdateVersion(String version) {
mUpdateVersion = version;
}
public void setInstalledVersion(String version) {
mInstalledVersion = version;
}
@Override
protected HttpURLConnection createConnection() {
MockConnection connection = null;
try {
URL url = new URL(mDelegate.getRequestGenerator().getServerUrl());
connection =
new MockConnection(
url,
mIsOnTablet,
mSendValidResponse,
mSendInstallEvent,
mConnectionTimesOut,
mUpdateVersion);
mMockConnections.addLast(connection);
} catch (MalformedURLException e) {
Assert.fail("Caught a malformed URL exception: " + e);
}
return connection;
}
@Override
protected String getInstalledVersion() {
return mInstalledVersion;
}
}
@Test
@Feature({"Omaha"})
public void testPipelineFreshInstall() {
final long now = mDelegate.getScheduler().getCurrentTime();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// A fresh install results in two requests to the Omaha server: one for the install request
// and one for the ping request.
Assert.assertTrue(mDelegate.mInstallEventWasSent);
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(OmahaBase.PostResult.SENT, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(2, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(1));
// Successful requests mean that the next scheduled event should be checking for when the
// user is active.
Assert.assertEquals(now + OmahaBase.MS_BETWEEN_REQUESTS, mDelegate.mNextScheduledTimestamp);
checkTimestamps(
now + OmahaBase.MS_BETWEEN_REQUESTS,
now + OmahaBase.MS_POST_BASE_DELAY,
mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testPipelineRegularPing() {
final long now = mDelegate.getScheduler().getCurrentTime();
// Record that an install event has already been sent and that we're due for a new request.
SharedPreferences.Editor editor = OmahaPrefUtils.getSharedPreferences().edit();
editor.putBoolean(OmahaPrefUtils.PREF_SEND_INSTALL_EVENT, false);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, now);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, now);
editor.apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// Only the regular ping should have been sent.
Assert.assertFalse(mDelegate.mInstallEventWasSent);
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(OmahaBase.PostResult.SENT, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(1, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
// Successful requests mean that the next scheduled event should be checking for when the
// user is active.
Assert.assertEquals(now + OmahaBase.MS_BETWEEN_REQUESTS, mDelegate.mNextScheduledTimestamp);
checkTimestamps(
now + OmahaBase.MS_BETWEEN_REQUESTS,
now + OmahaBase.MS_POST_BASE_DELAY,
mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testPipelineFreshInstallUpdatedAvailable_crbug_1095755() {
final long now = mDelegate.getScheduler().getCurrentTime();
final String updateVersion = "10.0.0.0";
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.setUpdateVersion(updateVersion);
mOmahaBase.run();
Assert.assertEquals(2, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(1));
SharedPreferences sharedPreferences = OmahaPrefUtils.getSharedPreferences();
String storedLastVersion =
sharedPreferences.getString(OmahaPrefUtils.PREF_LATEST_VERSION, null);
String storedMarketURL = sharedPreferences.getString(OmahaPrefUtils.PREF_MARKET_URL, null);
Assert.assertEquals(updateVersion, storedLastVersion);
Assert.assertEquals(MockConnection.STRIPPED_MARKET_URL, storedMarketURL);
}
@Test
@Feature({"Omaha"})
public void testPipelineRegularPingUpdateAvailable_crbug_1095755() {
final long now = mDelegate.getScheduler().getCurrentTime();
String updateVersion = "10.0.0.0";
// Record that an install event has already been sent and that we're due for a new request.
SharedPreferences.Editor editor = OmahaPrefUtils.getSharedPreferences().edit();
editor.putBoolean(OmahaPrefUtils.PREF_SEND_INSTALL_EVENT, false);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, now);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, now);
editor.apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.setUpdateVersion(updateVersion);
mOmahaBase.run();
// Only the regular ping should have been sent.
Assert.assertEquals(1, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
SharedPreferences sharedPreferences = OmahaPrefUtils.getSharedPreferences();
String storedLastVersion =
sharedPreferences.getString(OmahaPrefUtils.PREF_LATEST_VERSION, null);
String storedMarketURL = sharedPreferences.getString(OmahaPrefUtils.PREF_MARKET_URL, null);
Assert.assertEquals(updateVersion, storedLastVersion);
Assert.assertEquals(MockConnection.STRIPPED_MARKET_URL, storedMarketURL);
}
@Test
@Feature({"Omaha"})
public void testTooEarlyToPing() {
final long now = mDelegate.getScheduler().getCurrentTime();
final long later = now + 10000;
// Put the time for the next request in the future.
SharedPreferences prefs = OmahaPrefUtils.getSharedPreferences();
prefs.edit().putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, later).apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// Nothing should have been POSTed.
Assert.assertEquals(0, mDelegate.mPostResults.size());
Assert.assertEquals(0, mDelegate.mGenerateAndPostRequestResults.size());
// The next scheduled event is the request generation. Because there was nothing to POST,
// its timestamp should have remained unchanged and shouldn't have been considered when the
// new alarm was scheduled.
Assert.assertEquals(later, mDelegate.mNextScheduledTimestamp);
checkTimestamps(later, now, mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testTooEarlyToPostExistingRequest() {
final long timeGeneratedRequest = mDelegate.getScheduler().getCurrentTime() - 10000;
final long timeSendNewPost = timeGeneratedRequest + 20000L;
final long timeSendNewRequest = timeSendNewPost + 30000L;
SharedPreferences prefs = OmahaPrefUtils.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
// Make it so that a request was generated and is just waiting to be sent.
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, timeSendNewRequest);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_OF_REQUEST, timeGeneratedRequest);
editor.putString(OmahaPrefUtils.PREF_PERSISTED_REQUEST_ID, "persisted_id");
// Put the time for the next post in the future.
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, timeSendNewPost);
editor.apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// Request generation code should be skipped.
Assert.assertNull(mDelegate.mTimestampsOnRegisterNewRequest);
// Should be too early to post, causing it to be rescheduled.
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(
OmahaBase.PostResult.SCHEDULED, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(0, mDelegate.mGenerateAndPostRequestResults.size());
// The next scheduled event is the POST. Because request generation code wasn't run, the
// timestamp for it shouldn't have changed.
Assert.assertEquals(timeSendNewPost, mDelegate.mNextScheduledTimestamp);
checkTimestamps(timeSendNewRequest, timeSendNewPost, mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testPostExistingRequestSuccessfully() {
final long now = mDelegate.getScheduler().getCurrentTime();
final long timeGeneratedRequest = now - 10000;
final long timeSendNewPost = now;
final long timeRegisterNewRequest = now + 10000;
SharedPreferences prefs = OmahaPrefUtils.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
// Make it so that a regular <ping> was generated and is just waiting to be sent.
editor.putBoolean(OmahaPrefUtils.PREF_SEND_INSTALL_EVENT, false);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, timeRegisterNewRequest);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_OF_REQUEST, timeGeneratedRequest);
editor.putString(OmahaPrefUtils.PREF_PERSISTED_REQUEST_ID, "persisted_id");
// Send the POST now.
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, timeSendNewPost);
editor.apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// Registering code shouldn't have fired.
Assert.assertNull(mDelegate.mTimestampsOnRegisterNewRequest);
// Because we didn't send an install event, only one POST should have occurred.
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(OmahaBase.PostResult.SENT, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(1, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
// The next scheduled event is the request generation because there is nothing to POST.
// A successful POST adjusts all timestamps for the current time.
Assert.assertEquals(timeRegisterNewRequest, mDelegate.mNextScheduledTimestamp);
checkTimestamps(
now + OmahaBase.MS_BETWEEN_REQUESTS,
now + OmahaBase.MS_POST_BASE_DELAY,
mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testPostExistingButFails() {
final long now = mDelegate.getScheduler().getCurrentTime();
final long timeGeneratedRequest = now - 10000;
final long timeSendNewPost = now;
final long timeRegisterNewRequest = timeGeneratedRequest + OmahaBase.MS_BETWEEN_REQUESTS;
SharedPreferences prefs = OmahaPrefUtils.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
// Make it so that a regular <ping> was generated and is just waiting to be sent.
editor.putBoolean(OmahaPrefUtils.PREF_SEND_INSTALL_EVENT, false);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, timeRegisterNewRequest);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_OF_REQUEST, timeGeneratedRequest);
editor.putString(OmahaPrefUtils.PREF_PERSISTED_REQUEST_ID, "persisted_id");
// Send the POST now.
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, timeSendNewPost);
editor.apply();
// Trigger Omaha.
mOmahaBase =
createOmahaBase(
ServerResponse.FAILURE, ConnectionStatus.RESPONDS, DeviceType.HANDSET);
mOmahaBase.run();
// Registering code shouldn't have fired.
Assert.assertNull(mDelegate.mTimestampsOnRegisterNewRequest);
// Because we didn't send an install event, only one POST should have occurred.
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(OmahaBase.PostResult.FAILED, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(1, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertFalse(mDelegate.mGenerateAndPostRequestResults.get(0));
// The next scheduled event should be the POST event, which is delayed by the base delay
// because no failures have happened yet.
Assert.assertEquals(
mDelegate.mTimestampsOnSaveState.timestampNextPost,
mDelegate.mNextScheduledTimestamp);
checkTimestamps(
timeRegisterNewRequest,
now + OmahaBase.MS_POST_BASE_DELAY,
mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testTimestampWithinBounds() {
final long now = mDelegate.getScheduler().getCurrentTime();
final long timeRegisterNewRequest = OmahaBase.MS_BETWEEN_REQUESTS + 1;
SharedPreferences prefs = OmahaPrefUtils.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
// Indicate that the next request should be generated way past an expected timeframe.
editor.putBoolean(OmahaPrefUtils.PREF_SEND_INSTALL_EVENT, false);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, timeRegisterNewRequest);
editor.apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// Request generation code should fire.
Assert.assertNotNull(mDelegate.mTimestampsOnRegisterNewRequest);
// Because we didn't send an install event, only one POST should have occurred.
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(OmahaBase.PostResult.SENT, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(1, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
// The next scheduled event should be the timestamp for a new request generation.
Assert.assertEquals(
mDelegate.mTimestampsOnSaveState.timestampNextRequest,
mDelegate.mNextScheduledTimestamp);
checkTimestamps(
now + OmahaBase.MS_BETWEEN_REQUESTS,
now + OmahaBase.MS_POST_BASE_DELAY,
mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testOverdueRequestCausesNewRegistration() {
final long timeGeneratedRequest = 0L;
final long now = mDelegate.getScheduler().getCurrentTime();
final long timeSendNewPost = now;
final long timeRegisterNewRequest =
timeGeneratedRequest + OmahaBase.MS_BETWEEN_REQUESTS * 5;
// Record that a regular <ping> was generated, but not sent, then assign it an invalid
// timestamp and try to send it now.
SharedPreferences prefs = OmahaPrefUtils.getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(OmahaPrefUtils.PREF_SEND_INSTALL_EVENT, false);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEW_REQUEST, timeRegisterNewRequest);
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_OF_REQUEST, timeGeneratedRequest);
editor.putString(OmahaPrefUtils.PREF_PERSISTED_REQUEST_ID, "persisted_id");
editor.putLong(OmahaPrefUtils.PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, timeSendNewPost);
editor.apply();
// Trigger Omaha.
mOmahaBase = createOmahaBase();
mOmahaBase.run();
// Registering code shouldn't have fired.
checkTimestamps(
now + OmahaBase.MS_BETWEEN_REQUESTS,
now,
mDelegate.mTimestampsOnRegisterNewRequest);
// Because we didn't send an install event, only one POST should have occurred.
Assert.assertEquals(1, mDelegate.mPostResults.size());
Assert.assertEquals(OmahaBase.PostResult.SENT, mDelegate.mPostResults.get(0).intValue());
Assert.assertEquals(1, mDelegate.mGenerateAndPostRequestResults.size());
Assert.assertTrue(mDelegate.mGenerateAndPostRequestResults.get(0));
// The next scheduled event should be the registration event.
Assert.assertEquals(
mDelegate.mTimestampsOnSaveState.timestampNextRequest,
mDelegate.mNextScheduledTimestamp);
checkTimestamps(
now + OmahaBase.MS_BETWEEN_REQUESTS,
now + OmahaBase.MS_POST_BASE_DELAY,
mDelegate.mTimestampsOnSaveState);
}
@Test
@Feature({"Omaha"})
public void testCheckForUpdatesConnectionTimesOut() throws Exception {
mOmahaBase =
createOmahaBase(
ServerResponse.FAILURE, ConnectionStatus.TIMES_OUT, DeviceType.HANDSET);
@OmahaBase.UpdateStatus int status;
try (ClosableThreadAssertsDisabler ignored = new ClosableThreadAssertsDisabler()) {
status = mOmahaBase.checkForUpdates();
}
Assert.assertEquals(OmahaBase.UpdateStatus.OFFLINE, status);
}
@Test
@Feature({"Omaha"})
public void testCheckForUpdatesUpdated() throws Exception {
final String version = "89.0.12.5342";
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(version);
mOmahaBase.setUpdateVersion(version);
@OmahaBase.UpdateStatus int status;
try (ClosableThreadAssertsDisabler ignored = new ClosableThreadAssertsDisabler()) {
status = mOmahaBase.checkForUpdates();
}
Assert.assertEquals(OmahaBase.UpdateStatus.UPDATED, status);
}
@Test
@Feature({"Omaha"})
public void testCheckForUpdatesOutdated() throws Exception {
final String oldVersion = "89.0.12.5342";
final String newVersion = "89.0.13.1242";
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(oldVersion);
mOmahaBase.setUpdateVersion(newVersion);
@OmahaBase.UpdateStatus int status;
try (ClosableThreadAssertsDisabler ignored = new ClosableThreadAssertsDisabler()) {
status = mOmahaBase.checkForUpdates();
}
Assert.assertEquals(OmahaBase.UpdateStatus.OUTDATED, status);
}
@Test
@Feature({"Omaha"})
public void testCheckForUpdatesFailedIncorrectNewVersion() throws Exception {
final String oldVersion = "89.0.12.5342";
final String newVersion = "Unknown";
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(oldVersion);
mOmahaBase.setUpdateVersion(newVersion);
@OmahaBase.UpdateStatus int status;
try (ClosableThreadAssertsDisabler ignored = new ClosableThreadAssertsDisabler()) {
status = mOmahaBase.checkForUpdates();
}
Assert.assertEquals(OmahaBase.UpdateStatus.FAILED, status);
}
@Test
@Feature({"Omaha"})
public void testCheckForUpdatesFailedIncorrectOldVersion() throws Exception {
final String oldVersion = "Unknown";
final String newVersion = "89.0.13.1242";
mOmahaBase = createOmahaBase();
mOmahaBase.setInstalledVersion(oldVersion);
mOmahaBase.setUpdateVersion(newVersion);
@OmahaBase.UpdateStatus int status;
try (ClosableThreadAssertsDisabler ignored = new ClosableThreadAssertsDisabler()) {
status = mOmahaBase.checkForUpdates();
}
Assert.assertEquals(OmahaBase.UpdateStatus.FAILED, status);
}
private void checkTimestamps(
long expectedRequestTimestamp, long expectedPostTimestamp, TimestampPair timestamps) {
Assert.assertEquals(expectedRequestTimestamp, timestamps.timestampNextRequest);
Assert.assertEquals(expectedPostTimestamp, timestamps.timestampNextPost);
}
/** Simulates communication with the actual Omaha server. */
private static class MockConnection extends HttpURLConnection {
// Omaha appends a "/" to the URL.
private static final String STRIPPED_MARKET_URL =
"https://market.android.com/details?id=com.google.android.apps.chrome";
private static final String MARKET_URL = STRIPPED_MARKET_URL + "/";
// Parameters.
private final boolean mConnectionTimesOut;
private final ByteArrayInputStream mServerResponse;
private final ByteArrayOutputStream mOutputStream;
private final int mHTTPResponseCode;
private final String mUpdateVersion;
// Result variables.
private int mContentLength;
private int mNumTimesResponseCodeRetrieved;
private boolean mSentRequest;
private boolean mGotInputStream;
private String mRequestPropertyField;
private String mRequestPropertyValue;
MockConnection(
URL url,
boolean usingTablet,
boolean sendValidResponse,
boolean sendInstallEvent,
boolean connectionTimesOut,
String updateVersion) {
super(url);
Assert.assertEquals(MockRequestGenerator.SERVER_URL, url.toString());
mUpdateVersion = updateVersion;
String mockResponse = buildServerResponseString(usingTablet, sendInstallEvent);
mOutputStream = new ByteArrayOutputStream();
mServerResponse =
new ByteArrayInputStream(ApiCompatibilityUtils.getBytesUtf8(mockResponse));
mConnectionTimesOut = connectionTimesOut;
if (sendValidResponse) {
mHTTPResponseCode = HttpURLConnection.HTTP_OK; // 200
} else {
mHTTPResponseCode = HttpURLConnection.HTTP_NOT_FOUND; // 404
}
}
/**
* Build a simulated response from the Omaha server indicating an update is available.
* The response changes based on the device type.
*/
private String buildServerResponseString(boolean isOnTablet, boolean sendInstallEvent) {
String response = "";
response += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
response += "<response protocol=\"3.0\" server=\"prod\">";
response += "<daystart elapsed_days=\"4088\" elapsed_seconds=\"12345\"/>";
response += "<app appid=\"";
response +=
(isOnTablet
? MockRequestGenerator.UUID_TABLET
: MockRequestGenerator.UUID_PHONE);
response += "\" status=\"ok\">";
if (sendInstallEvent) {
response += "<event status=\"ok\"/>";
} else {
response += "<updatecheck status=\"ok\">";
response += "<urls><url codebase=\"" + MARKET_URL + "\"/></urls>";
response += "<manifest version=\"" + mUpdateVersion + "\">";
response += "<packages>";
response += "<package hash=\"0\" name=\"dummy.apk\" required=\"true\" size=\"0\"/>";
response += "</packages>";
response += "<actions>";
response += "<action event=\"install\" run=\"dummy.apk\"/>";
response += "<action event=\"postinstall\"/>";
response += "</actions>";
response += "</manifest>";
response += "</updatecheck>";
response += "<ping status=\"ok\"/>";
}
response += "</app>";
response += "</response>";
return response;
}
@Override
public boolean usingProxy() {
return false;
}
@Override
public void connect() throws SocketTimeoutException {
if (mConnectionTimesOut) {
throw new SocketTimeoutException("Connection timed out.");
}
}
@Override
public void disconnect() {}
@Override
public void setDoOutput(boolean value) throws IllegalAccessError {
Assert.assertTrue("Told the HTTPUrlConnection to send no request.", value);
}
@Override
public void setFixedLengthStreamingMode(int contentLength) {
mContentLength = contentLength;
}
@Override
public int getResponseCode() {
if (mNumTimesResponseCodeRetrieved == 0) {
// The output stream should now have the generated XML for the request.
// Check if its length is correct.
Assert.assertEquals(
"Expected OmahaBase to write out certain number of bytes",
mContentLength,
mOutputStream.toByteArray().length);
}
Assert.assertTrue(
"Tried to retrieve response code more than twice",
mNumTimesResponseCodeRetrieved < 2);
mNumTimesResponseCodeRetrieved++;
return mHTTPResponseCode;
}
@Override
public OutputStream getOutputStream() throws IOException {
mSentRequest = true;
connect();
return mOutputStream;
}
public String getOutputStreamContents() {
return mOutputStream.toString();
}
@Override
public InputStream getInputStream() {
Assert.assertTrue(
"Tried to read server response without sending request.", mSentRequest);
mGotInputStream = true;
return mServerResponse;
}
@Override
public void addRequestProperty(String field, String newValue) {
mRequestPropertyField = field;
mRequestPropertyValue = newValue;
}
public int getNumTimesResponseCodeRetrieved() {
return mNumTimesResponseCodeRetrieved;
}
public boolean getGotInputStream() {
return mGotInputStream;
}
public boolean getSentRequest() {
return mSentRequest;
}
public String getRequestPropertyField() {
return mRequestPropertyField;
}
public String getRequestPropertyValue() {
return mRequestPropertyValue;
}
}
}