Skip to content

Instantly share code, notes, and snippets.

@tomkoptel
Created April 23, 2018 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tomkoptel/ee2e8da4ceeda954051e4b076f9ce718 to your computer and use it in GitHub Desktop.
Save tomkoptel/ee2e8da4ceeda954051e4b076f9ce718 to your computer and use it in GitHub Desktop.
Extracted AndroidTestOrchestrator class from orchestrator-1.0.2-beta1.apk
package android.support.test.orchestrator;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
import android.support.test.internal.runner.tracker.AnalyticsBasedUsageTracker;
import android.support.test.orchestrator.TestRunnable.RunFinishedListener;
import android.support.test.orchestrator.junit.ParcelableDescription;
import android.support.test.orchestrator.listeners.OrchestrationListenerManager;
import android.support.test.orchestrator.listeners.OrchestrationResult.Builder;
import android.support.test.orchestrator.listeners.OrchestrationResultPrinter;
import android.support.test.orchestrator.listeners.OrchestrationXmlTestRunListener;
import android.support.test.runner.UsageTrackerFacilitator;
import android.support.test.services.shellexecutor.ShellCommandClient;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
public final class AndroidTestOrchestrator extends Instrumentation implements RunFinishedListener {
private static final Pattern FULLY_QUALIFIED_CLASS_AND_METHOD = Pattern.compile("[\\w\\.?]+#\\w+");
private static final List<String> RUNTIME_PERMISSIONS = Arrays.asList(new String[]{"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE"});
private volatile CallbackLogic callbackLogic;
private final OrchestrationListenerManager listenerManager = new OrchestrationListenerManager(this);
private Bundle mArguments;
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i("AndroidTestOrchestrator", "AndroidTestOrchestrator has connected to the orchestration service");
AndroidTestOrchestrator.this.callbackLogic = (CallbackLogic) iBinder;
AndroidTestOrchestrator.this.callbackLogic.setListenerManager(AndroidTestOrchestrator.this.listenerManager);
AndroidTestOrchestrator.this.collectTests();
}
public void onServiceDisconnected(ComponentName componentName) {
Log.e("AndroidTestOrchestrator", "AndroidTestOrchestrator has prematurely disconnected from the orchestration service,run cancelled.");
AndroidTestOrchestrator.this.finish(0, AndroidTestOrchestrator.this.createResultBundle());
}
};
private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(AndroidTestOrchestrator$$Lambda$0.$instance);
private String mTest;
private Iterator<String> mTestIterator;
private UsageTrackerFacilitator mUsageTrackerFacilitator;
private final Builder resultBuilder = new Builder();
private final OrchestrationResultPrinter resultPrinter = new OrchestrationResultPrinter();
private final OrchestrationXmlTestRunListener xmlTestRunListener = new OrchestrationXmlTestRunListener();
static final /* synthetic */ Thread lambda$new$0$AndroidTestOrchestrator(Runnable runnable) {
Thread newThread = Executors.defaultThreadFactory().newThread(runnable);
newThread.setName("AndroidTestOrchestrator");
return newThread;
}
public void onCreate(Bundle bundle) {
String string = bundle.getString("debug");
if (string != null && Boolean.parseBoolean(string)) {
Log.i("AndroidTestOrchestrator", "Waiting for debugger to connect...");
Debug.waitForDebugger();
Log.i("AndroidTestOrchestrator", "Debugger connected.");
}
if (bundle.getString("targetInstrumentation") == null) {
throw new IllegalArgumentException("You must provide a target instrumentation.");
}
this.mArguments = bundle;
this.mArguments.putString("orchestratorService", "OrchestratorService");
super.onCreate(bundle);
start();
}
public void onStart() {
super.onStart();
try {
registerUserTracker();
grantRuntimePermissions(RUNTIME_PERMISSIONS);
connectOrchestratorService();
} catch (Throwable e) {
String str = "Fatal exception when setting up.";
Log.e("AndroidTestOrchestrator", "Fatal exception when setting up.", e);
Bundle createResultBundle = createResultBundle();
String str2 = "stream";
String valueOf = String.valueOf("Fatal exception when setting up.\n");
String valueOf2 = String.valueOf(Log.getStackTraceString(e));
createResultBundle.putString(str2, valueOf2.length() != 0 ? valueOf.concat(valueOf2) : new String(valueOf));
finish(-1, createResultBundle);
}
}
private void grantRuntimePermissions(List<String> list) {
if (VERSION.SDK_INT >= 24) {
Context context = getContext();
for (String str : list) {
if (context.checkCallingOrSelfPermission(str) != 0) {
execShellCommandSync(context, getSecret(this.mArguments), "pm", Arrays.asList(new String[]{"grant", context.getPackageName(), str}));
if (context.checkCallingOrSelfPermission(str) != 0) {
throw new IllegalStateException("Permission requested but not granted!");
}
}
}
}
}
private void connectOrchestratorService() {
getContext().bindService(new Intent(getContext(), OrchestratorService.class), this.mConnection, 1);
}
private void collectTests() {
String string = this.mArguments.getString("class");
if (isSingleMethodTest(string)) {
Log.i("AndroidTestOrchestrator", String.format("Single test parameter %s, skipping test collection", new Object[]{string}));
this.callbackLogic.addTest(string);
runFinished();
return;
}
Log.i("AndroidTestOrchestrator", String.format("Multiple test parameter %s, starting test collection", new Object[]{string}));
this.mExecutorService.execute(TestRunnable.testCollectionRunnable(getContext(), getSecret(this.mArguments), this.mArguments, getOutputStream(), this));
}
static boolean isSingleMethodTest(String str) {
if (TextUtils.isEmpty(str)) {
return false;
}
return FULLY_QUALIFIED_CLASS_AND_METHOD.matcher(str).matches();
}
public void runFinished() {
if (this.mTest == null) {
List provideCollectedTests = this.callbackLogic.provideCollectedTests();
this.mTestIterator = provideCollectedTests.iterator();
addListeners(provideCollectedTests.size());
if (provideCollectedTests.isEmpty()) {
finish(0, createResultBundle());
return;
}
}
this.listenerManager.testProcessFinished(getOutputFile());
if (runsInIsolatedMode(this.mArguments)) {
executeNextTest();
} else {
executeEntireTestSuite();
}
}
private void executeEntireTestSuite() {
if (this.mTest != null) {
finish(-1, createResultBundle());
return;
}
this.mTest = "";
this.mExecutorService.execute(TestRunnable.legacyTestRunnable(getContext(), getSecret(this.mArguments), this.mArguments, getOutputStream(), this));
}
private void executeNextTest() {
if (this.mTestIterator.hasNext()) {
this.mTest = (String) this.mTestIterator.next();
this.listenerManager.testProcessStarted(new ParcelableDescription(this.mTest));
String addTestCoverageSupport = addTestCoverageSupport(this.mArguments, this.mTest);
if (addTestCoverageSupport != null) {
this.mArguments.putString("coverageFile", addTestCoverageSupport);
}
clearPackageData();
this.mExecutorService.execute(TestRunnable.singleTestRunnable(getContext(), getSecret(this.mArguments), this.mArguments, getOutputStream(), this, this.mTest));
if (addTestCoverageSupport != null) {
this.mArguments.remove("coverageFile");
return;
}
return;
}
finish(-1, createResultBundle());
}
private void clearPackageData() {
if (shouldClearPackageData(this.mArguments)) {
this.mExecutorService.execute(new Runnable() {
public void run() {
AndroidTestOrchestrator.execShellCommandSync(AndroidTestOrchestrator.this.getContext(), AndroidTestOrchestrator.getSecret(AndroidTestOrchestrator.this.mArguments), "pm", Arrays.asList(new String[]{"clear", AndroidTestOrchestrator.this.getTargetPackage(AndroidTestOrchestrator.this.mArguments)}));
AndroidTestOrchestrator.execShellCommandSync(AndroidTestOrchestrator.this.getContext(), AndroidTestOrchestrator.getSecret(AndroidTestOrchestrator.this.mArguments), "pm", Arrays.asList(new String[]{"clear", AndroidTestOrchestrator.getTargetInstrPackage(AndroidTestOrchestrator.this.mArguments)}));
}
});
}
}
static String addTestCoverageSupport(Bundle bundle, String str) {
if (!shouldRunCoverage(bundle) || !runsInIsolatedMode(bundle)) {
return null;
}
String string = bundle.getString("coverageFilePath");
if (string == null || string.isEmpty()) {
throw new IllegalStateException(String.format("The coverage path [%s] is either null or empty", new Object[]{string}));
} else if (bundle.getString("coverageFile") == null) {
return new StringBuilder((String.valueOf(string).length() + 3) + String.valueOf(str).length()).append(string).append(str).append(".ec").toString();
} else {
throw new IllegalStateException(String.format("Can't use a custom coverage file name [-e %s %s] when running through orchestrator in isolated mode. Since the generated coverage files will overwrite each other. Please consider using [%s] instead.", new Object[]{"coverageFile", bundle.getString("coverageFile"), "coverageFilePath"}));
}
}
private OutputStream getOutputStream() {
try {
Context context = getContext();
if (VERSION.SDK_INT >= 24) {
context = ContextCompat.createDeviceProtectedStorageContext(context);
}
return context.openFileOutput(getOutputFile(), 0);
} catch (FileNotFoundException e) {
throw new RuntimeException("Could not open stream for output");
}
}
private String getOutputFile() {
if (this.mTest == null) {
return "testCollection.txt";
}
return String.format("%s.txt", new Object[]{this.mTest});
}
private void addListeners(int i) {
this.listenerManager.addListener(this.xmlTestRunListener);
this.listenerManager.addListener(this.resultBuilder);
this.listenerManager.addListener(this.resultPrinter);
this.listenerManager.orchestrationRunStarted(i);
}
private Bundle createResultBundle() {
PrintStream printStream = new PrintStream(new ByteArrayOutputStream());
Bundle bundle = new Bundle();
try {
this.resultBuilder.orchestrationRunFinished();
this.resultPrinter.orchestrationRunFinished(printStream, this.resultBuilder.build());
bundle.putString("stream", String.format("\n%s", new Object[]{r0.toString()}));
return bundle;
} finally {
printStream.close();
}
}
/* JADX WARNING: inconsistent code. */
/* Code decompiled incorrectly, please refer to instructions dump. */
public void finish(int i, Bundle bundle) {
this.xmlTestRunListener.orchestrationRunFinished();
try {
this.mUsageTrackerFacilitator.trackUsage("AndroidTestOrchestrator", "1.0.0");
this.mUsageTrackerFacilitator.sendUsages();
try {
super.finish(i, bundle);
} catch (Throwable e) {
Log.e("AndroidTestOrchestrator", "Security exception thrown on shutdown", e);
super.finish(i, createResultBundle());
}
} catch (Throwable e2) {
Log.w("AndroidTestOrchestrator", "Failed to send analytics.", e2);
} catch (Throwable th) {
try {
super.finish(i, bundle);
} catch (Throwable e3) {
Log.e("AndroidTestOrchestrator", "Security exception thrown on shutdown", e3);
super.finish(i, createResultBundle());
}
}
}
public boolean onException(Object obj, Throwable th) {
this.resultPrinter.reportProcessCrash(th);
return super.onException(obj, th);
}
private static boolean runsInIsolatedMode(Bundle bundle) {
return !Boolean.FALSE.toString().equalsIgnoreCase(bundle.getString("isolated"));
}
private static boolean shouldTrackUsage(Bundle bundle) {
return !Boolean.parseBoolean(bundle.getString("disableAnalytics"));
}
private static boolean shouldRunCoverage(Bundle bundle) {
return Boolean.parseBoolean(bundle.getString("coverage"));
}
private static boolean shouldClearPackageData(Bundle bundle) {
return Boolean.parseBoolean(bundle.getString("clearPackageData"));
}
private static String getSecret(Bundle bundle) {
String string = bundle.getString("shellExecKey");
if (string != null) {
return string;
}
throw new IllegalArgumentException("Cannot find secret for ShellExecutor binder published at shellExecKey");
}
private static String getTargetInstrumetnation(Bundle bundle) {
String string = bundle.getString("targetInstrumentation");
if (string != null) {
return string;
}
throw new IllegalArgumentException("You must provide a target instrumentation using the following runner arg: targetInstrumentation");
}
private void registerUserTracker() {
this.mUsageTrackerFacilitator = new UsageTrackerFacilitator(shouldTrackUsage(this.mArguments));
Context targetContext = getTargetContext();
if (targetContext != null) {
this.mUsageTrackerFacilitator.registerUsageTracker(new AnalyticsBasedUsageTracker.Builder(targetContext).withTargetPackage(getTargetInstrPackage(this.mArguments)).buildIfPossible());
}
}
private static String execShellCommandSync(Context context, String str, String str2, List<String> list) {
try {
String execOnServerSync = ShellCommandClient.execOnServerSync(context, str, str2, list, new HashMap(), false);
if (null == null) {
return execOnServerSync;
}
Log.w("AndroidTestOrchestrator", String.format("Failed executing shell command [%s] with params [%s]", new Object[]{str2, list}), null);
return execOnServerSync;
} catch (Throwable e) {
if (e != null) {
Log.w("AndroidTestOrchestrator", String.format("Failed executing shell command [%s] with params [%s]", new Object[]{str2, list}), e);
return null;
}
return null;
} catch (Throwable e2) {
if (e2 != null) {
Log.w("AndroidTestOrchestrator", String.format("Failed executing shell command [%s] with params [%s]", new Object[]{str2, list}), e2);
return null;
}
return null;
} catch (Throwable e22) {
if (e22 != null) {
Log.w("AndroidTestOrchestrator", String.format("Failed executing shell command [%s] with params [%s]", new Object[]{str2, list}), e22);
return null;
}
return null;
} catch (Throwable th) {
if (null != null) {
Log.w("AndroidTestOrchestrator", String.format("Failed executing shell command [%s] with params [%s]", new Object[]{str2, list}), null);
}
}
}
private static String getTargetInstrPackage(Bundle bundle) {
return getTargetInstrumetnation(bundle).split("/", -1)[0];
}
private String getTargetPackage(Bundle bundle) {
String targetInstrPackage = getTargetInstrPackage(bundle);
String str = getTargetInstrumetnation(bundle).split("/", -1)[1];
try {
return getContext().getPackageManager().getInstrumentationInfo(new ComponentName(targetInstrPackage, str), 0).targetPackage;
} catch (NameNotFoundException e) {
throw new IllegalStateException(new StringBuilder(String.valueOf(targetInstrPackage).length() + 41).append("Package [").append(targetInstrPackage).append("] cannot be found on the system.").toString());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment