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