• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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