/******************************************************************************* * Copyright (c) 2015, 2016 Kichwa Coders 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: * Jonah Graham (Kichwa Coders) - initial API and implementation to Add support for gdb's "set substitute-path" (Bug 472765) *******************************************************************************/ package org.eclipse.cdt.tests.dsf.gdb.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.ExecutionException; import org.eclipse.cdt.core.ISourceFinder; import org.eclipse.cdt.core.model.IBinary; import org.eclipse.cdt.debug.core.CDIDebugModel; import org.eclipse.cdt.debug.core.CDebugCorePlugin; import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; import org.eclipse.cdt.debug.core.model.ICBreakpoint; import org.eclipse.cdt.debug.core.model.ICBreakpointType; import org.eclipse.cdt.debug.core.model.ICLineBreakpoint; import org.eclipse.cdt.debug.core.sourcelookup.AbsolutePathSourceContainer; import org.eclipse.cdt.debug.core.sourcelookup.CProjectSourceContainer; import org.eclipse.cdt.debug.core.sourcelookup.MappingSourceContainer; import org.eclipse.cdt.debug.core.sourcelookup.ProgramRelativePathSourceContainer; import org.eclipse.cdt.debug.internal.core.sourcelookup.CSourceLookupDirector; import org.eclipse.cdt.debug.internal.core.sourcelookup.MapEntrySourceContainer; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DsfExecutor; import org.eclipse.cdt.dsf.concurrent.Query; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMData; import org.eclipse.cdt.dsf.debug.sourcelookup.DsfSourceLookupDirector; import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils; import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIMixedInstruction; import org.eclipse.cdt.dsf.service.DsfServicesTracker; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.cdt.tests.dsf.gdb.framework.AsyncCompletionWaitor; import org.eclipse.cdt.tests.dsf.gdb.framework.BaseParametrizedTestCase; import org.eclipse.cdt.tests.dsf.gdb.framework.BaseTestCase; import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil; import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Adapters; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointListener; import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector; import org.eclipse.debug.core.sourcelookup.ISourceContainer; import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; import org.eclipse.debug.core.sourcelookup.containers.DefaultSourceContainer; import org.eclipse.debug.core.sourcelookup.containers.DirectorySourceContainer; import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; /** * Tests that interaction with source lookups works as expected. * <p> * All of these tests use one of SourceLookup*.exe that was built from a file * that was "moved" since build time. At build time the SourceLookup.cc file was * located in the {@link #BUILD_PATH} directory, but it is now located in the * {@link BaseTestCase#SOURCE_PATH} directory. * <p> * The wild card in SourceLookup*.exe can be one of the following to cover the * different effective types of source lookups that need to be done depending on * how the program was compiled. Each of these options produces different debug * information about where to find the source file. See the Makefile for more * information. * <ul> * <li><b>AC</b>: Absolute and Canonical path (no ../ in path passed to GCC) * </li> * <li><b>AN</b>: Absolute and Non-Canonical path (a ../ in path passed to GCC) * </li> * <li><b>RC</b>: Relative and Canonical path (no ../ in path passed to GCC) * </li> * <li><b>RN</b>: Relative and Non-Canonical path (a ../ in path passed to GCC) * </li> * <li><b>No suffix</b>: Compilation that does not need mapping to be found * </ul> * In addition, there can also be a <b>Dwarf2</b> in the name. That means it is * designed to run with GDB <= 7.4, see comment in Makefile for OLDDWARFFLAGS. * <p> * The result of the variations on compilation arguments means that some of the * tests are parameterised. * <p> * Some of the CDT source lookup features require newer versions of GDB than * others, therefore the relevant tests use assumeGdbVersion* methods to be * skipped when appropriate. */ @RunWith(Parameterized.class) public class SourceLookupTest extends BaseParametrizedTestCase { protected static final String BUILD_PATH = "data/launch/build/"; protected static final String BUILD2_PATH = "data/launch/build2/"; protected static final String SOURCE_NAME = "SourceLookup.cc"; //$NON-NLS-1$ protected static final int SOURCE_LINE = 15; /** Compiled with absolute and canonical path to SourceLookup.cc */ protected String EXEC_AC_NAME; /** Compiled with absolute and non-canonical path to SourceLookup.cc */ protected String EXEC_AN_NAME; /** Compiled with relative and canonical path to SourceLookup.cc */ protected String EXEC_RC_NAME; /** Compiled with relative and non-canonical path to SourceLookup.cc */ protected String EXEC_RN_NAME; /** * File compiled with SourceLookup.cc in the src directory, so GDB resolves * it without help. */ protected String EXEC_NAME; /** * For version of GDB <= 7.4 we need to use the strict dwarf2 flags. See * comment in Makefile on OLDDWARFFLAGS. */ protected void setExeNames() { String gdbVersion = getGdbVersion(); // has to be strictly lower boolean isLower = LaunchUtils.compareVersions(ITestConstants.SUFFIX_GDB_7_5, gdbVersion) > 0; if (isLower) { EXEC_AC_NAME = "SourceLookupDwarf2AC.exe"; //$NON-NLS-1$ EXEC_AN_NAME = "SourceLookupDwarf2AN.exe"; //$NON-NLS-1$ EXEC_RC_NAME = "SourceLookupDwarf2RC.exe"; //$NON-NLS-1$ EXEC_RN_NAME = "SourceLookupDwarf2RN.exe"; //$NON-NLS-1$ EXEC_NAME = "SourceLookupDwarf2.exe"; //$NON-NLS-1$ } else { EXEC_AC_NAME = "SourceLookupAC.exe"; //$NON-NLS-1$ EXEC_AN_NAME = "SourceLookupAN.exe"; //$NON-NLS-1$ EXEC_RC_NAME = "SourceLookupRC.exe"; //$NON-NLS-1$ EXEC_RN_NAME = "SourceLookupRN.exe"; //$NON-NLS-1$ EXEC_NAME = "SourceLookup.exe"; //$NON-NLS-1$ } } protected static final String SOURCE_ABSPATH = new File(SOURCE_PATH).getAbsolutePath(); protected static final String BUILD_ABSPATH = new File(BUILD_PATH).getAbsolutePath(); /** This path matches the non-canonical path used to build the *N.exe's */ protected static final String BUILD_NONCANONICAL_PATH = new File(new File(BUILD2_PATH).getAbsolutePath(), "../build/").toString(); /** * Map entry for non-canonical build dirs */ protected MapEntrySourceContainer fMapEntrySourceContainerN = new MapEntrySourceContainer(BUILD_NONCANONICAL_PATH, new Path(SOURCE_ABSPATH)); /** * Map entry for canonical build dirs */ protected MapEntrySourceContainer fMapEntrySourceContainerC = new MapEntrySourceContainer(BUILD_ABSPATH, new Path(SOURCE_ABSPATH)); protected AsyncCompletionWaitor fBreakpointInstalledWait = new AsyncCompletionWaitor(); protected IBreakpointListener fBreakpointListener = new IBreakpointListener() { @Override public void breakpointAdded(IBreakpoint breakpoint) { } @Override public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { } @Override public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { if (breakpoint instanceof ICBreakpoint) { try { if (((ICBreakpoint) breakpoint).isInstalled()) { fBreakpointInstalledWait.waitFinished(); } } catch (CoreException e) { } } } }; private IGDBControl fCommandControl; private CommandFactory fCommandFactory; @Override public void doBeforeTest() throws Exception { removeTeminatedLaunchesBeforeTest(); IBreakpointManager manager = DebugPlugin.getDefault().getBreakpointManager(); manager.addBreakpointListener(fBreakpointListener); setExeNames(); super.setLaunchAttributes(); // executable and source lookup attributes are custom per test, // so delay launch until they are setup } /** * Remove any platform breakpoints that have been created. */ @Override public void doAfterTest() throws Exception { super.doAfterTest(); IBreakpointManager manager = DebugPlugin.getDefault().getBreakpointManager(); IBreakpoint[] breakpoints = manager.getBreakpoints(); manager.removeBreakpoints(breakpoints, true); manager.removeBreakpointListener(fBreakpointListener); } protected void doLaunch(String programName) throws Exception { setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, programName); super.doLaunch(); final DsfSession session = getGDBLaunch().getSession(); Runnable runnable = new Runnable() { @Override public void run() { DsfServicesTracker tracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), session.getId()); fCommandControl = tracker.getService(IGDBControl.class); fCommandFactory = fCommandControl.getCommandFactory(); tracker.dispose(); } }; session.getExecutor().submit(runnable).get(); } @Override protected void doLaunch() throws Exception { throw new RuntimeException("Within this test you must use doLaunch(String) to setup program"); } /** * Return true if file refers to the real source file. */ protected boolean fileExists(String file) { if (!file.endsWith(SOURCE_NAME)) return false; return Files.exists(Paths.get(file)); } /** * Custom assertion that neither GDB nor the Source Locator found the source * file. */ protected void assertSourceNotFound() throws Throwable { // Check file as resolved by source lookup director ISourceLookupDirector director = (ISourceLookupDirector) getGDBLaunch().getSourceLocator(); IFrameDMContext frameDmc = SyncUtil.getStackFrame(0, 0); Object sourceElement = director.getSourceElement(frameDmc); assertNull("Source Locator unexpectedly found the source", sourceElement); // Check file name as returned from back end IFrameDMData frameData = SyncUtil.getFrameData(0, 0); assertFalse("GDB Unexpectedly located the source", fileExists(frameData.getFile())); // Check file as resolved by ISourceLookup service try { SyncUtil.getSource(frameData.getFile()); fail("Source Lookup service unexpectedly found the source"); } catch (ExecutionException e) { assertNotNull(e.getCause()); assertTrue(e.getCause() instanceof CoreException); assertEquals("No sources found", e.getCause().getMessage()); } } /** * Custom assertion that GDB did not find the source, but the Source Locator * found the source file. */ protected void assertSourceFoundByDirectorOnly() throws Throwable { // Check file as resolved by source lookup director ISourceLookupDirector director = (ISourceLookupDirector) getGDBLaunch().getSourceLocator(); IFrameDMContext frameDmc = SyncUtil.getStackFrame(0, 0); Object sourceElement = director.getSourceElement(frameDmc); assertTrue("Source locator failed to find source", sourceElement instanceof IStorage); // Check file name as returned from back end IFrameDMData frameData = SyncUtil.getFrameData(0, 0); assertFalse("GDB Unexpectedly located the source", fileExists(frameData.getFile())); // Check file as resolved by ISourceLookup service sourceElement = SyncUtil.getSource(frameData.getFile()); assertTrue("Source Lookup service failed to find source", sourceElement instanceof IStorage); } /** * Custom assertion that GDB and the Source Locator found the source file. */ protected void assertSourceFound() throws Throwable { // Check file as resolved by source lookup director ISourceLookupDirector director = (ISourceLookupDirector) getGDBLaunch().getSourceLocator(); IFrameDMContext frameDmc = SyncUtil.getStackFrame(0, 0); Object sourceElement = director.getSourceElement(frameDmc); assertTrue("Source locator failed to find source", sourceElement instanceof IStorage); // Check file name as returned from back end IFrameDMData frameData = SyncUtil.getFrameData(0, 0); assertTrue("GDB failed to find source", fileExists(frameData.getFile())); // Check file as resolved by ISourceLookup service sourceElement = SyncUtil.getSource(frameData.getFile()); assertTrue("Source Lookup service failed to find source", sourceElement instanceof IStorage); } /** * Test with only default source locators on, that the source file was not * found when stopped at main by any of GDB directly, with the source lookup * director, or via the ISourceLookup service. */ @Test public void defaultSourceLookup() throws Throwable { doLaunch(EXEC_PATH + EXEC_RC_NAME); assertSourceNotFound(); } protected AbstractSourceLookupDirector setSourceContainer(ISourceContainer container) throws CoreException { AbstractSourceLookupDirector director = (AbstractSourceLookupDirector) DebugPlugin.getDefault() .getLaunchManager().newSourceLocator("org.eclipse.cdt.debug.core.sourceLocator"); addSourceContainer(director, new DefaultSourceContainer()); addSourceContainer(director, container); return director; } protected void addSourceContainer(AbstractSourceLookupDirector director, ISourceContainer container) throws CoreException { ArrayList<ISourceContainer> containerList = new ArrayList<ISourceContainer>( Arrays.asList(director.getSourceContainers())); container.init(director); containerList.add(container); director.setSourceContainers(containerList.toArray(new ISourceContainer[containerList.size()])); setLaunchAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, director.getMemento()); setLaunchAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, director.getId()); } /** * Add the mapping source container to the common source lookup */ protected void doMappingInCommon(boolean canonical, boolean withBackend) { CSourceLookupDirector commonSourceLookupDirector = CDebugCorePlugin.getDefault() .getCommonSourceLookupDirector(); MappingSourceContainer mapContainer = new MappingSourceContainer("Mappings"); mapContainer.setIsMappingWithBackendEnabled(withBackend); if (canonical) { mapContainer.addMapEntry(fMapEntrySourceContainerC); } else { mapContainer.addMapEntry(fMapEntrySourceContainerN); } ArrayList<ISourceContainer> containerList = new ArrayList<ISourceContainer>( Arrays.asList(commonSourceLookupDirector.getSourceContainers())); containerList.add(mapContainer); commonSourceLookupDirector .setSourceContainers(containerList.toArray(new ISourceContainer[containerList.size()])); } /** * Resource common source container to the default */ protected void restoreCommonToDefault() { CSourceLookupDirector commonSourceLookupDirector = CDebugCorePlugin.getDefault() .getCommonSourceLookupDirector(); ISourceContainer[] containers = new ISourceContainer[3]; int i = 0; containers[i++] = new AbsolutePathSourceContainer(); containers[i++] = new ProgramRelativePathSourceContainer(); containers[i++] = new CProjectSourceContainer(null, true); commonSourceLookupDirector.setSourceContainers(containers); } /** * Set default source locators and a path mapping * {@link MappingSourceContainer} from BUILD_ABSPATH -> SOURCE_ABSPATH and * do the launch */ protected void doMappingAndLaunch(String programName, boolean withBackend) throws CoreException, Exception { MappingSourceContainer mapContainer = new MappingSourceContainer("Mappings"); mapContainer.setIsMappingWithBackendEnabled(withBackend); if (programName.endsWith("N.exe")) { mapContainer.addMapEntry(fMapEntrySourceContainerN); } else if (programName.endsWith("C.exe")) { mapContainer.addMapEntry(fMapEntrySourceContainerC); } else { fail("Unexpected file"); } setSourceContainer(mapContainer); doLaunch(EXEC_PATH + programName); } /** * Mapping test common. * * If backend is used for mapping then every layer should be able to find * source. * * If backned is not used for mapping then only once the source lookup * director gets involved should the source be found as GDB will not know * how to find it on its own. */ protected void sourceMapping(String programName, boolean withBackend) throws Throwable { doMappingAndLaunch(programName, withBackend); if (withBackend) { assertSourceFound(); } else { assertSourceFoundByDirectorOnly(); } } /** * With mapping test breakpoints can be inserted. */ protected void sourceMappingBreakpoints(String programName, boolean withBackend) throws Throwable { doMappingAndLaunch(programName, withBackend); assertInsertBreakpointSuccessful(); } /** * Assert that a breakpoint can be successfully inserted. To successfully * insert a breakpoint it means the the mapping of local file names to * compilation paths is working properly. */ protected void assertInsertBreakpointSuccessful() throws Throwable { // insert breakpoint in source file fBreakpointInstalledWait.waitReset(); ICLineBreakpoint bp = CDIDebugModel.createLineBreakpoint( new Path(SOURCE_ABSPATH).append(SOURCE_NAME).toOSString(), ResourcesPlugin.getWorkspace().getRoot(), ICBreakpointType.REGULAR, SOURCE_LINE, true, 0, "", true); // The delay here is based on: // 1) The installation of the breakpoint takes some time // 2) The notification of the IBreakpoint change needs the autobuild // to run, and it has up to a 1 second delay (depending on how // long since it last ran). See // org.eclipse.core.internal.events.AutoBuildJob.computeScheduleDelay() fBreakpointInstalledWait.waitUntilDone(TestsPlugin.massageTimeout(2000)); assertTrue("Breakpoint failed to install", bp.isInstalled()); } /** * Tests that GDB >= 7.6 because DSF is using the full path name to pass to * the {@link ISourceContainer#findSourceElements(String)}. In versions * prior to 7.6 the fullname field was not returned from GDB if the file was * not found by GDB. See * <a href= "https://sourceware.org/ml/gdb-patches/2012-12/msg00557.html"> * the mailing list</a> and associated <a href= * "https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=commitdiff;h=ec83d2110de6831ac2ed0e5a56dc33c60a477eb6"> * gdb/NEWS item</a> (although you have to dig quite deep on these changes.) * * Therefore in version < 7.6 the MI frame info has file="SourceLookup.cc" * and no fullname field. This means there is no path to source map against. * * In version >= 7.6 the MI frame info has file="SourceLookup.cc",fullname= * "<cdt.git path>/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/build/SourceLookup.cc" * fields, so there is a path to do the mapping against. Recall that the * test maps * "<cdt.git path>/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/build" * to "<cdt.git path>/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src" */ protected void assumeGdbVersionFullnameWorking() { assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_7_6); } /** * Inverse of {@link #assumeGdbVersionFullnameWorking()} */ protected void assumeGdbVersionFullnameNotWorking() { assumeGdbVersionLowerThen(ITestConstants.SUFFIX_GDB_7_6); } /** * Test source mappings with executable built with an Absolute and Canonical * build path */ @Test public void sourceMappingAC() throws Throwable { assumeGdbVersionFullnameWorking(); sourceMapping(EXEC_AC_NAME, false); } /** * Test source mappings with executable built with an Absolute and Canonical * build path */ @Test public void sourceSubstituteAC() throws Throwable { sourceMapping(EXEC_AC_NAME, true); } /** * Test source mappings with executable built with an Absolute and * Non-canonical build path */ @Test public void sourceMappingAN() throws Throwable { assumeGdbVersionFullnameWorking(); sourceMapping(EXEC_AN_NAME, false); } /** * Test source mappings with executable built with an Absolute and * Non-canonical build path */ @Test public void sourceSubstituteAN() throws Throwable { /* * GDB < 6.8 does not work correctly with substitute-paths with .. in * the build path when the build path is an absolute path. GDB 6.8 and * above works fine in this case. */ assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_6_8); sourceMapping(EXEC_AN_NAME, true); } /** * Test source mappings with executable built with a Relative and Canonical * build path */ @Test public void sourceMappingRC() throws Throwable { assumeGdbVersionFullnameWorking(); sourceMapping(EXEC_RC_NAME, false); } /** * Test source mappings with executable built with a Relative and Canonical * build path */ @Test public void sourceSubstituteRC() throws Throwable { sourceMapping(EXEC_RC_NAME, true); } /** * Test source mappings with executable built with a Relative and * Non-canonical build path */ @Test public void sourceMappingRN() throws Throwable { assumeGdbVersionFullnameWorking(); sourceMapping(EXEC_RN_NAME, false); } /** * Test source mappings with executable built with a Relative and * Non-canonical build path */ @Test public void sourceSubstituteRN() throws Throwable { /* * GDB < 7.6 does not work correctly with substitute-paths with .. in * the build path when the build path is a relative path. GDB 7.6 and * above works fine in this case. */ assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_7_6); sourceMapping(EXEC_RN_NAME, true); } /** * Test source mappings with executable built with an Absolute and Canonical * build path */ @Test public void sourceMappingBreakpointsAC() throws Throwable { assumeGdbVersionFullnameWorking(); sourceMappingBreakpoints(EXEC_AC_NAME, false); } /** * Test source mappings with executable built with an Absolute and Canonical * build path */ @Test public void sourceSubstituteBreakpointsAC() throws Throwable { sourceMappingBreakpoints(EXEC_AC_NAME, true); } /** * Test source mappings with executable built with an Absolute and * Non-canonical build path */ @Ignore("Not supported because GDB does not handle non-canonical paths. See Bug 477057") @Test public void sourceMappingBreakpointsAN() throws Throwable { sourceMappingBreakpoints(EXEC_AN_NAME, false); } /** * Test source mappings with executable built with an Absolute and * Non-canonical build path */ @Test public void sourceSubstituteBreakpointsAN() throws Throwable { /* * GDB < 6.8 does not work correctly with substitute-paths with .. in * the build path when the build path is an absolute path. GDB 6.8 and * above works fine in this case. */ assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_6_8); sourceMappingBreakpoints(EXEC_AN_NAME, true); } /** * Test source mappings with executable built with a Relative and Canonical * build path */ @Test public void sourceMappingBreakpointsRC() throws Throwable { assumeGdbVersionFullnameWorking(); sourceMappingBreakpoints(EXEC_RC_NAME, false); } /** * Test source mappings with executable built with a Relative and Canonical * build path */ @Test public void sourceSubstituteBreakpointsRC() throws Throwable { sourceMappingBreakpoints(EXEC_RC_NAME, true); } /** * Test source mappings with executable built with a Relative and * Non-canonical build path */ @Ignore("Not supported because GDB does not handle non-canonical paths. See Bug 477057") @Test public void sourceMappingBreakpointsRN() throws Throwable { sourceMappingBreakpoints(EXEC_RN_NAME, false); } /** * Test source mappings with executable built with a Relative and * Non-canonical build path */ @Test public void sourceSubstituteBreakpointsRN() throws Throwable { /* * GDB < 7.6 does not work correctly with substitute-paths with .. in * the build path when the build path is a relative path. GDB 7.6 and * above works fine in this case. */ assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_7_6); sourceMappingBreakpoints(EXEC_RN_NAME, true); } /** * Change directory to the binary (aka EXEC_PATH) */ protected void doCdToBinDir() throws Exception { Query<MIInfo> query = new Query<MIInfo>() { @Override protected void execute(DataRequestMonitor<MIInfo> rm) { fCommandControl.queueCommand(fCommandFactory.createMIEnvironmentCD(fCommandControl.getContext(), new File(EXEC_PATH).getAbsolutePath()), rm); } }; fCommandControl.getExecutor().execute(query); query.get(); } /** * Test that if the user changes the source mappings in the middle of a * debug session (e.g. with CSourceNotFoundEditor) that the lookups are * updated. */ public void sourceMappingChangesHelper(boolean withBackend) throws Throwable { doMappingAndLaunch(EXEC_AC_NAME, withBackend); DsfSourceLookupDirector sourceLocator = (DsfSourceLookupDirector) getGDBLaunch().getSourceLocator(); MapEntrySourceContainer incorrectMapEntry = new MapEntrySourceContainer(BUILD_ABSPATH + "/incorrectsubpath", new Path(SOURCE_ABSPATH)); if (withBackend) { assertSourceFound(); } else { assertSourceFoundByDirectorOnly(); } // Change the source mappings ISourceContainer[] containers = sourceLocator.getSourceContainers(); MappingSourceContainer mappingSourceContainer = (MappingSourceContainer) containers[1]; mappingSourceContainer.removeMapEntry(fMapEntrySourceContainerC); mappingSourceContainer.addMapEntry(incorrectMapEntry); sourceLocator.setSourceContainers(containers); /* * GDB (pre 7.0) changes the current directory when the above source is * found. As a result GDB is able to find the source even though we have * changed the source lookup paths. To make sure that GDB is really * doing a substitution rather than looking in current directory, change * the current directory. Without this, the assertSourceNotFound fails * because GDB unexpectedly finds the source (for the wrong reason). */ if (withBackend) { doCdToBinDir(); } assertSourceNotFound(); // Change the source mappings back containers = sourceLocator.getSourceContainers(); mappingSourceContainer = (MappingSourceContainer) containers[1]; mappingSourceContainer.removeMapEntry(incorrectMapEntry); mappingSourceContainer.addMapEntry(fMapEntrySourceContainerC); sourceLocator.setSourceContainers(containers); if (withBackend) { assertSourceFound(); } else { assertSourceFoundByDirectorOnly(); } } @Test public void sourceMappingChanges() throws Throwable { sourceMappingChangesHelper(false); } @Test public void sourceSubstituteChanges() throws Throwable { sourceMappingChangesHelper(true); } /** * Test that if the user changes the source mappings in the middle of a * debug session (e.g. with CSourceNotFoundEditor) that the lookups are * updated. * * This version is for a new source mapping where there wasn't one * previously. */ public void sourceMappingAddedHelper(boolean withBackend) throws Throwable { doLaunch(EXEC_PATH + EXEC_AC_NAME); assertSourceNotFound(); // Set the source mappings DsfSourceLookupDirector sourceLocator = (DsfSourceLookupDirector) getGDBLaunch().getSourceLocator(); ISourceContainer[] containers = sourceLocator.getSourceContainers(); MappingSourceContainer mappingSourceContainer = new MappingSourceContainer("Mappings"); mappingSourceContainer.setIsMappingWithBackendEnabled(withBackend); mappingSourceContainer.addMapEntry(fMapEntrySourceContainerC); ISourceContainer[] newContainers = new ISourceContainer[containers.length + 1]; System.arraycopy(containers, 0, newContainers, 0, containers.length); newContainers[newContainers.length - 1] = mappingSourceContainer; sourceLocator.setSourceContainers(newContainers); if (withBackend) { assertSourceFound(); } else { assertSourceFoundByDirectorOnly(); } } @Test public void sourceMappingAdded() throws Throwable { sourceMappingAddedHelper(false); } @Test public void sourceSubstituteAdded() throws Throwable { sourceMappingAddedHelper(true); } /** * Test with default source locators and a {@link DirectorySourceContainer} * for SOURCE_ABSPATH that GDB does not locate the file, but the source * lookup director and the source lookup service do find the file. * * This test does not work with modern GDBs because the path passed into * DirectorySourceContainer is an absolute path. See versioned test suites. */ @Test public void directorySource() throws Throwable { /* * DirectorySourceContainer only works if there is no fullname coming * from GDB */ assumeGdbVersionFullnameNotWorking(); DirectorySourceContainer container = new DirectorySourceContainer(new Path(SOURCE_ABSPATH), false); setSourceContainer(container); doLaunch(EXEC_PATH + EXEC_RC_NAME); assertSourceFoundByDirectorOnly(); } /** * Create an IBinary with the minimum necessary for use in * org.eclipse.cdt.debug.internal.core.srcfinder.CSourceFinder. * * A mock is used to avoid having to set up the significant of glue * necessary to create a real IBinary. All that CSourceFinder needs is the * path to the file. */ protected IBinary createMockIBinary(String path) { IPath absPath = new Path(new File(path).getAbsolutePath()); IResource exeResource = mock(IResource.class); when(exeResource.getFullPath()).thenReturn(absPath); when(exeResource.getProject()).thenReturn(null); IBinary exeBin = mock(IBinary.class); when(exeBin.getPath()).thenReturn(absPath); when(exeBin.getAdapter(IResource.class)).thenReturn(exeResource); /* * we use the adapter factory CSourceFinderFactory to convert IBinary to * ISourceFinder. The way the adapter is resolved it will first try and * and get the adapter from the IBinary (IBinary extends IAdaptable) so * we need to return null here for a clean failure. If we didn't * explicitly provide the null, an exception would be raised because an * unexpected method is invoked. */ when(exeBin.getAdapter(ISourceFinder.class)).thenReturn(null); return exeBin; } /** * Assert that the finder is able resolve the source file name */ protected void assertFinderFinds(String programName, String buildAbspath) { IBinary binary = createMockIBinary(EXEC_PATH + programName); ISourceFinder finder = Adapters.adapt(binary, ISourceFinder.class, true); try { String localPath = finder.toLocalPath(buildAbspath); assertEquals("Source Finder failed to find file", new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath(), localPath); } finally { finder.dispose(); } } /** * Assert that the finder is not able resolve the source file name */ protected void assertFinderDoesNotFind(String programName, String buildAbspath) { IBinary binary = createMockIBinary(EXEC_PATH + programName); ISourceFinder finder = Adapters.adapt(binary, ISourceFinder.class, true); try { String localPath = finder.toLocalPath(buildAbspath); assertNotEquals("Source Finder unexpectedly found file", new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath(), localPath); } finally { finder.dispose(); } } /** * Test the base case of the source finder, when it does not need any * special help like mapping to find a file. */ @Test public void sourceFinder() throws Throwable { assertFinderFinds(EXEC_AC_NAME, new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath()); } /** * Test the CSourceFinder's use of source lookup when there is an active * launch. * * In this case, the DSF specific director created as part of the launch * gets used. */ public void sourceFinderMappingAC_ActiveLaunchHelper(boolean withBackend) throws Throwable { assertFinderDoesNotFind(EXEC_AC_NAME, new File(BUILD_PATH, SOURCE_NAME).getAbsolutePath()); doMappingAndLaunch(EXEC_AC_NAME, withBackend); assertFinderFinds(EXEC_AC_NAME, new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath()); } @Test public void sourceFinderMappingAC_ActiveLaunch() throws Throwable { sourceFinderMappingAC_ActiveLaunchHelper(false); } @Test public void sourceFinderSubstituteAC_ActiveLaunch() throws Throwable { sourceFinderMappingAC_ActiveLaunchHelper(true); } /** * Test the CSourceFinder's use of source lookup when there is a terminated * launch. * * In this case, the DSF specific director created as part of the launch * gets used. */ public void sourceFinderMappingAC_TerminatedLaunchHelper(boolean withBackend) throws Throwable { sourceFinderMappingAC_ActiveLaunchHelper(withBackend); // Terminate the launch, but don't remove it doAfterTest(); assertFinderFinds(EXEC_AC_NAME, new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath()); } @Test public void sourceFinderMappingAC_TerminatedLaunch() throws Throwable { sourceFinderMappingAC_TerminatedLaunchHelper(false); } @Test public void sourceFinderSubstituteAC_TerminatedLaunch() throws Throwable { sourceFinderMappingAC_ActiveLaunchHelper(true); } /** * Test the CSourceFinder's use of source lookup when there is a not active * launch, but a launch configuration that can be used. * * In this case, the c general director created as part of the launch gets * used. */ public void sourceFinderMappingAC_LaunchConfigHelper(boolean withBackend) throws Throwable { sourceFinderMappingAC_TerminatedLaunchHelper(withBackend); // Remove the launch, so that we can test with the existing // configuration ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); ILaunch[] launches = launchManager.getLaunches(); assertEquals("Unexpected number of launches", 1, launches.length); assertTrue(launches[0].isTerminated()); launchManager.removeLaunches(launches); ILaunchConfiguration[] launchConfigurations = launchManager.getLaunchConfigurations(); assertEquals("Unexpected number of launch configuration", 1, launchConfigurations.length); assertFinderFinds(EXEC_AC_NAME, new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath()); } @Test public void sourceFinderMappingAC_LaunchConfig() throws Throwable { sourceFinderMappingAC_LaunchConfigHelper(false); } @Test public void sourceFinderSubstituteAC_LaunchConfig() throws Throwable { sourceFinderMappingAC_LaunchConfigHelper(true); } /** * Test that CSourceFinder works with the common source director, i.e. no * launches or launch configs in place. */ public void sourceFinderMappingAC_CommonLocatorHelper(boolean withBackend) throws Throwable { assertFinderDoesNotFind(EXEC_AC_NAME, new File(BUILD_PATH, SOURCE_NAME).getAbsolutePath()); doMappingInCommon(true, withBackend); try { assertFinderFinds(EXEC_AC_NAME, new File(SOURCE_PATH, SOURCE_NAME).getAbsolutePath()); } finally { restoreCommonToDefault(); } assertFinderDoesNotFind(EXEC_AC_NAME, new File(BUILD_PATH, SOURCE_NAME).getAbsolutePath()); } @Test public void sourceFinderMappingAC_CommonLocator() throws Throwable { sourceFinderMappingAC_CommonLocatorHelper(false); } @Test public void sourceFinderSubstituteAC_CommonLocator() throws Throwable { sourceFinderMappingAC_CommonLocatorHelper(true); } /** * This test verifies that doing a source lookup where the absolute name of * the file is provided by the backend resolves. * * In the normal DSF case * {@link ISourceLookupDirector#findSourceElements(Object)} is called with a * {@link IDMContext}, e.g. a stack frame DMC. * * However, the disassembly view/editor does the lookup on a String (it * passes the result of {@link MIMixedInstruction#getFileName()} to * findSourceElements). * * In both the CDI and DSF participants there is special handling to ensure * that absolute file names are resolved even if there are no source * containers in the launch configuration. */ @Test public void noExplicitSourceContainers() throws Throwable { // create a director with no containers so that the memento can be // created. AbstractSourceLookupDirector tmpDirector = (AbstractSourceLookupDirector) DebugPlugin.getDefault() .getLaunchManager().newSourceLocator("org.eclipse.cdt.debug.core.sourceLocator"); tmpDirector.setSourceContainers(new ISourceContainer[0]); setLaunchAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, tmpDirector.getMemento()); setLaunchAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, tmpDirector.getId()); // We are using the version of the executable that is resolvable, i.e // the one that has not had its source file moved since we compiled. doLaunch(EXEC_PATH + EXEC_NAME); assertSourceFound(); } /** * Test verifies interaction between director that has two mappers, one with * backend enabled and one without with the second being the only valid one. */ @Test public void twoMappersSecondValid() throws Throwable { MappingSourceContainer substituteContainer = new MappingSourceContainer("Mappings With Backend"); substituteContainer.setIsMappingWithBackendEnabled(true); /* * the entry here does not matter, as long as it is not the valid * substitution, we want to make sure that we process the other * MappingSourceContainer correctly */ substituteContainer.addMapEntry(new MapEntrySourceContainer("/from_invalid", new Path("/to_invalid"))); AbstractSourceLookupDirector director = setSourceContainer(substituteContainer); // this is the mapping we want to do the work MappingSourceContainer mapContainer = new MappingSourceContainer("Mappings"); mapContainer.setIsMappingWithBackendEnabled(false); mapContainer.addMapEntry(fMapEntrySourceContainerC); addSourceContainer(director, mapContainer); doLaunch(EXEC_PATH + EXEC_AC_NAME); /* * because the backend substitution does not apply, we resolve with the * CDT mapping, in this case that means that only the director should * locate the source. */ assertSourceFoundByDirectorOnly(); assertInsertBreakpointSuccessful(); } /** * Test verifies interaction between director that has two mappers, one with * backend enabled and one without, with the first being the only valid one, * and the second causing the breakpoint installation to fail. */ @Test public void twoMappersFirstValid() throws Throwable { // This first mapping is valid and should cause to find the source MappingSourceContainer substituteContainer = new MappingSourceContainer("Mappings With Backend"); substituteContainer.setIsMappingWithBackendEnabled(true); substituteContainer.addMapEntry(fMapEntrySourceContainerC); AbstractSourceLookupDirector director = setSourceContainer(substituteContainer); /* * Because of the above valid mapping substitution, GDB will provide the * proper path to the source and it will be found no matter what the * below mapping is set to. On the other hand, when setting a * breakpoint, we have to make sure that the below mapping does not * change the path to something GDB does not know. Therefore, we set the * below mapping from an invalid compilation path to the proper source * path. This is so that if the below mapping is triggered it will cause * us to try to set a breakpoint in GDB on an invalid path, thus failing * the test. This allows to verify that the first mapping is used once * it is found to be valid and does not fallback to the next mapping. */ MappingSourceContainer mapContainer = new MappingSourceContainer("Mappings"); mapContainer.setIsMappingWithBackendEnabled(false); mapContainer.addMapEntry(new MapEntrySourceContainer("/from_invalid", new Path(SOURCE_ABSPATH))); addSourceContainer(director, mapContainer); doLaunch(EXEC_PATH + EXEC_AC_NAME); /* * because the backend substitution applies, we should be able to find * the source with the director or without it. */ assertSourceFound(); assertInsertBreakpointSuccessful(); } /** * Terminate the session on the executor thread without blocking. * * This models the way DsfTerminateCommand terminates the launches. */ protected void terminateAsync(DsfSession session) throws Exception { DsfExecutor executor = session.getExecutor(); executor.execute(() -> { DsfServicesTracker tracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), session.getId()); IGDBControl commandControl = tracker.getService(IGDBControl.class); tracker.dispose(); commandControl.terminate(new RequestMonitor(executor, null)); }); } /** * Test that two launches can be launched and terminated without becoming * interlocked. * * This is a regression test for Bug 494650. * * XXX: If this test fails as it did for the reason of Bug 494650, there is * deadlock and the JVM does not recover, causing this test to timeout and * all subsequent tests not to work. */ @Test public void twoLaunchesTerminate() throws Throwable { Assume.assumeFalse("Test framework only supports multiple launches for non-remote", remote); // Launch first session doLaunch(EXEC_PATH + EXEC_NAME); GdbLaunch launch1 = getGDBLaunch(); // Launch additional session with same launch configuration GdbLaunch launch2 = doLaunchInner(); /* * Bug 494650 affects when two launches are terminated too close * together. In normal operation that means that the two terminates is * sufficient. However, it can happen that the first one terminates * progresses sufficiently far that the deadlock does not happen on the * second. * * NOTE: Can't use launch.terminate() here because it terminates * synchronously when adapters are not installed. Instead we need to * issue the terminates in a non-blocking way on both the the executor * threads, the way that terminate works when adapters are installed. */ terminateAsync(launch1.getSession()); terminateAsync(launch2.getSession()); /* * In Bug 494650 the UI locks up because the executor thread of both * sessions is waiting on each other and the UI thread is waiting on the * executor thread. The UI thread is waiting by using a Query, and * before the bug fix the two executor threads were waiting on each * other using a Query too. * * This test does not use the UI thread (aka main), but instead the * JUnit test thread. We determine success if both launches terminate, * because if they both terminate they have stayed responsive and * successfully completed the entire shutdown sequences without * deadlocking. */ waitUntil("Timeout waiting for launches to terminate", () -> launch1.isTerminated() && launch2.isTerminated()); } }