/*
* Copyright 2010 The Rabbit Eclipse Plug-in Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 rabbit.tracking.internal.trackers;
import rabbit.data.store.model.LaunchEvent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.ILaunch;
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.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.ISuspendResume;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
import org.eclipse.jdt.ui.wizards.JavaCapabilityConfigurationPage;
import org.joda.time.Interval;
import org.junit.BeforeClass;
import org.junit.Test;
import static java.lang.String.format;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @see LaunchTracker
*/
public class LaunchTrackerTest extends AbstractTrackerTest<LaunchEvent> {
/**
* Listener to help testing.
*/
private static class MyDebugListener implements IDebugEventSetListener {
private Map<String, Long> creationTimes = new HashMap<String, Long>();
private Map<String, Long> terminationTimes = new HashMap<String, Long>();
private Map<String, ILaunch> launches = new HashMap<String, ILaunch>();
@Override
public void handleDebugEvents(DebugEvent[] events) {
for (DebugEvent e : events) {
if (e.getSource() instanceof IProcess) {
IProcess process = (IProcess) e.getSource();
ILaunch launch = process.getLaunch();
String configName = launch.getLaunchConfiguration().getName();
launches.put(configName, launch);
if (e.getKind() == DebugEvent.CREATE) {
creationTimes.put(configName, System.currentTimeMillis());
} else if (e.getKind() == DebugEvent.TERMINATE) {
terminationTimes.put(configName, System.currentTimeMillis());
}
}
}
}
/**
* Gets the launch for the given launch configuration.
*
* @param launchConfigName The name of the launch configuration.
* @return The launch, or null.
*/
ILaunch getLaunch(String launchConfigName) {
return launches.get(launchConfigName);
}
/**
* Gets the process creation time for the given launch configuration.
*
* @param launchConfigName The name of the launch configuration.
* @return The process create time, or null.
*/
long getProcessCreationTimeMillis(String launchConfigName) {
return creationTimes.get(launchConfigName);
}
/**
* Gets the process termination time for the given launch configuration.
*
* @param launchConfigName The name of the launch configuration.
* @return The process termination time, or null.
*/
long getProcessTerminationTimeMillis(String launchConfigName) {
return terminationTimes.get(launchConfigName);
}
}
private static IPackageFragment pkg;
@BeforeClass
public static void beforeClass() throws Exception {
new OpenJavaPerspectiveAction().run();
// Create a new Java project:
IProject proj = ResourcesPlugin.getWorkspace().getRoot().getProject("P");
JavaCapabilityConfigurationPage.createProject(proj, (URI) null, null);
IJavaProject javaProj = JavaCore.create(proj);
JavaCapabilityConfigurationPage page = new JavaCapabilityConfigurationPage();
page.init(javaProj, null, null, true);
page.configureJavaProject(null);
// Create a package:
IPackageFragmentRoot src = javaProj.getPackageFragmentRoots()[0];
pkg = src.createPackageFragment("pkg", true, null);
}
@Test
public void testDisabled() throws Exception {
// Create a new Java class:
String className = "TestDisabled";
StringBuilder content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className));
content
.append(format(" public static void main(String[] args) throws Exception {%n"));
content.append(format(" Thread.sleep(20);%n"));
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit = pkg.createCompilationUnit(className + ".java",
content.toString(), true, null);
tracker.setEnabled(false);
launch("TestDisabled", pkg.getJavaProject().getElementName(),
unit.getTypes()[0].getFullyQualifiedName(), ILaunchManager.DEBUG_MODE);
assertTrue(tracker.getData().isEmpty());
}
/*
* Tests the launching in normal mode ("run");
*/
@Test
public void testEnabled() throws CoreException, IOException,
InterruptedException {
// Create a new Java class:
String className = "TestEnabled";
StringBuilder content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className));
content
.append(format(" public static void main(String[] args) throws Exception {%n"));
content.append(format(" Thread.sleep(100);%n")); // Sleep 100 millis.
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit = pkg.createCompilationUnit(className + ".java",
content.toString(), true, null);
MyDebugListener listener = new MyDebugListener();
DebugPlugin.getDefault().addDebugEventListener(listener);
tracker.setEnabled(true);
// Launch the application:
String configName = "TestEnabled";
ILaunchConfiguration config = launch(configName,
pkg.getJavaProject().getElementName(),
unit.getTypes()[0].getFullyQualifiedName(), ILaunchManager.RUN_MODE);
ILaunch launch = null;
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
lock.lock();
try {
// Wait for listener to be notified:
while (listener.getLaunch(configName) == null) {
condition.await(100, TimeUnit.MILLISECONDS);
}
launch = listener.getLaunch(configName);
// Wait for launch to terminate:
while (!launch.isTerminated()) {
condition.await(100, TimeUnit.MILLISECONDS);
}
// Give the tracker a bit more time to finish:
condition.await(100, TimeUnit.MILLISECONDS);
} finally {
lock.unlock();
}
tracker.setEnabled(false);
// Check the result:
assertEquals(1, tracker.getData().size());
LaunchEvent event = tracker.getData().iterator().next();
assertTrue(event.getFilePaths().isEmpty());
assertEquals(listener.getLaunch(configName), event.getLaunch());
assertEquals(config, event.getLaunchConfiguration());
long preStart = listener.getProcessCreationTimeMillis(configName) - 10;
long start = event.getInterval().getStartMillis();
long postStart = listener.getProcessCreationTimeMillis(configName) + 10;
long preEnd = listener.getProcessTerminationTimeMillis(configName) - 10;
long end = event.getInterval().getEndMillis();
long postEnd = listener.getProcessTerminationTimeMillis(configName) + 10;
checkTime(preStart, start, postStart, preEnd, end, postEnd);
}
@Test
public void testEnabled_multipleLaunches() throws Exception {
String className1 = "TestEnabled_MultipleLaunches1";
StringBuilder content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className1));
content
.append(format(" public static void main(String[] args) throws Exception {%n"));
content.append(format(" Thread.sleep(100);%n")); // Sleep 100 millis.
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit1 = pkg.createCompilationUnit(className1 + ".java",
content.toString(), true, null);
String className2 = "TestEnabled_MultipleLaunches2";
content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className2));
content
.append(format(" public static void main(String[] args) throws Exception {%n"));
content.append(format(" Thread.sleep(200);%n")); // Sleep 200 millis.
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit2 = pkg.createCompilationUnit(className2 + ".java",
content.toString(), true, null);
MyDebugListener listener = new MyDebugListener();
DebugPlugin.getDefault().addDebugEventListener(listener);
tracker.setEnabled(true);
// Launch two processes at the same time, running side by side:
String configName1 = className1;
ILaunchConfiguration config1 = launch(configName1,
pkg.getJavaProject().getElementName(),
unit1.getTypes()[0].getFullyQualifiedName(), ILaunchManager.RUN_MODE);
String configName2 = className2;
ILaunchConfiguration config2 = launch(configName2,
pkg.getJavaProject().getElementName(),
unit2.getTypes()[0].getFullyQualifiedName(), ILaunchManager.RUN_MODE);
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
lock.lock();
try {
while (listener.getLaunch(configName1) == null
|| listener.getLaunch(configName2) == null) {
condition.await(100, TimeUnit.MILLISECONDS);
}
while (!listener.getLaunch(configName1).isTerminated()
|| !listener.getLaunch(configName2).isTerminated()) {
condition.await(100, TimeUnit.MILLISECONDS);
}
condition.await(100, TimeUnit.MILLISECONDS);
} finally {
lock.unlock();
}
tracker.setEnabled(false);
assertEquals(2, tracker.getData().size());
LaunchEvent event1 = null;
LaunchEvent event2 = null;
Iterator<LaunchEvent> it = tracker.getData().iterator();
event1 = it.next();
event2 = it.next();
if (event1.getLaunch().getLaunchConfiguration().getName().equals(config2)) {
LaunchEvent tmp = event1;
event1 = event2;
event2 = tmp;
}
assertTrue(event1.getFilePaths().isEmpty());
assertEquals(listener.getLaunch(configName1), event1.getLaunch());
assertEquals(config1, event1.getLaunchConfiguration());
long preStart = listener.getProcessCreationTimeMillis(configName1) - 10;
long start = event1.getInterval().getStartMillis();
long postStart = listener.getProcessCreationTimeMillis(configName1) + 10;
long preEnd = listener.getProcessTerminationTimeMillis(configName1) - 10;
long end = event1.getInterval().getEndMillis();
long postEnd = listener.getProcessTerminationTimeMillis(configName1) + 10;
checkTime(preStart, start, postStart, preEnd, end, postEnd);
assertTrue(event2.getFilePaths().isEmpty());
assertEquals(listener.getLaunch(configName2), event2.getLaunch());
assertEquals(config2, event2.getLaunchConfiguration());
preStart = listener.getProcessCreationTimeMillis(configName2) - 10;
start = event2.getInterval().getStartMillis();
postStart = listener.getProcessCreationTimeMillis(configName2) + 10;
preEnd = listener.getProcessTerminationTimeMillis(configName2) - 10;
end = event2.getInterval().getEndMillis();
postEnd = listener.getProcessTerminationTimeMillis(configName2) + 10;
checkTime(preStart, start, postStart, preEnd, end, postEnd);
}
@Test
public void testEnabled_withBreakpoint() throws Exception {
String className = "TestEnabledWithBreakpoint";
StringBuilder content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className));
content.append(format(" public static void main(String[] args) {%n"));
content.append(format(" System.out.println();%n")); // This is line 4
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit = pkg.createCompilationUnit(className + ".java",
content.toString(), true, null);
// Create a breakpoint at line 4:
JDIDebugModel.createLineBreakpoint(unit.getResource(),
unit.getType(className).getFullyQualifiedName(), 4, -1, -1, 0, true,
null);
final ISuspendResume[] suspendResume = new ISuspendResume[1];
MyDebugListener listener = new MyDebugListener() {
@Override
public void handleDebugEvents(DebugEvent[] events) {
super.handleDebugEvents(events);
for (DebugEvent e : events) {
if (e.getKind() == DebugEvent.SUSPEND) {
synchronized (suspendResume) {
suspendResume[0] = (ISuspendResume) e.getSource();
suspendResume.notifyAll();
}
}
}
}
};
DebugPlugin.getDefault().addDebugEventListener(listener);
// Launch in debug mode:
tracker.setEnabled(true);
ILaunchConfiguration config = launch(className,
pkg.getJavaProject().getElementName(),
unit.getTypes()[0].getFullyQualifiedName(), ILaunchManager.DEBUG_MODE);
synchronized (suspendResume) {
while (suspendResume[0] == null) {
try {
suspendResume.wait();
} catch (InterruptedException e) {
// Just keep looping..
}
}
}
suspendResume[0].resume();
Thread.sleep(500);
tracker.setEnabled(false);
assertEquals(1, tracker.getData().size());
LaunchEvent event = tracker.getData().iterator().next();
assertEquals(listener.getLaunch(className), event.getLaunch());
assertEquals(config, event.getLaunchConfiguration());
assertEquals(1, event.getFilePaths().size());
assertTrue(event.getFilePaths().contains(unit.getResource().getFullPath()));
long preStart = listener.getProcessCreationTimeMillis(className) - 10;
long start = event.getInterval().getStartMillis();
long postStart = listener.getProcessCreationTimeMillis(className) + 10;
long preEnd = listener.getProcessTerminationTimeMillis(className) - 10;
long end = event.getInterval().getEndMillis();
long postEnd = listener.getProcessTerminationTimeMillis(className) + 10;
checkTime(preStart, start, postStart, preEnd, end, postEnd);
}
@Test
public void testEnabled_withBreakpointsAndFiles() throws Exception {
String className1 = "TestEnabledWithBreakpointsAndFiles1";
StringBuilder content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className1));
content.append(format(" public %s() {%n", className1));
content.append(format(" System.out.println();%n")); // This is line 4
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit1 = pkg.createCompilationUnit(className1 + ".java",
content.toString(), true, null);
// Create a breakpoint at line 4 in the above file:
JDIDebugModel.createLineBreakpoint(unit1.getResource(),
unit1.getType(className1).getFullyQualifiedName(), 4, -1, -1, 0, true,
null);
String className2 = "TestEnabledWithBreakpointsAndFiles2";
content = new StringBuilder();
content.append(format("package %s;%n", pkg.getElementName()));
content.append(format("public class %s {%n", className2));
content.append(format(" public static void main(String[] args) {%n"));
content.append(format(" new %s();%n", className1)); // This is line 4
content.append(format(" }%n"));
content.append(format("}"));
ICompilationUnit unit2 = pkg.createCompilationUnit(className2 + ".java",
content.toString(), true, null);
// Create a breakpoint at line 4 in the above file:
JDIDebugModel.createLineBreakpoint(unit2.getResource(),
unit2.getType(className2).getFullyQualifiedName(), 4, -1, -1, 0, true,
null);
final ISuspendResume[] suspendResume = new ISuspendResume[1];
MyDebugListener listener = new MyDebugListener() {
@Override
public void handleDebugEvents(DebugEvent[] events) {
super.handleDebugEvents(events);
for (DebugEvent e : events) {
if (e.getKind() == DebugEvent.SUSPEND) {
suspendResume[0] = (ISuspendResume) e.getSource();
}
}
}
};
DebugPlugin.getDefault().addDebugEventListener(listener);
// Launch in debug mode:
tracker.setEnabled(true);
launch("TestEnabledWithBreakpointsAndFiles",
pkg.getJavaProject().getElementName(),
unit2.getType(className2).getFullyQualifiedName(),
ILaunchManager.DEBUG_MODE);
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
lock.lock();
try {
// We had two breakpoints set, so we need to resume twice:
while (suspendResume[0] == null) {
condition.await(100, TimeUnit.MICROSECONDS);
}
suspendResume[0].resume();
suspendResume[0] = null;
condition.await(100, TimeUnit.MILLISECONDS);
while (suspendResume[0] == null) {
condition.await(100, TimeUnit.MILLISECONDS);
}
suspendResume[0].resume();
condition.await(500, TimeUnit.MILLISECONDS);
} finally {
lock.unlock();
}
assertEquals(1, tracker.getData().size());
LaunchEvent event = tracker.getData().iterator().next();
assertEquals(2, event.getFilePaths().size());
assertTrue(event.getFilePaths().contains(unit1.getResource().getFullPath()));
assertTrue(event.getFilePaths().contains(unit2.getResource().getFullPath()));
}
@Override
protected LaunchEvent createEvent() {
ILaunchConfigurationType type = mock(ILaunchConfigurationType.class);
given(type.getIdentifier()).willReturn("typeId");
ILaunchConfiguration config = mock(ILaunchConfiguration.class);
given(config.getName()).willReturn("name");
ILaunch launch = mock(ILaunch.class);
given(launch.getLaunchMode()).willReturn("run");
return new LaunchEvent(new Interval(0, 1), launch, config, type,
new HashSet<IPath>(Arrays.asList(new Path("/1"), new Path("/2"))));
}
@Override
protected LaunchTracker createTracker() {
return new LaunchTracker();
}
/**
* Launches a Java application.
*
* @param configName The name for the new launch configuration.
* @param projectName The name of the Java project.
* @param typeName The fully qualified name of the Java class.
* @param mode The launch mode
* @return The launch configuration launched.
* @see ILaunchManager#RUN_MODE
* @see ILaunchManager#DEBUG_MODE
*/
private ILaunchConfiguration launch(String configName, String projectName,
String typeName, String mode) throws CoreException {
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type = manager
.getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
ILaunchConfigurationWorkingCopy copy = type.newInstance(null, configName);
copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
projectName);
copy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
typeName);
ILaunchConfiguration config = copy.doSave();
config.launch(mode, null, true);
return config;
}
}