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 { |