• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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