1 /*
2  * Copyright (C) 2014 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 com.android.cts.runner;
18 
19 import android.app.Instrumentation;
20 import android.app.KeyguardManager;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.support.test.internal.runner.listener.InstrumentationRunListener;
24 import android.text.TextUtils;
25 import android.util.Log;
26 
27 import junit.framework.TestCase;
28 
29 import org.junit.runner.Description;
30 import org.junit.runner.notification.RunListener;
31 
32 import java.io.BufferedReader;
33 import java.io.File;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.lang.Class;
37 import java.lang.ReflectiveOperationException;
38 import java.lang.reflect.Field;
39 import java.lang.reflect.Modifier;
40 import java.net.Authenticator;
41 import java.net.CookieHandler;
42 import java.net.ResponseCache;
43 import java.text.DateFormat;
44 import java.util.Locale;
45 import java.util.Properties;
46 import java.util.TimeZone;
47 
48 import javax.net.ssl.HostnameVerifier;
49 import javax.net.ssl.HttpsURLConnection;
50 import javax.net.ssl.SSLSocketFactory;
51 
52 /**
53  * A {@link RunListener} for CTS. Sets the system properties necessary for many
54  * core tests to run. This is needed because there are some core tests that need
55  * writing access to the file system.
56  * Finally, we add a means to free memory allocated by a TestCase after its
57  * execution.
58  */
59 public class CtsTestRunListener extends InstrumentationRunListener {
60 
61     private static final String TAG = "CtsTestRunListener";
62 
63     private TestEnvironment mEnvironment;
64     private Class<?> lastClass;
65 
66     @Override
testRunStarted(Description description)67     public void testRunStarted(Description description) throws Exception {
68         mEnvironment = new TestEnvironment(getInstrumentation().getTargetContext());
69 
70         // We might want to move this to /sdcard, if is is mounted/writable.
71         File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
72         System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
73 
74         // attempt to disable keyguard, if current test has permission to do so
75         // TODO: move this to a better place, such as InstrumentationTestRunner
76         // ?
77         if (getInstrumentation().getContext().checkCallingOrSelfPermission(
78                 android.Manifest.permission.DISABLE_KEYGUARD)
79                 == PackageManager.PERMISSION_GRANTED) {
80             Log.i(TAG, "Disabling keyguard");
81             KeyguardManager keyguardManager =
82                     (KeyguardManager) getInstrumentation().getContext().getSystemService(
83                             Context.KEYGUARD_SERVICE);
84             keyguardManager.newKeyguardLock("cts").disableKeyguard();
85         } else {
86             Log.i(TAG, "Test lacks permission to disable keyguard. " +
87                     "UI based tests may fail if keyguard is up");
88         }
89     }
90 
91     @Override
testStarted(Description description)92     public void testStarted(Description description) throws Exception {
93         if (description.getTestClass() != lastClass) {
94             lastClass = description.getTestClass();
95             printMemory(description.getTestClass());
96         }
97 
98         mEnvironment.reset();
99     }
100 
101     @Override
testFinished(Description description)102     public void testFinished(Description description) {
103         // no way to implement this in JUnit4...
104         // offending test cases that need this logic should probably be cleaned
105         // up individually
106         // if (test instanceof TestCase) {
107         // cleanup((TestCase) test);
108         // }
109     }
110 
111     /**
112      * Dumps some memory info.
113      */
printMemory(Class<?> testClass)114     private void printMemory(Class<?> testClass) {
115         Runtime runtime = Runtime.getRuntime();
116 
117         long total = runtime.totalMemory();
118         long free = runtime.freeMemory();
119         long used = total - free;
120 
121         Log.d(TAG, "Total memory  : " + total);
122         Log.d(TAG, "Used memory   : " + used);
123         Log.d(TAG, "Free memory   : " + free);
124 
125         String tempdir = System.getProperty("java.io.tmpdir", "");
126         // TODO: Remove these extra Logs added to debug a specific timeout problem.
127         Log.d(TAG, "java.io.tmpdir is:" + tempdir);
128 
129         if (!TextUtils.isEmpty(tempdir)) {
130             String[] commands = {"df", tempdir};
131             BufferedReader in = null;
132             try {
133                 Log.d(TAG, "About to .exec df");
134                 Process proc = runtime.exec(commands);
135                 Log.d(TAG, ".exec returned");
136                 in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
137                 Log.d(TAG, "Stream reader created");
138                 String line;
139                 while ((line = in.readLine()) != null) {
140                     Log.d(TAG, line);
141                 }
142             } catch (IOException e) {
143                 Log.d(TAG, "Exception: " + e.toString());
144                 // Well, we tried
145             } finally {
146                 Log.d(TAG, "In finally");
147                 if (in != null) {
148                     try {
149                         in.close();
150                     } catch (IOException e) {
151                         // Meh
152                     }
153                 }
154             }
155         }
156 
157         Log.d(TAG, "Now executing : " + testClass.getName());
158     }
159 
160     /**
161      * Nulls all non-static reference fields in the given test class. This
162      * method helps us with those test classes that don't have an explicit
163      * tearDown() method. Normally the garbage collector should take care of
164      * everything, but since JUnit keeps references to all test cases, a little
165      * help might be a good idea.
166      */
cleanup(TestCase test)167     private void cleanup(TestCase test) {
168         Class<?> clazz = test.getClass();
169 
170         while (clazz != TestCase.class) {
171             Field[] fields = clazz.getDeclaredFields();
172             for (int i = 0; i < fields.length; i++) {
173                 Field f = fields[i];
174                 if (!f.getType().isPrimitive() &&
175                         !Modifier.isStatic(f.getModifiers())) {
176                     try {
177                         f.setAccessible(true);
178                         f.set(test, null);
179                     } catch (Exception ignored) {
180                         // Nothing we can do about it.
181                     }
182                 }
183             }
184 
185             clazz = clazz.getSuperclass();
186         }
187     }
188 
189     // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
190     static class TestEnvironment {
191         private static final Field sDateFormatIs24HourField;
192         static {
193             try {
194                 Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
195                 sDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
196             } catch (ReflectiveOperationException e) {
197                 throw new AssertionError("Missing DateFormat.is24Hour", e);
198             }
199         }
200 
201         private final Locale mDefaultLocale;
202         private final TimeZone mDefaultTimeZone;
203         private final HostnameVerifier mHostnameVerifier;
204         private final SSLSocketFactory mSslSocketFactory;
205         private final Properties mProperties = new Properties();
206         private final Boolean mDefaultIs24Hour;
207 
TestEnvironment(Context context)208         TestEnvironment(Context context) {
209             mDefaultLocale = Locale.getDefault();
210             mDefaultTimeZone = TimeZone.getDefault();
211             mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
212             mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
213 
214             mProperties.setProperty("user.home", "");
215             mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
216             // The CDD mandates that devices that support WiFi are the only ones that will have
217             // multicast.
218             PackageManager pm = context.getPackageManager();
219             mProperties.setProperty("android.cts.device.multicast",
220                     Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
221             mDefaultIs24Hour = getDateFormatIs24Hour();
222         }
223 
reset()224         void reset() {
225             System.setProperties(null);
226             System.setProperties(mProperties);
227             Locale.setDefault(mDefaultLocale);
228             TimeZone.setDefault(mDefaultTimeZone);
229             Authenticator.setDefault(null);
230             CookieHandler.setDefault(null);
231             ResponseCache.setDefault(null);
232             HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
233             HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
234             setDateFormatIs24Hour(mDefaultIs24Hour);
235         }
236 
getDateFormatIs24Hour()237         private static Boolean getDateFormatIs24Hour() {
238             try {
239                 return (Boolean) sDateFormatIs24HourField.get(null);
240             } catch (ReflectiveOperationException e) {
241                 throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
242             }
243         }
244 
setDateFormatIs24Hour(Boolean value)245         private static void setDateFormatIs24Hour(Boolean value) {
246             try {
247                 sDateFormatIs24HourField.set(null, value);
248             } catch (ReflectiveOperationException e) {
249                 throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
250             }
251         }
252     }
253 
254 }
255