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