1 /*
2  * Copyright (C) 2015 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 package com.android.compatibility.common.tradefed.testtype;
17 
18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.config.Option.Importance;
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.result.ITestInvocationListener;
25 import com.android.tradefed.result.ResultForwarder;
26 import com.android.tradefed.testtype.HostTest;
27 import com.android.tradefed.testtype.IRemoteTest;
28 import com.android.tradefed.util.StreamUtil;
29 
30 import com.google.common.annotations.VisibleForTesting;
31 
32 import junit.framework.Test;
33 
34 import java.io.File;
35 import java.io.IOException;
36 import java.lang.reflect.Modifier;
37 import java.net.URL;
38 import java.net.URLClassLoader;
39 import java.util.Collections;
40 import java.util.Enumeration;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.jar.JarEntry;
46 import java.util.jar.JarFile;
47 
48 /**
49  * Test runner for host-side JUnit tests.
50  */
51 public class JarHostTest extends HostTest {
52 
53     @Option(name="jar", description="The jars containing the JUnit test class to run.",
54             importance = Importance.IF_UNSET)
55     private Set<String> mJars = new HashSet<>();
56 
57     /**
58      * {@inheritDoc}
59      */
60     @Override
createHostTest(Class<?> classObj)61     protected HostTest createHostTest(Class<?> classObj) {
62         JarHostTest test = (JarHostTest) super.createHostTest(classObj);
63         // clean the jar option since we are loading directly from classes after.
64         test.mJars = new HashSet<>();
65         return test;
66     }
67 
68     /**
69      * Create a {@link CompatibilityBuildHelper} from the build info provided.
70      */
71     @VisibleForTesting
createBuildHelper(IBuildInfo info)72     CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
73         return new CompatibilityBuildHelper(info);
74     }
75 
76     /**
77      * {@inheritDoc}
78      */
79     @Override
getClasses()80     protected List<Class<?>> getClasses() throws IllegalArgumentException  {
81         List<Class<?>> classes = super.getClasses();
82         CompatibilityBuildHelper helper = createBuildHelper(getBuild());
83         for (String jarName : mJars) {
84             JarFile jarFile = null;
85             try {
86                 File file = new File(helper.getTestsDir(), jarName);
87                 jarFile = new JarFile(file);
88                 Enumeration<JarEntry> e = jarFile.entries();
89                 URL[] urls = {
90                         new URL(String.format("jar:file:%s!/", file.getAbsolutePath()))
91                 };
92                 URLClassLoader cl = URLClassLoader.newInstance(urls);
93 
94                 while (e.hasMoreElements()) {
95                     JarEntry je = e.nextElement();
96                     if (je.isDirectory() || !je.getName().endsWith(".class")
97                             || je.getName().contains("$")) {
98                         continue;
99                     }
100                     String className = getClassName(je.getName());
101                     try {
102                         Class<?> cls = cl.loadClass(className);
103                         int modifiers = cls.getModifiers();
104                         if ((IRemoteTest.class.isAssignableFrom(cls)
105                                 || Test.class.isAssignableFrom(cls)
106                                 || hasJUnit4Annotation(cls))
107                                 && !Modifier.isStatic(modifiers)
108                                 && !Modifier.isPrivate(modifiers)
109                                 && !Modifier.isProtected(modifiers)
110                                 && !Modifier.isInterface(modifiers)
111                                 && !Modifier.isAbstract(modifiers)) {
112                             classes.add(cls);
113                         }
114                     } catch (ClassNotFoundException cnfe) {
115                         throw new IllegalArgumentException(
116                                 String.format("Cannot find test class %s", className));
117                     }
118                 }
119             } catch (IOException e) {
120                 CLog.e(e);
121                 throw new RuntimeException(e);
122             } finally {
123                 StreamUtil.close(jarFile);
124             }
125         }
126         return classes;
127     }
128 
getClassName(String name)129     private static String getClassName(String name) {
130         // -6 because of .class
131         return name.substring(0, name.length() - 6).replace('/', '.');
132     }
133 
134     /**
135      * {@inheritDoc}
136      */
137     @Override
run(ITestInvocationListener listener)138     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
139         int numTests = countTestCases();
140         long startTime = System.currentTimeMillis();
141         listener.testRunStarted(getClass().getName(), numTests);
142         super.run(new HostTestListener(listener));
143         listener.testRunEnded(System.currentTimeMillis() - startTime, Collections.emptyMap());
144     }
145 
146     /**
147      * Wrapper listener that forwards all events except testRunStarted() and testRunEnded() to
148      * the embedded listener. Each test class in the jar will invoke these events, which
149      * HostTestListener withholds from listeners for console logging and result reporting.
150      */
151     public class HostTestListener extends ResultForwarder {
152 
HostTestListener(ITestInvocationListener listener)153         public HostTestListener(ITestInvocationListener listener) {
154             super(listener);
155         }
156 
157         /**
158          * {@inheritDoc}
159          */
160         @Override
testRunStarted(String name, int numTests)161         public void testRunStarted(String name, int numTests) {
162             CLog.d("HostTestListener.testRunStarted(%s, %d)", name, numTests);
163         }
164 
165         /**
166          * {@inheritDoc}
167          */
168         @Override
testRunEnded(long elapsedTime, Map<String, String> metrics)169         public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
170             CLog.d("HostTestListener.testRunEnded(%d, %s)", elapsedTime, metrics.toString());
171         }
172     }
173 }
174