1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php 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.ide.eclipse.adt.internal.launch; 18 19 import com.android.SdkConstants; 20 import com.android.ide.eclipse.adt.AdtConstants; 21 import com.android.ide.eclipse.adt.AdtPlugin; 22 23 import org.eclipse.core.runtime.CoreException; 24 import org.eclipse.core.runtime.FileLocator; 25 import org.eclipse.core.runtime.Platform; 26 import org.eclipse.debug.core.ILaunchConfiguration; 27 import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate; 28 import org.osgi.framework.Bundle; 29 30 import java.io.IOException; 31 import java.net.URL; 32 33 /** 34 * <p> 35 * For Android projects, android.jar gets added to the launch configuration of 36 * JUnit tests as a bootstrap entry. This breaks JUnit tests as android.jar 37 * contains a skeleton version of JUnit classes and the JVM will stop with an error similar 38 * to: <blockquote> Error occurred during initialization of VM 39 * java/lang/NoClassDefFoundError: java/lang/ref/FinalReference </blockquote> 40 * <p> 41 * At compile time, Eclipse does not know that there is no valid junit.jar in 42 * the classpath since it can find a correct reference to all the necessary 43 * org.junit.* classes in the android.jar so it does not prompt the user to add 44 * the JUnit3 or JUnit4 jar. 45 * <p> 46 * This delegates removes the android.jar from the bootstrap path and if 47 * necessary also puts back the junit.jar in the user classpath. 48 * <p> 49 * This delegate will be present for both Java and Android projects (delegates 50 * setting instead of only the current project) but the behavior for Java 51 * projects should be neutral since: 52 * <ol> 53 * <li>Java tests can only compile (and then run) when a valid junit.jar is 54 * present 55 * <li>There is no android.jar in Java projects 56 * </ol> 57 */ 58 public class JUnitLaunchConfigDelegate extends JUnitLaunchConfigurationDelegate { 59 60 private static final String JUNIT_JAR = "junit.jar"; //$NON-NLS-1$ 61 62 @Override getBootpathExt(ILaunchConfiguration configuration)63 public String[][] getBootpathExt(ILaunchConfiguration configuration) throws CoreException { 64 String[][] bootpath = super.getBootpathExt(configuration); 65 return fixBootpathExt(bootpath); 66 } 67 68 @Override getClasspath(ILaunchConfiguration configuration)69 public String[] getClasspath(ILaunchConfiguration configuration) throws CoreException { 70 String[] classpath = super.getClasspath(configuration); 71 return fixClasspath(classpath, getJavaProjectName(configuration)); 72 } 73 74 /** 75 * Removes the android.jar from the bootstrap path if present. 76 * 77 * @param bootpath Array of Arrays of bootstrap class paths 78 * @return a new modified (if applicable) bootpath 79 */ fixBootpathExt(String[][] bootpath)80 public static String[][] fixBootpathExt(String[][] bootpath) { 81 for (int i = 0; i < bootpath.length; i++) { 82 if (bootpath[i] != null && bootpath[i].length > 0) { 83 // we assume that the android.jar can only be present in the 84 // bootstrap path of android tests 85 if (bootpath[i][0].endsWith(SdkConstants.FN_FRAMEWORK_LIBRARY)) { 86 bootpath[i] = null; 87 } 88 } 89 } 90 return bootpath; 91 } 92 93 /** 94 * Add the junit.jar to the user classpath; since Eclipse was relying on 95 * android.jar to provide the appropriate org.junit classes, it does not 96 * know it actually needs the junit.jar. 97 * 98 * @param classpath Array containing classpath 99 * @param projectName The name of the project (for logging purposes) 100 * 101 * @return a new modified (if applicable) classpath 102 */ fixClasspath(String[] classpath, String projectName)103 public static String[] fixClasspath(String[] classpath, String projectName) { 104 // search for junit.jar; if any are found return immediately 105 for (int i = 0; i < classpath.length; i++) { 106 if (classpath[i].endsWith(JUNIT_JAR)) { 107 return classpath; 108 } 109 } 110 111 // This delegate being called without a junit.jar present is only 112 // possible for Android projects. In a non-Android project, the test 113 // would not compile and would be unable to run. 114 try { 115 // junit4 is backward compatible with junit3 and they uses the 116 // same junit.jar from bundle org.junit: 117 // When a project has mixed JUnit3 and JUnit4 tests, if JUnit3 jar 118 // is added first it is then replaced by the JUnit4 jar when user is 119 // prompted to fix the JUnit4 test failure 120 String jarLocation = getJunitJarLocation(); 121 // we extend the classpath by one element and append junit.jar 122 String[] newClasspath = new String[classpath.length + 1]; 123 System.arraycopy(classpath, 0, newClasspath, 0, classpath.length); 124 newClasspath[newClasspath.length - 1] = jarLocation; 125 classpath = newClasspath; 126 } catch (IOException e) { 127 // This should not happen as we depend on the org.junit 128 // plugin explicitly; the error is logged here so that the user can 129 // trace back the cause when the test fails to run 130 AdtPlugin.log(e, "Could not find a valid junit.jar"); 131 AdtPlugin.printErrorToConsole(projectName, 132 "Could not find a valid junit.jar"); 133 // Return the classpath as-is (with no junit.jar) anyway because we 134 // will let the actual launch config fails. 135 } 136 137 return classpath; 138 } 139 140 /** 141 * Returns the path of the junit jar in the highest version bundle. 142 * 143 * (This is public only so that the test can call it) 144 * 145 * @return the path as a string 146 * @throws IOException 147 */ getJunitJarLocation()148 public static String getJunitJarLocation() throws IOException { 149 Bundle bundle = Platform.getBundle("org.junit"); //$NON-NLS-1$ 150 if (bundle == null) { 151 throw new IOException("Cannot find org.junit bundle"); 152 } 153 URL jarUrl = bundle.getEntry(AdtConstants.WS_SEP + JUNIT_JAR); 154 return FileLocator.resolve(jarUrl).getFile(); 155 } 156 } 157