/******************************************************************************* * Copyright (c) 2016, 2017 SSI Schaefer IT Solutions GmbH 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: * SSI Schaefer IT Solutions GmbH *******************************************************************************/ package org.eclipse.debug.tests.launching; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchListener; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.IStreamListener; import org.eclipse.debug.core.model.IDisconnect; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStreamMonitor; import org.eclipse.debug.core.model.IStreamsProxy; import org.eclipse.debug.internal.core.groups.GroupLaunchConfigurationDelegate; import org.eclipse.debug.internal.core.groups.GroupLaunchElement; import org.eclipse.debug.internal.core.groups.GroupLaunchElement.GroupElementPostLaunchAction; import org.eclipse.debug.internal.ui.launchConfigurations.LaunchHistory; import org.eclipse.debug.ui.IDebugUIConstants; public class LaunchGroupTests extends AbstractLaunchTest { private static final String GROUP_TYPE = "org.eclipse.debug.core.groups.GroupLaunchConfigurationType"; //$NON-NLS-1$ private static final String DEF_GRP_NAME = "Test Group"; //$NON-NLS-1$ private final AtomicInteger launchCount = new AtomicInteger(0); private ILaunchConfiguration lcToCount = null; private ILaunchListener lcListener = new ILaunchListener() { @Override public void launchRemoved(ILaunch launch) { } @Override public void launchChanged(ILaunch launch) { } @Override public void launchAdded(ILaunch launch) { if (launch.getLaunchConfiguration().contentsEqual(lcToCount)) { launchCount.incrementAndGet(); } } }; public LaunchGroupTests() { super("Launch Groups Test"); //$NON-NLS-1$ } @Override protected void setUp() throws Exception { super.setUp(); // reset count launchCount.set(0); } @Override protected void tearDown() throws Exception { // make sure listener is removed getLaunchManager().removeLaunchListener(lcListener); super.tearDown(); } private ILaunchConfiguration createLaunchGroup(String name, GroupLaunchElement... children) throws CoreException { ILaunchConfigurationWorkingCopy grp = getLaunchManager().getLaunchConfigurationType(GROUP_TYPE).newInstance(null, name); GroupLaunchConfigurationDelegate.storeLaunchElements(grp, Arrays.asList(children)); return grp.doSave(); } private GroupLaunchElement createLaunchGroupElement(ILaunchConfiguration source, GroupElementPostLaunchAction action, Object param, boolean adopt) { GroupLaunchElement e = new GroupLaunchElement(); e.name = source.getName(); e.data = source; e.action = action; e.actionParam = param; e.mode = GroupLaunchElement.MODE_INHERIT; e.enabled = true; e.adoptIfRunning = adopt; return e; } private LaunchHistory getRunLaunchHistory() { LaunchHistory h = getLaunchConfigurationManager().getLaunchHistory(IDebugUIConstants.ID_RUN_LAUNCH_GROUP); // clear the history for (ILaunchConfiguration c : h.getHistory()) { h.removeFromHistory(c); } return h; } public void testNone() throws Exception { ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ ILaunchConfiguration t2 = getLaunchConfiguration("Test2"); //$NON-NLS-1$ ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.NONE, null, false), createLaunchGroupElement(t2, GroupElementPostLaunchAction.NONE, null, false)); // attention: need to do this before launching! LaunchHistory runHistory = getRunLaunchHistory(); grp.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor()); ILaunchConfiguration[] history = runHistory.getHistory(); assertEquals(3, history.length); assertTrue("history[0] should be Test Group", history[0].contentsEqual(grp)); //$NON-NLS-1$ assertTrue("history[1] should be Test2", history[1].contentsEqual(t2)); //$NON-NLS-1$ assertTrue("history[2] should be Test1", history[2].contentsEqual(t1)); //$NON-NLS-1$ } public void testDelay() throws Exception { ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ ILaunchConfiguration t2 = getLaunchConfiguration("Test2"); //$NON-NLS-1$ ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.DELAY, 2, false), createLaunchGroupElement(t2, GroupElementPostLaunchAction.NONE, null, false)); long start = System.currentTimeMillis(); // attention: need to do this before launching! LaunchHistory runHistory = getRunLaunchHistory(); grp.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor()); assertTrue("delay was not awaited", (System.currentTimeMillis() - start) >= 2000); //$NON-NLS-1$ ILaunchConfiguration[] history = runHistory.getHistory(); assertEquals(3, history.length); assertTrue("history[0] should be Test Group", history[0].contentsEqual(grp)); //$NON-NLS-1$ assertTrue("history[1] should be Test2", history[1].contentsEqual(t2)); //$NON-NLS-1$ assertTrue("history[2] should be Test1", history[2].contentsEqual(t1)); //$NON-NLS-1$ } public void testTerminated() throws Exception { final ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ final ILaunchConfiguration t2 = getLaunchConfiguration("Test2"); //$NON-NLS-1$ ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.WAIT_FOR_TERMINATION, null, false), createLaunchGroupElement(t2, GroupElementPostLaunchAction.NONE, null, false)); long start = System.currentTimeMillis(); new Thread("Terminate Test1") { //$NON-NLS-1$ @Override public void run() { try { // wait for some time Thread.sleep(2000); // now find and nuke Test1 for (ILaunch l : getLaunchManager().getLaunches()) { if (l.getLaunchConfiguration().contentsEqual(t1)) { // add a dummy process, otherwise the launch never // terminates... attachDummyProcess(l); l.terminate(); } } } catch (Exception e) { // uh oh e.printStackTrace(); } } }.start(); // attention: need to do this before launching! LaunchHistory runHistory = getRunLaunchHistory(); grp.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor()); assertTrue("returned before termination of Test1", (System.currentTimeMillis() - start) >= 2000); //$NON-NLS-1$ // is there a way to assert that the group waited for test1 to // terminate? don't think so - at least run the code path to have it // covered. ILaunchConfiguration[] history = runHistory.getHistory(); assertEquals(3, history.length); assertTrue("history[0] should be Test Group", history[0].contentsEqual(grp)); //$NON-NLS-1$ assertTrue("history[1] should be Test2", history[1].contentsEqual(t2)); //$NON-NLS-1$ assertTrue("history[2] should be Test1", history[2].contentsEqual(t1)); //$NON-NLS-1$ } public void testAdopt() throws Exception { final ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ final ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.NONE, null, false), createLaunchGroupElement(t1, GroupElementPostLaunchAction.NONE, null, true)); // attention: need to do this before launching! LaunchHistory runHistory = getRunLaunchHistory(); lcToCount = t1; getLaunchManager().addLaunchListener(lcListener); grp.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor()); ILaunchConfiguration[] history = runHistory.getHistory(); assertEquals(2, history.length); assertTrue("history[0] should be Test Group", history[0].contentsEqual(grp)); //$NON-NLS-1$ assertTrue("history[1] should be Test1", history[1].contentsEqual(t1)); //$NON-NLS-1$ assertEquals("Test1 should be launched only once", 1, launchCount.get()); //$NON-NLS-1$ } public void testAdoptComplex() throws Exception { final ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ // Group 1 has Test1 (adopt = false) // Group 2 has Group 1 // Group 3 has Group 2 and Test1 (adopt = true) final ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.NONE, null, false)); final ILaunchConfiguration grp2 = createLaunchGroup("Group 2", createLaunchGroupElement(grp, GroupElementPostLaunchAction.NONE, null, false)); //$NON-NLS-1$ final ILaunchConfiguration grp3 = createLaunchGroup("Group 3", createLaunchGroupElement(grp2, GroupElementPostLaunchAction.NONE, null, false), createLaunchGroupElement(t1, GroupElementPostLaunchAction.NONE, null, true)); //$NON-NLS-1$ // attention: need to do this before launching! LaunchHistory runHistory = getRunLaunchHistory(); lcToCount = t1; getLaunchManager().addLaunchListener(lcListener); grp3.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor()); ILaunchConfiguration[] history = runHistory.getHistory(); assertEquals(4, history.length); assertTrue("history[0] should be Group 3", history[0].contentsEqual(grp3)); //$NON-NLS-1$ assertTrue("history[1] should be Group 2", history[1].contentsEqual(grp2)); //$NON-NLS-1$ assertTrue("history[2] should be Group 1", history[2].contentsEqual(grp)); //$NON-NLS-1$ assertTrue("history[3] should be Test1", history[3].contentsEqual(t1)); //$NON-NLS-1$ assertEquals("Test1 should be launched only once", 1, launchCount.get()); //$NON-NLS-1$ } public void testWaitForOutput() throws Exception { String testOutput = "TestOutput"; //$NON-NLS-1$ final ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ ILaunchConfiguration t2 = getLaunchConfiguration("Test2"); //$NON-NLS-1$ ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.OUTPUT_REGEXP, testOutput, false), createLaunchGroupElement(t2, GroupElementPostLaunchAction.NONE, null, false)); // attach a dummy process to the launch once it is launched. final DummyAttachListener attachListener = new DummyAttachListener(t1); getLaunchManager().addLaunchListener(attachListener); final AtomicBoolean finished = new AtomicBoolean(); long start = System.currentTimeMillis(); // start a thread that will produce output on the dummy process after // some time new Thread("Output Producer") { //$NON-NLS-1$ @Override public void run() { try { // wait some time before causing the group to continue Thread.sleep(2000); attachListener.getStream().write("TestOutput"); //$NON-NLS-1$ finished.set(true); } catch (Exception e) { e.printStackTrace(); } } }.start(); // attention: need to do this before launching! LaunchHistory runHistory = getRunLaunchHistory(); // launching the group must block until the output is produced grp.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor()); getLaunchManager().removeLaunchListener(attachListener); assertTrue("thread did not finish", finished.get()); //$NON-NLS-1$ assertTrue("output was not awaited", (System.currentTimeMillis() - start) >= 2000); //$NON-NLS-1$ ILaunchConfiguration[] history = runHistory.getHistory(); assertEquals(3, history.length); assertTrue("history[0] should be Test Group", history[0].contentsEqual(grp)); //$NON-NLS-1$ assertTrue("history[1] should be Test2", history[1].contentsEqual(t2)); //$NON-NLS-1$ assertTrue("history[2] should be Test1", history[2].contentsEqual(t1)); //$NON-NLS-1$ } public void testRename() throws Exception { ILaunchConfiguration t1 = getLaunchConfiguration("Test1"); //$NON-NLS-1$ ILaunchConfiguration t2 = getLaunchConfiguration("Test2"); //$NON-NLS-1$ ILaunchConfiguration grp = createLaunchGroup(DEF_GRP_NAME, createLaunchGroupElement(t1, GroupElementPostLaunchAction.NONE, null, false), createLaunchGroupElement(t2, GroupElementPostLaunchAction.NONE, null, false)); ILaunchConfigurationWorkingCopy workingCopy = t1.getWorkingCopy(); workingCopy.rename("AnotherTest"); //$NON-NLS-1$ workingCopy.doSave(); assertTrue("name should not be transiently updated", grp.getName().equals(DEF_GRP_NAME)); //$NON-NLS-1$ // need to re-fetch configuration grp = getLaunchConfiguration(DEF_GRP_NAME); List<GroupLaunchElement> elements = GroupLaunchConfigurationDelegate.createLaunchElements(grp); assertTrue("group element should be updated", elements.get(0).name.equals("AnotherTest")); //$NON-NLS-1$//$NON-NLS-2$ } private static DummyStream attachDummyProcess(final ILaunch l) { final DummyStream dummy = new DummyStream(); final InvocationHandler streamProxyHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if (name.equals("getOutputStreamMonitor")) { //$NON-NLS-1$ return dummy; } return null; } }; final InvocationHandler handler = new InvocationHandler() { boolean terminated = false; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if (name.equals("equals")) { //$NON-NLS-1$ return args.length == 1 && proxy == args[0]; } if (name.equals("getStreamsProxy")) { //$NON-NLS-1$ return Proxy.newProxyInstance(LaunchGroupTests.class.getClassLoader(), new Class[] { IStreamsProxy.class }, streamProxyHandler); } if (name.equals("getLaunch")) { //$NON-NLS-1$ return l; } if (name.equals("getLabel")) { //$NON-NLS-1$ return l.getLaunchConfiguration().getName(); } if (name.equals("getAttribute")) { //$NON-NLS-1$ return null; } if (name.equals("isTerminated")) { //$NON-NLS-1$ return terminated; } if (name.equals("terminate")) { //$NON-NLS-1$ terminated = true; } if (name.equals("getAdapter")) { //$NON-NLS-1$ return null; } if (name.equals("hashCode")) { //$NON-NLS-1$ return Integer.valueOf(0); } return Boolean.TRUE; } }; IProcess process = (IProcess) Proxy.newProxyInstance(LaunchGroupTests.class.getClassLoader(), new Class[] { IProcess.class, IDisconnect.class }, handler); l.addProcess(process); return dummy; } private static final class DummyStream implements IStreamMonitor { private final List<IStreamListener> listeners = new ArrayList<>(); @Override public void addListener(IStreamListener listener) { listeners.add(listener); } @Override public String getContents() { return null; } @Override public void removeListener(IStreamListener listener) { listeners.remove(listener); } public void write(String s) { for (IStreamListener l : listeners) { l.streamAppended(s, null); } } } private static final class DummyAttachListener implements ILaunchListener { private ILaunchConfiguration cfg; private DummyStream stream; public DummyAttachListener(ILaunchConfiguration cfg) { this.cfg = cfg; } public DummyStream getStream() { return stream; } @Override public void launchRemoved(ILaunch launch) { } @Override public void launchAdded(ILaunch launch) { if (launch.getLaunchConfiguration().equals(cfg)) { stream = attachDummyProcess(launch); } } @Override public void launchChanged(ILaunch launch) { } } }