/******************************************************************************* * Copyright (c) 2007, 2010 Ericsson 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: * Ericsson - Initial Implementation *******************************************************************************/ package org.eclipse.cdt.tests.dsf.gdb.framework; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.Reader; import java.util.HashMap; import java.util.Map; import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; import org.eclipse.cdt.dsf.datamodel.IDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.cdt.dsf.service.DsfSession.SessionStartedListener; import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; import org.eclipse.cdt.utils.spawner.ProcessFactory; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.rules.TestName; /** * This is the base class for the GDB/MI Unit tests. * It provides the @Before and @After methods which setup * and teardown the launch, for each test. * If these methods are overwridden by a subclass, the new method * must call super.baseSetup or super.baseTeardown itself, if this * code is to be run. */ public class BaseTestCase { // Make the current test naem available through testName.getMethodName() @Rule public TestName testName = new TestName(); public static final String ATTR_DEBUG_SERVER_NAME = TestsPlugin.PLUGIN_ID + ".DEBUG_SERVER_NAME"; private static final String DEFAULT_TEST_APP = "data/launch/bin/GDBMIGenericTestApp"; private static GdbLaunch fLaunch; // The set of attributes used for the launch // These are seset to their default whenever a new class // of tests is started. private static Map<String, Object> launchAttributes; // A set of global launch attributes which are not // reset when we load a new class of tests. // This allows a Suite to set an attribute // The suite is reponsible for clearing those attributes // once it is finished private static Map<String, Object> globalLaunchAttributes = new HashMap<String, Object>(); private static Process gdbserverProc; /** The MI event associated with the breakpoint at main() */ private MIStoppedEvent fInitialStoppedEvent; /** Flag we set to true when the target has reached the breakpoint at main() */ private boolean fTargetSuspended; /** Event semaphore we set when the target has reached the breakpoint at main() */ final private String fTargetSuspendedSem = new String(); // just used as a semaphore public GdbLaunch getGDBLaunch() { return fLaunch; } public static void setLaunchAttribute(String key, Object value) { launchAttributes.put(key, value); } public static void setGlobalLaunchAttribute(String key, Object value) { globalLaunchAttributes.put(key, value); } public static void removeGlobalLaunchAttribute(String key) { globalLaunchAttributes.remove(key); } public synchronized MIStoppedEvent getInitialStoppedEvent() { return fInitialStoppedEvent; } /** * We listen for the target to stop at the main breakpoint. This listener is * installed when the session is created and we uninstall ourselves when we * get to the breakpoint state, as we have no further need to monitor events * beyond that point. */ protected class SessionEventListener { private DsfSession fSession; SessionEventListener(DsfSession session) { fSession = session; Assert.assertNotNull(session); } @DsfServiceEventHandler public void eventDispatched(IDMEvent<?> event) { if (event instanceof MIStoppedEvent) { // We get this low-level event first. Record the MI event; various // tests use it for context synchronized(this) { fInitialStoppedEvent = (MIStoppedEvent)event; } } else if (event instanceof ISuspendedDMEvent) { // We get this higher level event shortly thereafter. We don't want // to consider the session suspended until we get it. Set the event // semaphore that will allow the test to proceed synchronized (fTargetSuspendedSem) { fTargetSuspended = true; fTargetSuspendedSem.notify(); } // no further need for this listener. Note fLaunch could be null if the test ran into a failure fSession.removeServiceEventListener(this); } } } @BeforeClass public static void baseBeforeClassMethod() { // Clear all launch attributes before starting a new class of tests launchAttributes = new HashMap<String, Object>(); // Setup information for the launcher launchAttributes.put(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, DEFAULT_TEST_APP); launchAttributes.put(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, true); launchAttributes.put(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN_SYMBOL, ICDTLaunchConfigurationConstants.DEBUGGER_STOP_AT_MAIN_SYMBOL_DEFAULT); launchAttributes.put(IGDBLaunchConfigurationConstants.ATTR_GDB_INIT, ".gdbinit"); if (launchAttributes.get(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE) == null) { launchAttributes.put(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN ); } // Set these up in case we will be running Remote tests. They will be ignored if we don't launchAttributes.put(ATTR_DEBUG_SERVER_NAME, "gdbserver"); launchAttributes.put(IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP, true); launchAttributes.put(IGDBLaunchConfigurationConstants.ATTR_HOST, "localhost"); launchAttributes.put(IGDBLaunchConfigurationConstants.ATTR_PORT, "9999"); // Set the global launch attributes launchAttributes.putAll(globalLaunchAttributes); } @Before public void baseBeforeMethod() throws Exception { System.out.println("===================================================================================================="); System.out.println("Running test: " + testName.getMethodName() + " using GDB: " + launchAttributes.get(IGDBLaunchConfigurationConstants.ATTR_DEBUG_NAME)); System.out.println("===================================================================================================="); boolean postMortemLaunch = launchAttributes.get(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE) .equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_CORE); // First check if we should launch gdbserver in the case of a remote session launchGdbServer(); ILaunchManager launchMgr = DebugPlugin.getDefault().getLaunchManager(); ILaunchConfigurationType lcType = launchMgr.getLaunchConfigurationType("org.eclipse.cdt.tests.dsf.gdb.TestLaunch"); assert lcType != null; ILaunchConfigurationWorkingCopy lcWorkingCopy = lcType.newInstance( null, launchMgr.generateLaunchConfigurationName("Test Launch")); //$NON-NLS-1$ assert lcWorkingCopy != null; lcWorkingCopy.setAttributes(launchAttributes); final ILaunchConfiguration lc = lcWorkingCopy.doSave(); // Register ourselves as a listener for the new session so that we can // register ourselves with that particular session before any events // occur. We want to find out when the break on main() occurs. SessionStartedListener sessionStartedListener = new SessionStartedListener() { public void sessionStarted(DsfSession session) { session.addServiceEventListener(new SessionEventListener(session), null); } }; // Launch the debug session. The session-started listener will be called // before the launch() call returns (unless, of course, there was a // problem launching and no session is created). DsfSession.addSessionStartedListener(sessionStartedListener); fLaunch = (GdbLaunch)lc.launch(ILaunchManager.DEBUG_MODE, new NullProgressMonitor()); DsfSession.removeSessionStartedListener(sessionStartedListener); // If we haven't hit main() yet, // wait for the program to hit the breakpoint at main() before // proceeding. All tests assume that stable initial state. Two // seconds is plenty; we typically get to that state in a few // hundred milliseconds with the tiny test programs we use. if (!postMortemLaunch && !fTargetSuspended) { synchronized (fTargetSuspendedSem) { fTargetSuspendedSem.wait(TestsPlugin.massageTimeout(2000)); Assert.assertTrue(fTargetSuspended); } } // This should be a given if the above check passes if (!postMortemLaunch) { synchronized(this) { Assert.assertNotNull(fInitialStoppedEvent); } } // If we started a gdbserver add it to the launch to make sure it is killed at the end if (gdbserverProc != null) { DebugPlugin.newProcess(fLaunch, gdbserverProc, "gdbserver"); } // Now initialize our SyncUtility, since we have the launcher SyncUtil.initialize(fLaunch.getSession()); } @After public void baseAfterMethod() throws Exception { if (fLaunch != null) { fLaunch.terminate(); fLaunch = null; } } @AfterClass public static void baseAfterClassMehod() throws Exception { } /** * This method start gdbserver on the localhost. * If the user specified a different host, things won't work. */ private static void launchGdbServer() { if (launchAttributes.get(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE) .equals(IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE)) { if (launchAttributes.get(IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP).equals(Boolean.TRUE)) { String server = (String)launchAttributes.get(ATTR_DEBUG_SERVER_NAME); String port = (String)launchAttributes.get(IGDBLaunchConfigurationConstants.ATTR_PORT); String program = (String)launchAttributes.get(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME); String commandLine = server + " :" + port + " " + program; try { System.out.println("Staring gdbserver with command: " + commandLine); gdbserverProc = ProcessFactory.getFactory().exec(commandLine); Reader r = new InputStreamReader(gdbserverProc.getErrorStream()); BufferedReader reader = new BufferedReader(r); String line; while ((line = reader.readLine()) != null) { System.out.println(line); line = line.trim(); if (line.startsWith("Listening on port")) { break; } } } catch (Exception e) { System.out.println("Error while launching command: " + commandLine); e.printStackTrace(); assert false; } } } } /** * Sets the name of the gdb and gdbserver programs into the launch * configuration used by the test class. * * <p> * Leaf subclasses are specific to a particular version of GDB and must call * this from their "@BeforeClass" static method so that we end up invoking * the appropriate gdb. * * @param version * string that contains the major and minor version number, e.g., * "6.8" */ protected static void setGdbProgramNamesLaunchAttributes(String version) { // See bugzilla 303811 for why we have to append ".exe" on Windows boolean isWindows = Platform.getOS().equals(Platform.OS_WIN32); BaseTestCase.setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUG_NAME, "gdb." + version + (isWindows ? ".exe" : "")); BaseTestCase.setLaunchAttribute(ATTR_DEBUG_SERVER_NAME, "gdbserver." + version + (isWindows ? ".exe" : "")); } }