Created
April 23, 2018 12:38
-
-
Save tomkoptel/ee2e8da4ceeda954051e4b076f9ce718 to your computer and use it in GitHub Desktop.
Extracted AndroidTestOrchestrator class from orchestrator-1.0.2-beta1.apk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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