| Index: testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
|
| diff --git a/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java b/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
|
| index 4db6286258a5b13d85d70d386d9233e6f7cf6cee..6e39401882a20d741df6361ac6951cfb31643c51 100644
|
| --- a/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
|
| +++ b/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
|
| @@ -5,13 +5,19 @@
|
| package org.chromium.native_test;
|
|
|
| import android.app.Activity;
|
| +import android.app.ActivityManager;
|
| import android.app.Instrumentation;
|
| import android.content.ComponentName;
|
| +import android.content.Context;
|
| import android.content.Intent;
|
| import android.os.Bundle;
|
| import android.os.Environment;
|
| +import android.os.Handler;
|
| +import android.os.Process;
|
| +import android.util.SparseArray;
|
|
|
| import org.chromium.base.Log;
|
| +import org.chromium.test.reporter.TestStatusReceiver;
|
| import org.chromium.test.support.ResultsBundleGenerator;
|
| import org.chromium.test.support.RobotiumBundleGenerator;
|
|
|
| @@ -20,10 +26,15 @@ import java.io.BufferedReader;
|
| import java.io.File;
|
| import java.io.FileInputStream;
|
| import java.io.FileNotFoundException;
|
| +import java.io.FileReader;
|
| import java.io.IOException;
|
| import java.io.InputStreamReader;
|
| +import java.util.ArrayDeque;
|
| +import java.util.ArrayList;
|
| import java.util.HashMap;
|
| import java.util.Map;
|
| +import java.util.Queue;
|
| +import java.util.concurrent.atomic.AtomicBoolean;
|
| import java.util.regex.Matcher;
|
| import java.util.regex.Pattern;
|
|
|
| @@ -33,22 +44,38 @@ import java.util.regex.Pattern;
|
| public class NativeTestInstrumentationTestRunner extends Instrumentation {
|
|
|
| public static final String EXTRA_NATIVE_TEST_ACTIVITY =
|
| - "org.chromium.native_test.NativeTestInstrumentationTestRunner."
|
| - + "NativeTestActivity";
|
| + "org.chromium.native_test.NativeTestInstrumentationTestRunner.NativeTestActivity";
|
| + public static final String EXTRA_SHARD_NANO_TIMEOUT =
|
| + "org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardNanoTimeout";
|
| + public static final String EXTRA_SHARD_SIZE_LIMIT =
|
| + "org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardSizeLimit";
|
| + public static final String EXTRA_TEST_LIST_FILE =
|
| + "org.chromium.native_test.NativeTestInstrumentationTestRunner.TestList";
|
|
|
| - private static final String TAG = Log.makeTag("native_test");
|
| + private static final String TAG = "cr.native_test";
|
|
|
| - private static final int ACCEPT_TIMEOUT_MS = 5000;
|
| + private static final long DEFAULT_SHARD_NANO_TIMEOUT = 60 * 1000000000L;
|
| + // Default to no size limit.
|
| + private static final int DEFAULT_SHARD_SIZE_LIMIT = 0;
|
| private static final String DEFAULT_NATIVE_TEST_ACTIVITY =
|
| "org.chromium.native_test.NativeUnitTestActivity";
|
| - private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*");
|
| + private static final Pattern RE_TEST_OUTPUT =
|
| + Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+)( .*)?$");
|
|
|
| private ResultsBundleGenerator mBundleGenerator = new RobotiumBundleGenerator();
|
| private String mCommandLineFile;
|
| private String mCommandLineFlags;
|
| + private Handler mHandler = new Handler();
|
| private String mNativeTestActivity;
|
| private Bundle mLogBundle = new Bundle();
|
| + private TestStatusReceiver mReceiver;
|
| + private Map<String, ResultsBundleGenerator.TestResult> mResults =
|
| + new HashMap<String, ResultsBundleGenerator.TestResult>();
|
| + private Queue<ArrayList<String>> mShards = new ArrayDeque<ArrayList<String>>();
|
| + private long mShardNanoTimeout = DEFAULT_SHARD_NANO_TIMEOUT;
|
| + private int mShardSizeLimit = DEFAULT_SHARD_SIZE_LIMIT;
|
| private File mStdoutFile;
|
| + private SparseArray<ShardMonitor> mMonitors = new SparseArray<ShardMonitor>();
|
|
|
| @Override
|
| public void onCreate(Bundle arguments) {
|
| @@ -57,6 +84,39 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
|
| mNativeTestActivity = arguments.getString(EXTRA_NATIVE_TEST_ACTIVITY);
|
| if (mNativeTestActivity == null) mNativeTestActivity = DEFAULT_NATIVE_TEST_ACTIVITY;
|
|
|
| + String shardNanoTimeout = arguments.getString(EXTRA_SHARD_NANO_TIMEOUT);
|
| + if (shardNanoTimeout != null) mShardNanoTimeout = Long.parseLong(shardNanoTimeout);
|
| +
|
| + String shardSizeLimit = arguments.getString(EXTRA_SHARD_SIZE_LIMIT);
|
| + if (shardSizeLimit != null) mShardSizeLimit = Integer.parseInt(shardSizeLimit);
|
| +
|
| + String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE);
|
| + if (testListFilePath != null) {
|
| + File testListFile = new File(testListFilePath);
|
| + try {
|
| + BufferedReader testListFileReader =
|
| + new BufferedReader(new FileReader(testListFile));
|
| +
|
| + String test;
|
| + ArrayList<String> workingShard = new ArrayList<String>();
|
| + while ((test = testListFileReader.readLine()) != null) {
|
| + workingShard.add(test);
|
| + if (workingShard.size() == mShardSizeLimit) {
|
| + mShards.add(workingShard);
|
| + workingShard = new ArrayList<String>();
|
| + }
|
| + }
|
| +
|
| + if (!workingShard.isEmpty()) {
|
| + mShards.add(workingShard);
|
| + }
|
| +
|
| + testListFileReader.close();
|
| + } catch (IOException e) {
|
| + Log.e(TAG, "Error reading %s", testListFile.getAbsolutePath(), e);
|
| + }
|
| + }
|
| +
|
| try {
|
| mStdoutFile = File.createTempFile(
|
| ".temp_stdout_", ".txt", Environment.getExternalStorageDirectory());
|
| @@ -66,62 +126,153 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
|
| finish(Activity.RESULT_CANCELED, new Bundle());
|
| return;
|
| }
|
| +
|
| start();
|
| }
|
|
|
| @Override
|
| public void onStart() {
|
| super.onStart();
|
| - Bundle results = runTests();
|
| - finish(Activity.RESULT_OK, results);
|
| +
|
| + mReceiver = new TestStatusReceiver();
|
| + mReceiver.register(getContext());
|
| + mReceiver.registerCallback(new TestStatusReceiver.TestRunCallback() {
|
| + @Override
|
| + public void testRunStarted(int pid) {
|
| + if (pid != Process.myPid()) {
|
| + ShardMonitor m = new ShardMonitor(
|
| + pid, System.nanoTime() + mShardNanoTimeout);
|
| + mMonitors.put(pid, m);
|
| + mHandler.post(m);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void testRunFinished(int pid) {
|
| + ShardMonitor m = mMonitors.get(pid);
|
| + if (m != null) {
|
| + m.stopped();
|
| + mMonitors.remove(pid);
|
| + }
|
| + mHandler.post(new ShardEnder(pid));
|
| + }
|
| + });
|
| +
|
| + mHandler.post(new ShardStarter());
|
| }
|
|
|
| - /** Runs the tests in the NativeTestActivity and returns a Bundle containing the results.
|
| - */
|
| - private Bundle runTests() {
|
| - Log.i(TAG, "Creating activity.");
|
| - Activity activityUnderTest = startNativeTestActivity();
|
| + /** Monitors a test shard's execution. */
|
| + private class ShardMonitor implements Runnable {
|
| + private static final int MONITOR_FREQUENCY_MS = 1000;
|
|
|
| - Log.i(TAG, "Waiting for tests to finish.");
|
| - try {
|
| - while (!activityUnderTest.isFinishing()) {
|
| - Thread.sleep(100);
|
| + private long mExpirationNanoTime;
|
| + private int mPid;
|
| + private AtomicBoolean mStopped;
|
| +
|
| + public ShardMonitor(int pid, long expirationNanoTime) {
|
| + mPid = pid;
|
| + mExpirationNanoTime = expirationNanoTime;
|
| + mStopped = new AtomicBoolean(false);
|
| + }
|
| +
|
| + public void stopped() {
|
| + mStopped.set(true);
|
| + }
|
| +
|
| + @Override
|
| + public void run() {
|
| + if (mStopped.get()) {
|
| + return;
|
| + }
|
| +
|
| + if (isAppProcessAlive(getContext(), mPid)) {
|
| + if (System.nanoTime() > mExpirationNanoTime) {
|
| + Log.e(TAG, "Test process %d timed out.", mPid);
|
| + mHandler.post(new ShardEnder(mPid));
|
| + return;
|
| + } else {
|
| + mHandler.postDelayed(this, MONITOR_FREQUENCY_MS);
|
| + return;
|
| + }
|
| }
|
| - } catch (InterruptedException e) {
|
| - Log.e(TAG, "Interrupted while waiting for activity to be destroyed: ", e);
|
| +
|
| + Log.e(TAG, "Test process %d died unexpectedly.", mPid);
|
| + mHandler.post(new ShardEnder(mPid));
|
| }
|
|
|
| - Log.i(TAG, "Getting results.");
|
| - Map<String, ResultsBundleGenerator.TestResult> results = parseResults(activityUnderTest);
|
| + }
|
|
|
| - Log.i(TAG, "Parsing results and generating output.");
|
| - return mBundleGenerator.generate(results);
|
| + private static boolean isAppProcessAlive(Context context, int pid) {
|
| + ActivityManager activityManager =
|
| + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
| + for (ActivityManager.RunningAppProcessInfo processInfo :
|
| + activityManager.getRunningAppProcesses()) {
|
| + if (processInfo.pid == pid) return true;
|
| + }
|
| + return false;
|
| }
|
|
|
| /** Starts the NativeTestActivty.
|
| */
|
| - private Activity startNativeTestActivity() {
|
| - Intent i = new Intent(Intent.ACTION_MAIN);
|
| - i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity));
|
| - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
| - if (mCommandLineFile != null) {
|
| - Log.i(TAG, "Passing command line file extra: %s", mCommandLineFile);
|
| - i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FILE, mCommandLineFile);
|
| + private class ShardStarter implements Runnable {
|
| + @Override
|
| + public void run() {
|
| + Intent i = new Intent(Intent.ACTION_MAIN);
|
| + i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity));
|
| + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
| + if (mCommandLineFile != null) {
|
| + Log.i(TAG, "Passing command line file extra: %s", mCommandLineFile);
|
| + i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FILE, mCommandLineFile);
|
| + }
|
| + if (mCommandLineFlags != null) {
|
| + Log.i(TAG, "Passing command line flag extra: %s", mCommandLineFlags);
|
| + i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FLAGS, mCommandLineFlags);
|
| + }
|
| + if (mShards != null && !mShards.isEmpty()) {
|
| + ArrayList<String> shard = mShards.remove();
|
| + i.putStringArrayListExtra(NativeTestActivity.EXTRA_SHARD, shard);
|
| + }
|
| + i.putExtra(NativeTestActivity.EXTRA_STDOUT_FILE, mStdoutFile.getAbsolutePath());
|
| + getContext().startActivity(i);
|
| }
|
| - if (mCommandLineFlags != null) {
|
| - Log.i(TAG, "Passing command line flag extra: %s", mCommandLineFlags);
|
| - i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FLAGS, mCommandLineFlags);
|
| + }
|
| +
|
| + private class ShardEnder implements Runnable {
|
| + private static final int WAIT_FOR_DEATH_MILLIS = 10;
|
| +
|
| + private int mPid;
|
| +
|
| + public ShardEnder(int pid) {
|
| + mPid = pid;
|
| + }
|
| +
|
| + @Override
|
| + public void run() {
|
| + if (mPid != Process.myPid()) {
|
| + Process.killProcess(mPid);
|
| + try {
|
| + while (isAppProcessAlive(getContext(), mPid)) {
|
| + Thread.sleep(WAIT_FOR_DEATH_MILLIS);
|
| + }
|
| + } catch (InterruptedException e) {
|
| + Log.e(TAG, "%d may still be alive.", mPid, e);
|
| + }
|
| + }
|
| + mResults.putAll(parseResults());
|
| +
|
| + if (mShards != null && !mShards.isEmpty()) {
|
| + mHandler.post(new ShardStarter());
|
| + } else {
|
| + finish(Activity.RESULT_OK, mBundleGenerator.generate(mResults));
|
| + }
|
| }
|
| - i.putExtra(NativeTestActivity.EXTRA_STDOUT_FILE, mStdoutFile.getAbsolutePath());
|
| - return startActivitySync(i);
|
| }
|
|
|
| /**
|
| * Generates a map between test names and test results from the instrumented Activity's
|
| * output.
|
| */
|
| - private Map<String, ResultsBundleGenerator.TestResult> parseResults(
|
| - Activity activityUnderTest) {
|
| + private Map<String, ResultsBundleGenerator.TestResult> parseResults() {
|
| Map<String, ResultsBundleGenerator.TestResult> results =
|
| new HashMap<String, ResultsBundleGenerator.TestResult>();
|
|
|
| @@ -153,7 +304,7 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
|
| Log.i(TAG, l);
|
| }
|
| } catch (FileNotFoundException e) {
|
| - Log.e(TAG, "Couldn't find stdout file file: ", e);
|
| + Log.e(TAG, "Couldn't find stdout file: ", e);
|
| } catch (IOException e) {
|
| Log.e(TAG, "Error handling stdout file: ", e);
|
| } finally {
|
|
|