blob: 4e1a067907597e46d225b78f7256eba118bdd7f2 [file] [log] [blame]
// Copyright 2014 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.native_test;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An Instrumentation that runs tests based on ChromeNativeTestActivity.
*/
public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
// TODO(jbudorick): Remove this extra when b/18981674 is fixed.
public static final String EXTRA_ONLY_OUTPUT_FAILURES =
"org.chromium.native_test.ChromeNativeTestInstrumentationTestRunner."
+ "OnlyOutputFailures";
private static final String TAG = "ChromeNativeTestInstrumentationTestRunner";
private static final int ACCEPT_TIMEOUT_MS = 5000;
private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*");
private static interface ResultsBundleGenerator {
public Bundle generate(Map<String, TestResult> rawResults);
}
private String mCommandLineFile;
private String mCommandLineFlags;
private File mStdoutFile;
private Bundle mLogBundle;
private ResultsBundleGenerator mBundleGenerator;
private boolean mOnlyOutputFailures;
@Override
public void onCreate(Bundle arguments) {
mCommandLineFile = arguments.getString(ChromeNativeTestActivity.EXTRA_COMMAND_LINE_FILE);
mCommandLineFlags = arguments.getString(ChromeNativeTestActivity.EXTRA_COMMAND_LINE_FLAGS);
try {
mStdoutFile = File.createTempFile(
".temp_stdout_", ".txt", Environment.getExternalStorageDirectory());
Log.i(TAG, "stdout file created: " + mStdoutFile.getAbsolutePath());
} catch (IOException e) {
Log.e(TAG, "Unable to create temporary stdout file." + e.toString());
finish(Activity.RESULT_CANCELED, new Bundle());
return;
}
mLogBundle = new Bundle();
mBundleGenerator = new RobotiumBundleGenerator();
mOnlyOutputFailures = arguments.containsKey(EXTRA_ONLY_OUTPUT_FAILURES);
start();
}
@Override
public void onStart() {
super.onStart();
Bundle results = runTests();
finish(Activity.RESULT_OK, results);
}
/** Runs the tests in the ChromeNativeTestActivity and returns a Bundle containing the results.
*/
private Bundle runTests() {
Log.i(TAG, "Creating activity.");
Activity activityUnderTest = startNativeTestActivity();
Log.i(TAG, "Waiting for tests to finish.");
try {
while (!activityUnderTest.isFinishing()) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for activity to be destroyed: " + e.toString());
}
Log.i(TAG, "Getting results.");
Map<String, TestResult> results = parseResults(activityUnderTest);
Log.i(TAG, "Parsing results and generating output.");
return mBundleGenerator.generate(results);
}
/** Starts the ChromeNativeTestActivty.
*/
private Activity startNativeTestActivity() {
Intent i = new Intent(Intent.ACTION_MAIN);
i.setComponent(new ComponentName(
"org.chromium.native_test",
"org.chromium.native_test.ChromeNativeTestActivity"));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (mCommandLineFile != null) {
Log.i(TAG, "Passing command line file extra: " + mCommandLineFile);
i.putExtra(ChromeNativeTestActivity.EXTRA_COMMAND_LINE_FILE, mCommandLineFile);
}
if (mCommandLineFlags != null) {
Log.i(TAG, "Passing command line flag extra: " + mCommandLineFlags);
i.putExtra(ChromeNativeTestActivity.EXTRA_COMMAND_LINE_FLAGS, mCommandLineFlags);
}
i.putExtra(ChromeNativeTestActivity.EXTRA_STDOUT_FILE, mStdoutFile.getAbsolutePath());
return startActivitySync(i);
}
private static enum TestResult {
PASSED, FAILED, ERROR, UNKNOWN
}
/**
* Generates a map between test names and test results from the instrumented Activity's
* output.
*/
private Map<String, TestResult> parseResults(Activity activityUnderTest) {
Map<String, TestResult> results = new HashMap<String, TestResult>();
BufferedReader r = null;
try {
if (mStdoutFile == null || !mStdoutFile.exists()) {
Log.e(TAG, "Unable to find stdout file.");
return results;
}
r = new BufferedReader(new InputStreamReader(
new BufferedInputStream(new FileInputStream(mStdoutFile))));
for (String l = r.readLine(); l != null && !l.equals("<<ScopedMainEntryLogger");
l = r.readLine()) {
Matcher m = RE_TEST_OUTPUT.matcher(l);
boolean isFailure = false;
if (m.matches()) {
if (m.group(1).equals("RUN")) {
results.put(m.group(2), TestResult.UNKNOWN);
} else if (m.group(1).equals("FAILED")) {
results.put(m.group(2), TestResult.FAILED);
isFailure = true;
mLogBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, l + "\n");
sendStatus(0, mLogBundle);
} else if (m.group(1).equals("OK")) {
results.put(m.group(2), TestResult.PASSED);
}
}
// TODO(jbudorick): mOnlyOutputFailures is a workaround for b/18981674. Remove it
// once that issue is fixed.
if (!mOnlyOutputFailures || isFailure) {
mLogBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, l + "\n");
sendStatus(0, mLogBundle);
}
Log.i(TAG, l);
}
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find stdout file file: " + e.toString());
} catch (IOException e) {
Log.e(TAG, "Error handling stdout file: " + e.toString());
} finally {
if (r != null) {
try {
r.close();
} catch (IOException e) {
Log.e(TAG, "Error while closing stdout reader.");
}
}
if (mStdoutFile != null) {
if (!mStdoutFile.delete()) {
Log.e(TAG, "Unable to delete " + mStdoutFile.getAbsolutePath());
}
}
}
return results;
}
/**
* Creates a results bundle that emulates the one created by Robotium.
*/
private static class RobotiumBundleGenerator implements ResultsBundleGenerator {
public Bundle generate(Map<String, TestResult> rawResults) {
Bundle resultsBundle = new Bundle();
int testsPassed = 0;
int testsFailed = 0;
for (Map.Entry<String, TestResult> entry : rawResults.entrySet()) {
switch (entry.getValue()) {
case PASSED:
++testsPassed;
break;
case FAILED:
// TODO(jbudorick): Remove this log message once AMP execution and
// results handling has been stabilized.
Log.d(TAG, "FAILED: " + entry.getKey());
++testsFailed;
break;
default:
Log.w(TAG, "Unhandled: " + entry.getKey() + ", "
+ entry.getValue().toString());
break;
}
}
StringBuilder resultBuilder = new StringBuilder();
if (testsFailed > 0) {
resultBuilder.append(
"\nFAILURES!!! Tests run: " + Integer.toString(rawResults.size())
+ ", Failures: " + Integer.toString(testsFailed) + ", Errors: 0");
} else {
resultBuilder.append("\nOK (" + Integer.toString(testsPassed) + " tests)");
}
resultsBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
resultBuilder.toString());
return resultsBundle;
}
}
}