Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(78)

Unified Diff: chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java

Issue 2432593002: [Offline Pages] Add basic evaluation tests and related changes. (Closed)
Patch Set: Fix build failure. Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/android/java_sources.gni ('k') | chrome/browser/BUILD.gn » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..db1854f7e721d263c70e8d0d72f2177496d4d67c
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageSavePageLaterEvaluationTest.java
@@ -0,0 +1,438 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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.offlinepages;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.LongSparseArray;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.Manual;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.offlinepages.evaluation.OfflinePageEvaluationBridge;
+import org.chromium.chrome.browser.offlinepages.evaluation.OfflinePageEvaluationBridge.OfflinePageEvaluationObserver;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.test.ChromeActivityTestCaseBase;
+import org.chromium.components.offlinepages.BackgroundSavePageResult;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests OfflinePageBridge.SavePageLater over a batch of urls.
+ * Tests against a list of top EM urls, try to call SavePageLater on each of the url. It also
+ * record metrics (failure rate, time elapsed etc.) by writing metrics to a file on external
+ * storage.
+ * */
+public class OfflinePageSavePageLaterEvaluationTest
+ extends ChromeActivityTestCaseBase<ChromeActivity> {
+ /**
+ * Class which is used to calculate time difference.
+ */
+ static class TimeDelta {
+ public void setStartTime(Long startTime) {
+ mStartTime = startTime;
+ }
+ public void setEndTime(Long endTime) {
+ mEndTime = endTime;
+ }
+ // Return time delta in milliseconds.
+ public Long getTimeDelta() {
+ return mEndTime - mStartTime;
+ }
+
+ private Long mStartTime, mEndTime;
+ }
+
+ static class RequestMetadata {
+ public long mId;
+ public OfflinePageItem mPage;
+ public int mStatus;
+ public TimeDelta mTimeDelta;
+ public String mUrl;
+ }
+
+ private static final String TAG = "OPSPLEvaluation";
+ private static final String NAMESPACE = "async_loading";
+ private static final String NEW_LINE = System.getProperty("line.separator");
+ private static final String INPUT_FILE_PATH = "paquete/offline_eval_urls.txt";
+ private static final String RESULT_OUTPUT_FILE_PATH = "paquete/offline_eval_results.txt";
+ private static final String LOG_OUTPUT_FILE_PATH = "paquete/offline_eval_logs.txt";
+ private static final int PAGE_MODEL_LOAD_TIMEOUT_MS = 5000;
+ private static final int GET_PAGES_TIMEOUT_MS = 5000;
+ private static final int TIMEOUT_PER_URL_USUAL_CASE = 180;
+ private static final int TIMEOUT_PER_URL_AUTO_SCHEDULE = 24 * 60 * 60;
+ private static final String DELIMITER = ";";
+ private static final long DEFAULT_TIMEOUT_PER_URL_IN_SECONDS = 180L;
+
+ private OfflinePageEvaluationBridge mBridge;
+ private OfflinePageEvaluationObserver mObserver;
+
+ private Semaphore mDoneSemaphore;
+ private List<String> mUrls;
+ private int mCount;
+ private boolean mIsUserRequested;
+ private boolean mUseTestScheduler;
+
+ private LongSparseArray<RequestMetadata> mRequestMetadata;
+ private Long mTimeoutPerUrlInSeconds = 0L;
+ private String mInputFilePath = INPUT_FILE_PATH;
+ private String mResultOutputFilePath = RESULT_OUTPUT_FILE_PATH;
+ private String mLogOutputFilePath = LOG_OUTPUT_FILE_PATH;
+ private OutputStreamWriter mLogOutput;
+
+ public OfflinePageSavePageLaterEvaluationTest() {
+ super(ChromeActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mRequestMetadata = new LongSparseArray<RequestMetadata>();
+ mCount = 0;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ NotificationManager notificationManager =
+ (NotificationManager) ContextUtils.getApplicationContext().getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+
+ @Override
+ public void startMainActivity() throws InterruptedException {
+ startMainActivityOnBlankPage();
+ }
+
+ /**
+ * Get a reader for a given input file path.
+ */
+ private BufferedReader getInputStream(String inputFilePath) throws FileNotFoundException {
+ FileReader fileReader =
+ new FileReader(new File(Environment.getExternalStorageDirectory(), inputFilePath));
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+ return bufferedReader;
+ }
+
+ /**
+ * Get a writer for given output file path.
+ */
+ private OutputStreamWriter getOutputStream(String outputFilePath) throws IOException {
+ File outputFile = new File(Environment.getExternalStorageDirectory(), outputFilePath);
+ return new FileWriter(outputFile);
+ }
+
+ /**
+ * Logs error in both console and output file.
+ */
+ private void logError(String error) {
+ Log.e(TAG, error);
+ if (mLogOutput != null) {
+ try {
+ mLogOutput.write(error);
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Initializes the evaluation bridge which will be used.
+ * @param useCustomScheduler True if customized scheduler (the one with immediate scheduling)
+ * will be used. False otherwise.
+ */
+ private void initializeBridgeForProfile(final boolean useTestingScheduler)
+ throws InterruptedException {
+ final Semaphore semaphore = new Semaphore(0);
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Profile profile = Profile.getLastUsedProfile();
+ mBridge = OfflinePageEvaluationBridge.getForProfile(profile, useTestingScheduler);
+ if (mBridge == null) {
+ logError("OfflinePageEvaluationBridge initialization failed!");
+ return;
+ }
+ if (mBridge.isOfflinePageModelLoaded()) {
+ semaphore.release();
+ return;
+ }
+ mBridge.addObserver(new OfflinePageEvaluationObserver() {
+ @Override
+ public void offlinePageModelLoaded() {
+ semaphore.release();
+ mBridge.removeObserver(this);
+ }
+ });
+ }
+ });
+ assertTrue(semaphore.tryAcquire(PAGE_MODEL_LOAD_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ /**
+ * Set up the input/output, bridge and observer we're going to use.
+ * @param useCustomScheduler True if customized scheduler (the one with immediate scheduling)
+ * will be used. False otherwise.
+ */
+ protected void setUpIOAndBridge(final boolean useCustomScheduler) throws InterruptedException {
+ // TODO(romax): Get the file urls from command line/user input.
+ try {
+ mLogOutput = getOutputStream(mLogOutputFilePath);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Cannot set output file!");
+ Log.wtf(TAG, e.getMessage(), e);
+ }
+ try {
+ getUrlListFromInputFile(mInputFilePath);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Cannot read input file!");
+ Log.wtf(TAG, e.getMessage(), e);
+ }
+ assertTrue("URLs weren't loaded.", mUrls != null);
+ assertFalse("No valid URLs in the input file.", mUrls.size() == 0);
+
+ initializeBridgeForProfile(useCustomScheduler);
+ mObserver = new OfflinePageEvaluationObserver() {
+ public void savePageRequestAdded(SavePageRequest request) {
+ RequestMetadata metadata = new RequestMetadata();
+ metadata.mId = request.getRequestId();
+ metadata.mUrl = request.getUrl();
+ metadata.mStatus = -1;
+ TimeDelta timeDelta = new TimeDelta();
+ timeDelta.setStartTime(System.currentTimeMillis());
+ metadata.mTimeDelta = timeDelta;
+ mRequestMetadata.put(request.getRequestId(), metadata);
+ }
+ public void savePageRequestCompleted(SavePageRequest request, int status) {
+ RequestMetadata metadata = mRequestMetadata.get(request.getRequestId());
+ metadata.mTimeDelta.setEndTime(System.currentTimeMillis());
+ if (metadata.mStatus == -1) {
+ mCount++;
+ }
+ metadata.mStatus = status;
+ if (mCount == mUrls.size()) {
+ mDoneSemaphore.release();
+ return;
+ }
+ }
+ public void savePageRequestChanged(SavePageRequest request) {}
+ };
+ mBridge.addObserver(mObserver);
+ }
+
+ /**
+ * Calls SavePageLater on the bridge to try to offline an url.
+ * @param url The url to be saved.
+ * @param namespace The namespace this request belongs to.
+ */
+ private void savePageLater(final String url, final String namespace)
+ throws InterruptedException {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBridge.savePageLater(url, namespace, mIsUserRequested);
+ }
+ });
+ }
+
+ private void processUrls(List<String> urls) throws InterruptedException, IOException {
+ if (mBridge == null) {
+ logError("Test initialization error, aborting. No results would be written.");
+ return;
+ }
+ mDoneSemaphore = new Semaphore(0);
+ // If no timeout value is given, set 180 seconds for each url as default.
+ // TODO(romax): Find a way to get network condition and apply different default values.
+ if (mTimeoutPerUrlInSeconds == 0) {
+ mTimeoutPerUrlInSeconds = DEFAULT_TIMEOUT_PER_URL_IN_SECONDS;
+ }
+ for (String url : mUrls) {
+ savePageLater(url, NAMESPACE);
+ }
+
+ if (!mDoneSemaphore.tryAcquire(urls.size() * mTimeoutPerUrlInSeconds, TimeUnit.SECONDS)) {
+ writeResults(false);
+ } else {
+ writeResults(true);
+ }
+ }
+
+ private void getUrlListFromInputFile(String inputFilePath)
+ throws IOException, InterruptedException {
+ mUrls = new ArrayList<String>();
+ try {
+ BufferedReader bufferedReader = getInputStream(inputFilePath);
+ try {
+ String url;
+ while ((url = bufferedReader.readLine()) != null) {
+ if (!TextUtils.isEmpty(url)) {
+ mUrls.add(url);
+ }
+ }
+ } finally {
+ if (bufferedReader != null) {
+ bufferedReader.close();
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getMessage(), e);
+ fail(String.format("URL file %s is not found.", inputFilePath));
+ }
+ }
+
+ // Translate the int value of status to BackgroundSavePageResult.
+ private String statusToString(int status) {
+ switch (status) {
+ case BackgroundSavePageResult.SUCCESS:
+ return "SUCCESS";
+ case BackgroundSavePageResult.PRERENDER_FAILURE:
+ return "PRERENDER_FAILURE";
+ case BackgroundSavePageResult.PRERENDER_CANCELED:
+ return "PRERENDER_CANCELED";
+ case BackgroundSavePageResult.FOREGROUND_CANCELED:
+ return "FOREGROUND_CANCELED";
+ case BackgroundSavePageResult.SAVE_FAILED:
+ return "SAVE_FAILED";
+ case BackgroundSavePageResult.EXPIRED:
+ return "EXPIRED";
+ case BackgroundSavePageResult.RETRY_COUNT_EXCEEDED:
+ return "RETRY_COUNT_EXCEEDED";
+ case BackgroundSavePageResult.START_COUNT_EXCEEDED:
+ return "START_COUNT_EXCEEDED";
+ case BackgroundSavePageResult.REMOVED:
+ return "REMOVED";
+ case -1:
+ return "NOT_COMPLETED";
+ default:
+ return "UNDEFINED_STATUS";
+ }
+ }
+
+ /**
+ * Get saved offline pages and align them with the metadata we got from testing.
+ */
+ private void loadSavedPages() throws InterruptedException {
+ final Semaphore semaphore = new Semaphore(0);
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBridge.getAllPages(new Callback<List<OfflinePageItem>>() {
+ @Override
+ public void onResult(List<OfflinePageItem> pages) {
+ for (OfflinePageItem page : pages) {
+ mRequestMetadata.get(page.getOfflineId()).mPage = page;
+ }
+ semaphore.release();
+ }
+ });
+ }
+ });
+ assertTrue(semaphore.tryAcquire(GET_PAGES_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ /**
+ * Writes test results to output file. The format would be:
+ * URL;OFFLINE_STATUS;FILE_SIZE;TIME_SINCE_TEST_START
+ * If page loading failed, size and timestamp would not be written to file.
+ * Examples:
+ * http://indianrail.gov.in/;START_COUNT_EXCEEDED
+ * http://www.21cineplex.com/;SUCCESS;1160 KB;171700
+ * https://www.google.com/;SUCCESS;110 KB;273805
+ * At the end of the file there will be a summary:
+ * Total requested URLs: XX, Completed: XX, Failed: XX, Failure Rate: XX.XX%
+ */
+ private void writeResults(boolean completed) throws IOException, InterruptedException {
+ loadSavedPages();
+ OutputStreamWriter output = getOutputStream(mResultOutputFilePath);
+ try {
+ int failedCount = 0;
+ if (!completed) {
+ logError("Test terminated before all requests completed." + NEW_LINE);
+ }
+ for (int i = 0; i < mRequestMetadata.size(); i++) {
+ RequestMetadata metadata = mRequestMetadata.valueAt(i);
+ long requestId = metadata.mId;
+ int status = metadata.mStatus;
+ OfflinePageItem page = metadata.mPage;
+ if (page == null) {
+ output.write(metadata.mUrl + DELIMITER + statusToString(status) + NEW_LINE);
+ if (status != -1) {
+ failedCount++;
+ }
+ continue;
+ }
+ output.write(metadata.mUrl + DELIMITER + statusToString(status) + DELIMITER
+ + page.getFileSize() / 1000 + " KB" + DELIMITER
+ + metadata.mTimeDelta.getTimeDelta() + NEW_LINE);
+ }
+ output.write(String.format(
+ "Total requested URLs: %d, Completed: %d, Failed: %d, Failure Rate: %.2f%%"
+ + NEW_LINE,
+ mUrls.size(), mCount, failedCount, (failedCount * 100.0 / mCount)));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } finally {
+ if (output != null) {
+ output.close();
+ }
+ }
+ }
+
+ /**
+ * The tests would terminate after #urls * mTimeoutPerUrlInSeconds even if some urls are still
+ * being processed.
+ */
+ @Manual
+ public void testFailureRateWithTimeoutPerUrl() throws IOException, InterruptedException {
+ // TODO(romax) All manual setting of private attributes should be considered moving to a
+ // config file or from command-line by user. Also find a better place for default values.
+ mTimeoutPerUrlInSeconds = (long) (TIMEOUT_PER_URL_USUAL_CASE);
+ mIsUserRequested = true;
+ mResultOutputFilePath = RESULT_OUTPUT_FILE_PATH;
+ mUseTestScheduler = true;
+ // Use testing scheduler.
+ setUpIOAndBridge(mUseTestScheduler);
+ processUrls(mUrls);
+ }
+
+ @Manual
+ public void testFailureRate() throws IOException, InterruptedException {
+ mTimeoutPerUrlInSeconds = (long) (TIMEOUT_PER_URL_AUTO_SCHEDULE);
+ mResultOutputFilePath = RESULT_OUTPUT_FILE_PATH;
+ mIsUserRequested = false;
+ mUseTestScheduler = true;
+ // Use testing scheduler.
+ setUpIOAndBridge(mUseTestScheduler);
+ processUrls(mUrls);
+ }
+
+ @Manual
+ public void testFailureRateWithGCMScheduler() throws IOException, InterruptedException {
+ mTimeoutPerUrlInSeconds = (long) (TIMEOUT_PER_URL_AUTO_SCHEDULE);
+ mResultOutputFilePath = RESULT_OUTPUT_FILE_PATH;
+ mIsUserRequested = false;
+ mUseTestScheduler = false;
+ // Use default scheduler with GCMNetworkManager.
+ setUpIOAndBridge(mUseTestScheduler);
+ processUrls(mUrls);
+ }
+}
« no previous file with comments | « chrome/android/java_sources.gni ('k') | chrome/browser/BUILD.gn » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698