/*******************************************************************************
* Copyright (c) 2013, 2014 S.Boyko 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:
* S.Boyko - initial API and implementation
*******************************************************************************/
package org.eclipse.m2m.internal.tests.qvt.oml.debugger;
import java.io.File;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import junit.framework.TestCase;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
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.Launch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.m2m.internal.qvt.oml.common.io.FileUtil;
import org.eclipse.m2m.internal.qvt.oml.common.launch.IQvtLaunchConstants;
import org.eclipse.m2m.internal.qvt.oml.common.launch.TargetUriData;
import org.eclipse.m2m.internal.qvt.oml.common.launch.TargetUriData.TargetType;
import org.eclipse.m2m.internal.qvt.oml.debug.ui.RetargettableActionAdapterFactory;
import org.eclipse.m2m.internal.qvt.oml.editor.ui.QvtEditor;
import org.eclipse.m2m.internal.qvt.oml.emf.util.URIUtils;
import org.eclipse.m2m.internal.qvt.oml.emf.util.WorkspaceUtils;
import org.eclipse.m2m.internal.qvt.oml.project.builder.QVTOBuilder;
import org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegate;
import org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchUtil;
import org.eclipse.m2m.internal.tests.qvt.oml.debugger.DebugEventLogger.LogRecord;
import org.eclipse.m2m.internal.tests.qvt.oml.debugger.MarkedTransformation.LineMarker;
import org.eclipse.m2m.qvt.oml.debug.core.QVTOBreakpoint;
import org.eclipse.m2m.qvt.oml.debug.core.QVTODebugCore;
import org.eclipse.m2m.qvt.oml.debug.core.launch.QVTODebugConfiguration;
import org.eclipse.m2m.qvt.oml.debug.core.srclookup.QVTOSourceLookupDirector;
import org.eclipse.m2m.tests.qvt.oml.AllTests;
import org.eclipse.m2m.tests.qvt.oml.TestProject;
import org.eclipse.m2m.tests.qvt.oml.transform.ModelTestData;
import org.eclipse.m2m.tests.qvt.oml.util.ReaderInputStream;
import org.eclipse.m2m.tests.qvt.oml.util.TestUtil;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
/**
* A JUnit test class for QVTO Debugger execution
* @author sboyko
*/
public class TestQvtoDebugger extends TestCase {
public TestQvtoDebugger(ModelTestData data) {
super(data.getName());
}
public void setUp() throws Exception {
if (myProject != null) {
return;
}
Display.getCurrent().syncExec(new Runnable() {
public void run() {
DebugUIPlugin.getDefault().getPreferenceStore().putValue(IInternalDebugUIConstants.PREF_SWITCH_TO_PERSPECTIVE, MessageDialogWithToggle.NEVER);
DebugUIPlugin.getDefault().getPreferenceStore().putValue(IInternalDebugUIConstants.PREF_ACTIVATE_DEBUG_VIEW, Boolean.FALSE.toString());
DebugUIPlugin.getDefault().getPreferenceStore().putValue(IInternalDebugUIConstants.PREF_SWITCH_PERSPECTIVE_ON_SUSPEND, MessageDialogWithToggle.NEVER);
}
});
TestUtil.turnOffAutoBuilding();
myProject = new TestProject(PROJECT_NAME, new String[] {});
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type = manager.getLaunchConfigurationType(QvtLaunchConfigurationDelegate.LAUNCH_CONFIGURATION_TYPE_ID);
myLaunchConfigurationWorkingCopy = type.newInstance(null, CONFIG_NAME);
copyModelData(TestUtil.getPluginRelativeFile(AllTests.BUNDLE_ID + ".ui", TEST_DATA_FOLDER + "/tests")); //$NON-NLS-1$ //$NON-NLS-2$
}
public void tearDown() throws Exception {
IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (activeWorkbenchWindow != null) {
IWorkbenchPage activePage = activeWorkbenchWindow.getActivePage();
if (activePage != null) {
IEditorPart activeEditor = activePage.getActiveEditor();
if (activeEditor != null) {
activePage.closeEditor(activeEditor, false);
}
}
}
}
@Override
protected void runTest() throws Throwable {
doTest(getName());
}
/**
* Copies all files from a specified source project folder to a <i>debuggerTestData</i> folder
* of a temporary project
* @param srcFolder - project-relative
* @throws Exception
*/
private void copyModelData(File srcFolder) throws Exception {
File folder = new File(myProject.getProject().getLocation().toString() + "/" + TEST_DATA_FOLDER); //$NON-NLS-1$
folder.mkdirs();
FileUtil.copyFolder(srcFolder, folder);
myProject.getProject().refreshLocal(IResource.DEPTH_INFINITE, null);
}
/**
* Suspends current thread until the debugger listener resumes it
* @param time - maximum time to wait in milliseconds, 0 for no time-bound
* @throws InterruptedException
*/
public void waitForDebugger(int time) throws InterruptedException {
synchronized(this) {
if (time > 0) {
this.wait(time);
}
else {
this.wait();
}
};
}
/**
* Adds a breakpoint on the given line to the module currently selected in launch
* configuration
* @param line - line number to set a breakpoint on
* @throws CoreException
* @throws InterruptedException
* @throws OperationCanceledException
*/
private void addBreakpoint(QvtEditor qvtEditor, final int line) throws CoreException, OperationCanceledException, InterruptedException {
//IToggleBreakpointsTarget breakpointAdapter = (IToggleBreakpointsTarget) qvtEditor.getAdapter(IToggleBreakpointsTarget.class);
IToggleBreakpointsTarget breakpointAdapter = (IToggleBreakpointsTarget) new RetargettableActionAdapterFactory().getAdapter(qvtEditor, IToggleBreakpointsTarget.class);
breakpointAdapter.toggleLineBreakpoints(qvtEditor, new ITextSelection() {
public boolean isEmpty() {
return false;
}
public String getText() {
return null;
}
public int getStartLine() {
return line-1;
}
public int getOffset() {
return 0;
}
public int getLength() {
return 1;
}
public int getEndLine() {
return getStartLine();
}
});
IJobManager jobMan = Job.getJobManager();
jobMan.join(QVTOBreakpoint.QVTO_BREAKPOINT_JOBFAMILY, new NullProgressMonitor());
}
private String getProjectFileName(String folder, String file) {
return myProject.project.getName() + "/" + TEST_DATA_FOLDER + "/" + folder + "/" + file; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
private String getProjectFilePath(String folder, String file) {
return MessageFormat.format("{0}/{1}/{2}/{3}", new Object[] {myProject.project.getLocation(), TEST_DATA_FOLDER, folder, file}); //$NON-NLS-1$
}
/**
* @param folder - debuggerTestData-realtive folder name without a starting slash (/)
* structure of such a folder is:
* /markedTest.qvt - a transformation the debugger is being tested on with breakpoint markers
* markers are deleted before launching debugger
* /test.xml - an XML description of debugging scenario
* /in.ecore - input EMF ecore model
* @throws Exception
*/
private void setTestData(String folder) throws Exception {
// Parse transformation file for breakpoint labels
String markedName = getProjectFileName(folder, MARKED_TEST_FILE);
IFile markedFile = WorkspaceUtils.getWorkspaceFile(markedName);
String markedData = QVTOBuilder.getFileContents(markedFile);
myMarkedTrans = new MarkedTransformation(markedData);
// Creating a clean test file (without breakpoint markers)
String testName = getProjectFileName(folder, TEST_FILE);
IFile testFile = WorkspaceUtils.getWorkspaceFile(testName);
testFile.delete(false, null);
testFile.create(new ReaderInputStream(myMarkedTrans.getCleanText()), true, null);
// Set launch configuration parameters
myLaunchConfigurationWorkingCopy.setAttribute(IQvtLaunchConstants.MODULE, URIUtils.getResourceURI(testFile).toString());
QvtLaunchUtil.saveTargetUriData(myLaunchConfigurationWorkingCopy, new TargetUriData(TargetType.EXISTING_CONTAINER,
URI.createPlatformResourceURI(getProjectFileName(folder, IN_MODEL), true).toString(), null, false), 1);
myLaunchConfigurationWorkingCopy.setAttribute(IQvtLaunchConstants.ELEM_COUNT, 1);
myLaunchConfigurationWorkingCopy.setAttribute(IQvtLaunchConstants.TRACE_FILE, getProjectFileName(folder, TRACE_FILE));
myLaunchConfigurationWorkingCopy.doSave();
// Parse a script
myScript = new Script(getProjectFilePath(folder, SCRIPT_FILE), myMarkedTrans);
// Open script in editor
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
QvtEditor qvtEditor = (QvtEditor) IDE.openEditor(page, testFile);
// Force compilation in editor
qvtEditor.getValidCompiledModule(10000);
// Set up the specified breakpoints
for (final LineMarker rec : myMarkedTrans.getBreakpointLineMarkers()) {
addBreakpoint(qvtEditor, rec.lineNumber);
}
}
/**
* Launches the debugger with a configuration set up by {@link #setTestData(String)}
* @throws CoreException
* @throws InterruptedException
*/
private void runDebugger() throws CoreException, InterruptedException {
ILaunchConfiguration config = myLaunchConfigurationWorkingCopy.doSave();
QVTODebugConfiguration debugConfig = new QVTODebugConfiguration();
ILaunch launch = new Launch(config, ILaunchManager.DEBUG_MODE, new QVTOSourceLookupDirector());
myListener = new DebugEventLogger(this, myScript);
DebugPlugin.getDefault().addDebugEventListener(myListener);
debugConfig.launch(config, ILaunchManager.DEBUG_MODE, launch, new NullProgressMonitor());
while(!launch.isTerminated()) {
while (Display.getDefault().readAndDispatch()) {
}
Thread.sleep(10);
}
DebugPlugin.getDefault().removeDebugEventListener(myListener);
}
/**
* Loads test data and performs a test
* @throws CoreException
* @throws InterruptedException
* @see #setTestData(String)
*/
private void doTest(String folder) throws Exception {
System.out.println(MessageFormat.format(LOADING_TEST_MESSAGE, new Object[]{folder}));
setTestData(folder);
System.out.println(RUNNING_DEBUGGER_MESSAGE);
runDebugger();
System.out.println(MessageFormat.format(TEST_FINISHED_MESSAGE, new Object[]{folder}));
// Check up set breakpoints
Collection<LineMarker> allBreakpoints = myMarkedTrans.getBreakpointLineMarkers();
Set<Integer> physicalBreakpoints = getPhysicalBreakpoints();
for (LineMarker rec : allBreakpoints) {
assertTrue(
MessageFormat.format(rec.fail?BREAKPOINT_SET_MESSAGE:BREAKPOINT_NOT_SET_MESSAGE, new Object[]{rec, myLaunchConfigurationWorkingCopy.getAttribute(IQvtLaunchConstants.MODULE, "")}), //$NON-NLS-1$
(!physicalBreakpoints.contains(rec.lineNumber)) == rec.fail
) ;
}
// Check up debugger log
for (LogRecord rec : myListener.getLog()) {
System.out.println(rec.toString(myMarkedTrans));
assertTrue(MessageFormat.format(WRONG_LINE_NUMBER_MESSAGE, new Object[]{rec.getCommand(), rec.toString()}), rec.getLineNumber() == rec.getCommand().lineNumber);
assertTrue(MessageFormat.format(WRONG_CAUSE_MESSAGE,
new Object[]{rec.getCommand(), LogRecord.eventDetailToString(rec.getEventDetail())}), rec.getEventDetail() == rec.getCommand().getEventDetail());
}
assertFalse(
MessageFormat.format(SCRIPT_NOT_FINISHED_MESSAGE, new Object[]{getProjectFileName(folder, SCRIPT_FILE)}),
myScript.hasNextCommand()
);
}
private Set<Integer> getPhysicalBreakpoints() throws CoreException {
Set<Integer> physicalBreakpoints = new HashSet<Integer>();
IBreakpointManager manager = DebugPlugin.getDefault().getBreakpointManager();
IBreakpoint[] qvtBreakpoints = manager.getBreakpoints(QVTODebugCore.MODEL_ID);
for (int i = 0; i < qvtBreakpoints.length; i++) {
if (qvtBreakpoints[i] instanceof QVTOBreakpoint == false) {
continue;
}
QVTOBreakpoint breakpoint = (QVTOBreakpoint) qvtBreakpoints[i];
if (breakpoint.isEnabled()) {
physicalBreakpoints.add(breakpoint.getLineNumber());
}
}
return physicalBreakpoints;
}
private static TestProject myProject;
private static ILaunchConfigurationWorkingCopy myLaunchConfigurationWorkingCopy;
private DebugEventLogger myListener;
private Script myScript;
private MarkedTransformation myMarkedTrans;
private static final String PROJECT_NAME = "DebuggerTest"; //$NON-NLS-1$
private static final String CONFIG_NAME = "debugConfig"; //$NON-NLS-1$
private static final String TEST_DATA_FOLDER = "debuggerTestData"; //$NON-NLS-1$
private static final String MARKED_TEST_FILE = "markedTest.qvto"; //$NON-NLS-1$
private static final String TEST_FILE = "test.qvto"; //$NON-NLS-1$
private static final String IN_MODEL = "in.ecore"; //$NON-NLS-1$
private static final String TRACE_FILE = "in.ecore.trace"; //$NON-NLS-1$
private static final String SCRIPT_FILE = "test.xml"; //$NON-NLS-1$
private static final String LOADING_TEST_MESSAGE = "DebuggerTester: Loading test {0}"; //$NON-NLS-1$
private static final String RUNNING_DEBUGGER_MESSAGE = "DebuggerTester: Running debugger..."; //$NON-NLS-1$
private static final String TEST_FINISHED_MESSAGE = "DebuggerTester: Debugging finished: {0}"; //$NON-NLS-1$
private static final String BREAKPOINT_SET_MESSAGE = "Breakpoint set incorrectly: {0} in {1}"; //$NON-NLS-1$
private static final String BREAKPOINT_NOT_SET_MESSAGE = "Breakpoint not set incorrectly: {0} in {1}"; //$NON-NLS-1$
private static final String WRONG_LINE_NUMBER_MESSAGE = "Line numbers don''t correspond: {0} vs {1}."; //$NON-NLS-1$
private static final String WRONG_CAUSE_MESSAGE = "Stop causes don''t correspond: {0} vs \"{1}\"."; //$NON-NLS-1$
private static final String SCRIPT_NOT_FINISHED_MESSAGE = "Script not finished: {0}"; //$NON-NLS-1$
}