1/* 2 * Copyright (C) 2017 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 17package com.android.rs.unittest; 18 19import android.content.Context; 20import android.renderscript.RenderScript; 21import android.renderscript.RenderScript.RSMessageHandler; 22import android.support.test.InstrumentationRegistry; 23import android.util.Log; 24 25import dalvik.system.DexFile; 26 27import java.io.IOException; 28import java.util.ArrayList; 29import java.util.Enumeration; 30import java.util.HashSet; 31import java.util.List; 32import java.util.Set; 33import java.util.concurrent.CountDownLatch; 34import java.util.concurrent.TimeUnit; 35 36public abstract class UnitTest { 37 public enum UnitTestResult { 38 UT_NOT_STARTED, 39 UT_RUNNING, 40 UT_SUCCESS, 41 UT_FAIL; 42 43 @Override 44 public String toString() { 45 switch (this) { 46 case UT_NOT_STARTED: 47 return "NOT STARTED"; 48 case UT_RUNNING: 49 return "RUNNING"; 50 case UT_SUCCESS: 51 return "PASS"; 52 case UT_FAIL: 53 return "FAIL"; 54 default: 55 throw new RuntimeException( 56 "missing enum case in UnitTestResult#toString()"); 57 } 58 } 59 } 60 61 private final static String TAG = "RSUnitTest"; 62 63 private String mName; 64 private UnitTestResult mResult; 65 private Context mCtx; 66 /* Necessary to avoid race condition on pass/fail message. */ 67 private CountDownLatch mCountDownLatch; 68 69 /* These constants must match those in shared.rsh */ 70 public static final int RS_MSG_TEST_PASSED = 100; 71 public static final int RS_MSG_TEST_FAILED = 101; 72 73 public UnitTest(String n, Context ctx) { 74 mName = n; 75 mCtx = ctx; 76 mResult = UnitTestResult.UT_NOT_STARTED; 77 mCountDownLatch = null; 78 } 79 80 protected void _RS_ASSERT(String message, boolean b) { 81 if (!b) { 82 Log.e(TAG, message + " FAILED"); 83 failTest(); 84 } 85 } 86 87 /** 88 * Returns a RenderScript instance created from mCtx. 89 * 90 * @param enableMessages 91 * true if expecting exactly one pass/fail message from the RenderScript instance. 92 * false if no messages expected. 93 * Any other messages are not supported. 94 */ 95 protected RenderScript createRenderScript(boolean enableMessages) { 96 RenderScript rs = RenderScript.create(mCtx); 97 if (enableMessages) { 98 RSMessageHandler handler = new RSMessageHandler() { 99 public void run() { 100 switch (mID) { 101 case RS_MSG_TEST_PASSED: 102 passTest(); 103 break; 104 case RS_MSG_TEST_FAILED: 105 failTest(); 106 break; 107 default: 108 Log.w(TAG, String.format("Unit test %s got unexpected message %d", 109 UnitTest.this.toString(), mID)); 110 break; 111 } 112 mCountDownLatch.countDown(); 113 } 114 }; 115 rs.setMessageHandler(handler); 116 mCountDownLatch = new CountDownLatch(1); 117 } 118 return rs; 119 } 120 121 protected synchronized void failTest() { 122 mResult = UnitTestResult.UT_FAIL; 123 } 124 125 protected synchronized void passTest() { 126 if (mResult != UnitTestResult.UT_FAIL) { 127 mResult = UnitTestResult.UT_SUCCESS; 128 } 129 } 130 131 public void logStart(String tag, String testSuite) { 132 String thisDeviceName = android.os.Build.DEVICE; 133 int thisApiVersion = android.os.Build.VERSION.SDK_INT; 134 Log.i(tag, String.format("%s: starting '%s' " 135 + "on device %s, API version %d", 136 testSuite, toString(), thisDeviceName, thisApiVersion)); 137 } 138 139 public void logEnd(String tag) { 140 Log.i(tag, String.format("RenderScript test '%s': %s", 141 toString(), getResultString())); 142 } 143 144 public UnitTestResult getResult() { 145 return mResult; 146 } 147 148 public String getResultString() { 149 return mResult.toString(); 150 } 151 152 public boolean getSuccess() { 153 return mResult == UnitTestResult.UT_SUCCESS; 154 } 155 156 public void runTest() { 157 mResult = UnitTestResult.UT_RUNNING; 158 run(); 159 if (mCountDownLatch != null) { 160 try { 161 boolean success = mCountDownLatch.await(5 * 60, TimeUnit.SECONDS); 162 if (!success) { 163 failTest(); 164 Log.e(TAG, String.format("Unit test %s waited too long for pass/fail message", 165 toString())); 166 } 167 } catch (InterruptedException e) { 168 failTest(); 169 Log.e(TAG, String.format("Unit test %s raised InterruptedException when " + 170 "listening for pass/fail message", toString())); 171 } 172 } 173 switch (mResult) { 174 case UT_NOT_STARTED: 175 case UT_RUNNING: 176 Log.w(TAG, String.format("unexpected unit test result for test %s: %s", 177 this.toString(), mResult.toString())); 178 break; 179 } 180 } 181 182 abstract protected void run(); 183 184 @Override 185 public String toString() { 186 return mName; 187 } 188 189 190 /** 191 * Throws RuntimeException if any tests have the same name. 192 */ 193 public static void checkDuplicateNames(Iterable<UnitTest> tests) { 194 Set<String> names = new HashSet<>(); 195 List<String> duplicates = new ArrayList<>(); 196 for (UnitTest test : tests) { 197 String name = test.toString(); 198 if (names.contains(name)) { 199 duplicates.add(name); 200 } 201 names.add(name); 202 } 203 if (!duplicates.isEmpty()) { 204 throw new RuntimeException("duplicate name(s): " + duplicates); 205 } 206 } 207 208 public static Iterable<Class<? extends UnitTest>> getProperSubclasses(Context ctx) 209 throws ClassNotFoundException, IOException { 210 return getProperSubclasses(UnitTest.class, ctx); 211 } 212 213 /** Returns a list of all proper subclasses of the input class */ 214 private static <T> Iterable<Class<? extends T>> getProperSubclasses(Class<T> klass, Context ctx) 215 throws ClassNotFoundException, IOException { 216 ArrayList<Class<? extends T>> ret = new ArrayList<>(); 217 DexFile df = new DexFile(ctx.getPackageCodePath()); 218 Enumeration<String> iter = df.entries(); 219 while (iter.hasMoreElements()) { 220 String s = iter.nextElement(); 221 Class<?> cur = Class.forName(s); 222 while (cur != null) { 223 if (cur.getSuperclass() == klass) { 224 break; 225 } 226 cur = cur.getSuperclass(); 227 } 228 if (cur != null) { 229 ret.add((Class<? extends T>) cur); 230 } 231 } 232 return ret; 233 } 234} 235 236