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