/******************************************************************************* * Copyright (c) 2011, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.debug.tests.sourcelookup; import java.nio.file.Files; import java.util.List; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.model.ISourceLocator; import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.core.model.IValue; import org.eclipse.debug.core.sourcelookup.ISourceContainer; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.testplugin.JavaProjectHelper; import org.eclipse.jdt.debug.tests.AbstractDebugTest; import org.eclipse.jdt.debug.tests.TestUtil; import org.eclipse.jdt.internal.core.ClassFile; import org.eclipse.jdt.internal.launching.JavaSourceLookupUtil; import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer; /** * Tests for finding / showing source from jar files from related projects */ public class JarSourceLookupTests extends AbstractDebugTest { private static final String SAMPLE_JAR_PATH = "/JarProject/lib/sample.jar"; public static final String A_RUN_JAR = "testJar.RunJar"; static IJavaProject fgJarProject = null; String RefPjName = "JarRefProject"; String fJarProject = "JarProject"; /** * Constructor */ public JarSourceLookupTests() { super("JarSourceLookupTests"); } /** * Disposes all source containers after a test, ensures no containers are still holding open Jar references, which can lead to {@link ResourceException}s * when we try to delete / setup following tests * @param containers */ void disposeContainers(ISourceContainer[] containers) { if(containers != null) { for (int i = 0; i < containers.length; i++) { containers[i].dispose(); } } } @Override protected IJavaProject getProjectContext() { return fgJarProject; } @Override protected void setUp() throws Exception { super.setUp(); TestUtil.cleanUp(getName()); IPath testrpath = new Path("testresources"); IProject jarProject = createProjectClone(fJarProject, testrpath.append(fJarProject).toString(), true); IFile jar = jarProject.getFile("lib/sample.jar"); assertTrue("lib/sample.jar is missing in project: " + jarProject.getName(), jar.exists()); fgJarProject = createJavaProjectClone(RefPjName, testrpath.append(RefPjName).toString(), JavaProjectHelper.J2SE_1_4_EE_NAME, true); IProject jarRefProject = fgJarProject.getProject(); IFile cp = jarRefProject.getFile(".classpath"); assertTrue(".classpath is missing in project: " + jarRefProject.getName(), cp.exists()); java.nio.file.Path path = cp.getLocation().toFile().toPath(); List<String> lines = Files.readAllLines(path); boolean foundJar = false; for (String line : lines) { if (line.contains(SAMPLE_JAR_PATH)) { foundJar = true; break; } } if (!foundJar) { fail("The .classpath from project " + jarRefProject + " is unexpected and does not have an entry for " + SAMPLE_JAR_PATH + ": " + new String(Files.readAllBytes(path))); } waitForBuild(); } @Override protected void tearDown() throws Exception { removeAllBreakpoints(); if (fgJarProject.exists()) { IProject project = fgJarProject.getProject(); // Before deleting, let indexer to finish his work to avoid error below (see 516351) TestUtil.waitForJobs(getName(), 100, 3000); try { project.delete(true, null); } catch (ResourceException e) { // Indexer still running on our jars? TestUtil.waitForJobs(getName(), 1000, 5000); if (project.exists()) { project.delete(true, null); } } } super.tearDown(); } /** * Ensures the translation of source containers yields the correct containers * * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=346116 * * @throws Exception */ public void testTranslateContainers() throws Exception { createLaunchConfiguration(fgJarProject, LAUNCHCONFIGURATIONS, A_RUN_JAR); ILaunchConfiguration config = getLaunchConfiguration(fgJarProject, LAUNCHCONFIGURATIONS, A_RUN_JAR); IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedSourceLookupPath(config); assertEquals("There should be 2 containers returned (JRE and classpath)", 2, entries.length); IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveSourceLookupPath(entries, config); ISourceContainer[] containers = JavaSourceLookupUtil.translate(resolved); try { assertTrue("There must be computed containers", containers.length > 0); //the number of containers is M + 2, where M is unknown across JREs, 1 for the project container and 1 for the JAR we are looking for assertTrue("There should be at least 2 containers returned", containers.length >= 2); for (int i = 0; i < containers.length; i++) { ISourceContainer sourceContainer = containers[i]; if ("sample.jar".equals(sourceContainer.getName()) && sourceContainer instanceof PackageFragmentRootSourceContainer) { PackageFragmentRootSourceContainer container = (PackageFragmentRootSourceContainer) sourceContainer; if (SAMPLE_JAR_PATH.equals(container.getPackageFragmentRoot().getPath().toString())) { return; } } } StringBuilder dump = new StringBuilder(); for (ISourceContainer sc : containers) { dump.append(sc.getName()); if (sc instanceof PackageFragmentRootSourceContainer) { PackageFragmentRootSourceContainer pfsc = (PackageFragmentRootSourceContainer) sc; dump.append(" with path: ").append(pfsc.getPath()); } dump.append(", "); } dump.setLength(dump.length() - 2); dump.append(".\n Those containers were resolved from: "); for (IRuntimeClasspathEntry cpe : resolved) { dump.append(cpe); dump.append(", "); } dump.setLength(dump.length() - 2); fail("We did not find a source container that was a PackageFragmentRootSourceContainer " + "and had the name " + SAMPLE_JAR_PATH + ", but found source containers: " + dump); } finally { disposeContainers(containers); } } /** * Tests that the class file is found as source when the lookup is done from a jar from another project * * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=346116 * * @throws Exception */ public void testInspectClassFileFromJar() throws Exception { createLaunchConfiguration(fgJarProject, LAUNCHCONFIGURATIONS, A_RUN_JAR); createLineBreakpoint(18, A_RUN_JAR); ILaunchConfiguration config = getLaunchConfiguration(fgJarProject, LAUNCHCONFIGURATIONS, A_RUN_JAR); IJavaThread thread = null; try { thread = launchToBreakpoint(config); IStackFrame frame = thread.getTopStackFrame(); assertTrue("The found frame should be an IJavaStackFrame", frame instanceof IJavaStackFrame); stepInto((IJavaStackFrame)frame); assertNotNull("the stack frame from the thread cannot be null", frame); IValue value = doEval(thread, "this"); assertNotNull("The evaluation result cannot be null", value); assertEquals("the name of the type being inspected must be a.JarClass", "a.JarClass", value.getReferenceTypeName()); } finally { terminateAndRemove(thread); } } /** * Tests that the class file is found as source when the lookup is done from a jar from another project * * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=346116 * * @throws Exception */ public void testShowClassFileFromJar() throws Exception { createLaunchConfiguration(fgJarProject, LAUNCHCONFIGURATIONS, A_RUN_JAR); createLineBreakpoint(18, A_RUN_JAR); ILaunchConfiguration config = getLaunchConfiguration(fgJarProject, LAUNCHCONFIGURATIONS, A_RUN_JAR); IJavaThread thread = null; try { thread = launchToBreakpoint(config); IStackFrame frame = thread.getTopStackFrame(); assertNotNull("The top stack frame cannot be null", frame); assertTrue("The found frame should be an IJavaStackFrame", frame instanceof IJavaStackFrame); Object source = lookupSource(frame); assertNotNull("We should have found source for the main class testJar.RunJar", source); assertTrue("The found source should be an IFile", source instanceof IFile); assertEquals("We should have found a file named RunJar.java", ((IFile)source).getName(), "RunJar.java"); stepInto((IJavaStackFrame)frame); frame = thread.getTopStackFrame(); assertNotNull("The top stack frame cannot be null", frame); source = lookupSource(frame); assertNotNull("We should have found source for the jar class a.JarClass", source); assertTrue("The found source should be a ClassFile", source instanceof ClassFile); assertEquals("we should have found a file named a.JarClass.class", ((ClassFile)source).getElementName(), "JarClass.class"); } finally { terminateAndRemove(thread); } } /** * Looks up source for the given frame using its backing {@link ISourceLocator} from its {@link ILaunch} * @param frame the frame to look up source for * @return the source object or <code>null</code> */ Object lookupSource(IStackFrame frame) { ISourceLocator locator = frame.getLaunch().getSourceLocator(); assertNotNull("The default Java source locator cannot be null", locator); return locator.getSourceElement(frame); } }