1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app.sdksandbox.testutils.testscenario; 18 19 import android.app.sdksandbox.SandboxedSdk; 20 import android.app.sdksandbox.SandboxedSdkProvider; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.os.IBinder; 24 import android.os.RemoteException; 25 import android.util.Log; 26 import android.view.View; 27 28 import androidx.annotation.Nullable; 29 30 import java.io.PrintWriter; 31 import java.io.StringWriter; 32 import java.lang.annotation.Annotation; 33 import java.lang.reflect.InvocationTargetException; 34 import java.lang.reflect.Method; 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * This class contains all the binding logic needed for a test suite within an SDK. To set up SDK 40 * tests, extend this class. To attach a custom view to your tests, override beforeEachTest() to 41 * return your view (for example a WebView). 42 */ 43 public abstract class SdkSandboxTestScenarioRunner extends SandboxedSdkProvider { 44 private static final String TAG = SdkSandboxTestScenarioRunner.class.getName(); 45 46 private Object mTestInstance = this; 47 private @Nullable IBinder mBinder; 48 49 /** 50 * This method can be used to provide a separate test class to execute tests against. In child 51 * classes, it should be called in the onLoadSdk method before calling super. 52 */ setTestInstance(Object testInstance)53 public void setTestInstance(Object testInstance) { 54 mTestInstance = testInstance; 55 } 56 57 @Override getView(Context windowContext, Bundle params, int width, int height)58 public View getView(Context windowContext, Bundle params, int width, int height) { 59 return new View(windowContext); 60 } 61 62 @Override onLoadSdk(Bundle params)63 public SandboxedSdk onLoadSdk(Bundle params) { 64 mBinder = params.getBinder(ISdkSandboxTestExecutor.TEST_AUTHOR_DEFINED_BINDER); 65 66 ISdkSandboxTestExecutor.Stub testExecutor = 67 new ISdkSandboxTestExecutor.Stub() { 68 public List<String> retrieveAnnotatedMethods(String annotationName) { 69 List<String> annotatedMethods = new ArrayList<>(); 70 71 try { 72 Class<? extends Annotation> annotation = 73 Class.forName(annotationName).asSubclass(Annotation.class); 74 Method[] testInstanceMethods = 75 mTestInstance.getClass().getDeclaredMethods(); 76 77 for (final Method method : testInstanceMethods) { 78 if (method.isAnnotationPresent(annotation)) { 79 annotatedMethods.add(method.getName()); 80 } 81 } 82 } catch (Exception e) { 83 Log.e(TAG, "Failed to find methods with annotations"); 84 } 85 86 return annotatedMethods; 87 } 88 89 public void invokeMethod( 90 String methodName, 91 Bundle methodParams, 92 ISdkSandboxResultCallback resultCallback) { 93 try { 94 // We allow test authors to write a test without the bundle parameters 95 // for convenience. 96 // We will first look for the test name with a bundle parameter 97 // if we don't find that, we will load the test without a parameter. 98 boolean hasParams = true; 99 Method methodFound = 100 findMethodOnTestInstance( 101 methodName, /*throwException*/ false, Bundle.class); 102 if (methodFound == null) { 103 hasParams = false; 104 methodFound = 105 findMethodOnTestInstance( 106 methodName, /*throwException*/ true); 107 } 108 109 invokeMethodOnTestInstance( 110 methodFound, hasParams, methodParams, resultCallback); 111 } catch (NoSuchMethodException error) { 112 try { 113 resultCallback.onError(getStackTrace(error)); 114 } catch (RemoteException e) { 115 Log.e(TAG, "Failed to find method and report back"); 116 } 117 } 118 } 119 }; 120 121 return new SandboxedSdk(testExecutor); 122 } 123 124 @Nullable getCustomInterface()125 protected IBinder getCustomInterface() { 126 return mBinder; 127 } 128 findMethodOnTestInstance( String methodName, boolean throwException, Class<?>... parameterTypes)129 private Method findMethodOnTestInstance( 130 String methodName, boolean throwException, Class<?>... parameterTypes) 131 throws NoSuchMethodException { 132 try { 133 return mTestInstance.getClass().getMethod(methodName, parameterTypes); 134 } catch (NoSuchMethodException error) { 135 if (throwException) { 136 throw error; 137 } 138 return null; 139 } 140 } 141 invokeMethodOnTestInstance( final Method method, final boolean hasParams, final Bundle params, final ISdkSandboxResultCallback resultCallback)142 private void invokeMethodOnTestInstance( 143 final Method method, 144 final boolean hasParams, 145 final Bundle params, 146 final ISdkSandboxResultCallback resultCallback) { 147 try { 148 if (hasParams) { 149 method.invoke(mTestInstance, params); 150 } else { 151 method.invoke(mTestInstance); 152 } 153 resultCallback.onResult(); 154 } catch (Exception error) { 155 String errorStackTrace = getStackTrace(error); 156 157 try { 158 resultCallback.onError(errorStackTrace); 159 } catch (Exception ex) { 160 if (error.getCause() instanceof AssertionError) { 161 Log.e(TAG, "Assertion failed on invoked method " + errorStackTrace); 162 } else if (error.getCause() instanceof InvocationTargetException) { 163 Log.e(TAG, "Invocation target failed " + errorStackTrace); 164 } else if (error.getCause() instanceof NoSuchMethodException) { 165 Log.e(TAG, "Test method not found " + errorStackTrace); 166 } else { 167 Log.e(TAG, "Test execution failed " + errorStackTrace); 168 } 169 } 170 } 171 } 172 getStackTrace(Exception error)173 private String getStackTrace(Exception error) { 174 StringWriter errorStackTrace = new StringWriter(); 175 PrintWriter errorWriter = new PrintWriter(errorStackTrace); 176 error.getCause().printStackTrace(errorWriter); 177 return errorStackTrace.toString(); 178 } 179 } 180