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 void cleanOnTestFinish() { 69 cleanUpOnTestFinish(); 70 } 71 72 public List<String> retrieveAnnotatedMethods(String annotationName) { 73 List<String> annotatedMethods = new ArrayList<>(); 74 75 try { 76 Class<? extends Annotation> annotation = 77 Class.forName(annotationName).asSubclass(Annotation.class); 78 Method[] testInstanceMethods = 79 mTestInstance.getClass().getDeclaredMethods(); 80 81 for (final Method method : testInstanceMethods) { 82 if (method.isAnnotationPresent(annotation)) { 83 annotatedMethods.add(method.getName()); 84 } 85 } 86 } catch (Exception e) { 87 Log.e(TAG, "Failed to find methods with annotations"); 88 } 89 90 return annotatedMethods; 91 } 92 93 public void invokeMethod( 94 String methodName, 95 Bundle methodParams, 96 ISdkSandboxResultCallback resultCallback) { 97 try { 98 // We allow test authors to write a test without the bundle parameters 99 // for convenience. 100 // We will first look for the test name with a bundle parameter 101 // if we don't find that, we will load the test without a parameter. 102 boolean hasParams = true; 103 Method methodFound = 104 findMethodOnTestInstance( 105 methodName, /*throwException*/ false, Bundle.class); 106 if (methodFound == null) { 107 hasParams = false; 108 methodFound = 109 findMethodOnTestInstance( 110 methodName, /*throwException*/ true); 111 } 112 113 invokeMethodOnTestInstance( 114 methodFound, hasParams, methodParams, resultCallback); 115 } catch (NoSuchMethodException error) { 116 try { 117 resultCallback.onError(getStackTrace(error)); 118 } catch (RemoteException e) { 119 Log.e(TAG, "Failed to find method and report back"); 120 } 121 } 122 } 123 }; 124 125 return new SandboxedSdk(testExecutor); 126 } 127 128 @Nullable getCustomInterface()129 protected IBinder getCustomInterface() { 130 return mBinder; 131 } 132 findMethodOnTestInstance( String methodName, boolean throwException, Class<?>... parameterTypes)133 private Method findMethodOnTestInstance( 134 String methodName, boolean throwException, Class<?>... parameterTypes) 135 throws NoSuchMethodException { 136 try { 137 return mTestInstance.getClass().getMethod(methodName, parameterTypes); 138 } catch (NoSuchMethodException error) { 139 if (throwException) { 140 throw error; 141 } 142 return null; 143 } 144 } 145 invokeMethodOnTestInstance( final Method method, final boolean hasParams, final Bundle params, final ISdkSandboxResultCallback resultCallback)146 private void invokeMethodOnTestInstance( 147 final Method method, 148 final boolean hasParams, 149 final Bundle params, 150 final ISdkSandboxResultCallback resultCallback) { 151 try { 152 if (hasParams) { 153 method.invoke(mTestInstance, params); 154 } else { 155 method.invoke(mTestInstance); 156 } 157 resultCallback.onResult(); 158 } catch (Exception error) { 159 String errorStackTrace = getStackTrace(error); 160 161 try { 162 resultCallback.onError(errorStackTrace); 163 } catch (Exception ex) { 164 if (error.getCause() instanceof AssertionError) { 165 Log.e(TAG, "Assertion failed on invoked method " + errorStackTrace); 166 } else if (error.getCause() instanceof InvocationTargetException) { 167 Log.e(TAG, "Invocation target failed " + errorStackTrace); 168 } else if (error.getCause() instanceof NoSuchMethodException) { 169 Log.e(TAG, "Test method not found " + errorStackTrace); 170 } else { 171 Log.e(TAG, "Test execution failed " + errorStackTrace); 172 } 173 } 174 } 175 } 176 getStackTrace(Exception error)177 private String getStackTrace(Exception error) { 178 StringWriter errorStackTrace = new StringWriter(); 179 PrintWriter errorWriter = new PrintWriter(errorStackTrace); 180 error.getCause().printStackTrace(errorWriter); 181 return errorStackTrace.toString(); 182 } 183 cleanUpOnTestFinish()184 public abstract void cleanUpOnTestFinish(); 185 186 } 187