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