aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/JUnitLaunchConfigDelegate.java
blob: fdb1b305a7b5daaba3f30ad6a8cf3157d1c5295f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.launch;

import com.android.SdkConstants;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
import org.osgi.framework.Bundle;

import java.io.IOException;
import java.net.URL;

/**
 * <p>
 * For Android projects, android.jar gets added to the launch configuration of
 * JUnit tests as a bootstrap entry. This breaks JUnit tests as android.jar
 * contains a skeleton version of JUnit classes and the JVM will stop with an error similar
 * to: <blockquote> Error occurred during initialization of VM
 * java/lang/NoClassDefFoundError: java/lang/ref/FinalReference </blockquote>
 * <p>
 * At compile time, Eclipse does not know that there is no valid junit.jar in
 * the classpath since it can find a correct reference to all the necessary
 * org.junit.* classes in the android.jar so it does not prompt the user to add
 * the JUnit3 or JUnit4 jar.
 * <p>
 * This delegates removes the android.jar from the bootstrap path and if
 * necessary also puts back the junit.jar in the user classpath.
 * <p>
 * This delegate will be present for both Java and Android projects (delegates
 * setting instead of only the current project) but the behavior for Java
 * projects should be neutral since:
 * <ol>
 * <li>Java tests can only compile (and then run) when a valid junit.jar is
 * present
 * <li>There is no android.jar in Java projects
 * </ol>
 */
public class JUnitLaunchConfigDelegate extends JUnitLaunchConfigurationDelegate {

    private static final String JUNIT_JAR = "junit.jar"; //$NON-NLS-1$

    @Override
    public String[][] getBootpathExt(ILaunchConfiguration configuration) throws CoreException {
        String[][] bootpath = super.getBootpathExt(configuration);
        return fixBootpathExt(bootpath);
    }

    @Override
    public String[] getClasspath(ILaunchConfiguration configuration) throws CoreException {
        String[] classpath = super.getClasspath(configuration);
        return fixClasspath(classpath, getJavaProjectName(configuration));
    }

    /**
     * Removes the android.jar from the bootstrap path if present.
     *
     * @param bootpath Array of Arrays of bootstrap class paths
     * @return a new modified (if applicable) bootpath
     */
    public static String[][] fixBootpathExt(String[][] bootpath) {
        for (int i = 0; i < bootpath.length; i++) {
            if (bootpath[i] != null && bootpath[i].length > 0) {
                // we assume that the android.jar can only be present in the
                // bootstrap path of android tests
                if (bootpath[i][0].endsWith(SdkConstants.FN_FRAMEWORK_LIBRARY)) {
                    bootpath[i] = null;
                }
            }
        }
        return bootpath;
    }

    /**
     * Add the junit.jar to the user classpath; since Eclipse was relying on
     * android.jar to provide the appropriate org.junit classes, it does not
     * know it actually needs the junit.jar.
     *
     * @param classpath Array containing classpath
     * @param projectName The name of the project (for logging purposes)
     *
     * @return a new modified (if applicable) classpath
     */
    public static String[] fixClasspath(String[] classpath, String projectName) {
        // search for junit.jar; if any are found return immediately
        for (int i = 0; i < classpath.length; i++) {
            if (classpath[i].endsWith(JUNIT_JAR)) {
                return classpath;
            }
        }

        // This delegate being called without a junit.jar present is only
        // possible for Android projects. In a non-Android project, the test
        // would not compile and would be unable to run.
        try {
            // junit4 is backward compatible with junit3 and they uses the
            // same junit.jar from bundle org.junit:
            // When a project has mixed JUnit3 and JUnit4 tests, if JUnit3 jar
            // is added first it is then replaced by the JUnit4 jar when user is
            // prompted to fix the JUnit4 test failure
            String jarLocation = getJunitJarLocation();
            // we extend the classpath by one element and append junit.jar
            String[] newClasspath = new String[classpath.length + 1];
            System.arraycopy(classpath, 0, newClasspath, 0, classpath.length);
            newClasspath[newClasspath.length - 1] = jarLocation;
            classpath = newClasspath;
        } catch (IOException e) {
            // This should not happen as we depend on the org.junit
            // plugin explicitly; the error is logged here so that the user can
            // trace back the cause when the test fails to run
            AdtPlugin.log(e, "Could not find a valid junit.jar");
            AdtPlugin.printErrorToConsole(projectName,
                    "Could not find a valid junit.jar");
            // Return the classpath as-is (with no junit.jar) anyway because we
            // will let the actual launch config fails.
        }

        return classpath;
    }

    /**
     * Returns the path of the junit jar in the highest version bundle.
     *
     * (This is public only so that the test can call it)
     *
     * @return the path as a string
     * @throws IOException
     */
    public static String getJunitJarLocation() throws IOException {
        Bundle bundle = Platform.getBundle("org.junit"); //$NON-NLS-1$
        if (bundle == null) {
            throw new IOException("Cannot find org.junit bundle");
        }
        URL jarUrl = bundle.getEntry(AdtConstants.WS_SEP + JUNIT_JAR);
        return FileLocator.resolve(jarUrl).getFile();
    }
}