package com.redhat.ceylon.eclipse.ui.test;
import static org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable.asyncExec;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IBuildContext;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEclipseEditor;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor;
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
import org.eclipse.swtbot.swt.finder.utils.Position;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.utils.SWTUtils;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.IOverwriteQuery;
import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ImportOperation;
import org.junit.Assert;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.eclipse.code.editor.Navigation;
import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder;
import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.CeylonBuildHook;
public class Utils {
public static IProject importProject(final IWorkspace workspace,
final String destinationRootPath,
final IPath projectDescriptionPath) throws CoreException,
InvocationTargetException, InterruptedException {
final IProjectDescription originalProjectDescription =
workspace.loadProjectDescription(projectDescriptionPath);
return importProject(workspace, destinationRootPath,
originalProjectDescription);
}
public static void openInEditor(IProject project, String fileName) {
final IFile runFile = project.getFile(fileName);
openInEditor(runFile);
}
public static void openInEditor(final IFile runFile) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
try {
Navigation.gotoLocation(runFile, 0);
} catch (Exception ex) {
System.err.println(ex);
}
}
});
}
public static IProject importProject(final IWorkspace workspace,
final String destinationRootPath,
final IProjectDescription originalProjectDescription)
throws InvocationTargetException, InterruptedException {
final String projectName = originalProjectDescription.getName();
WorkspaceModifyOperation importOperation = new WorkspaceModifyOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException,
InvocationTargetException, InterruptedException {
final IProject project = workspace.getRoot().getProject(projectName);
// import from file system
File importSource = null;
// import project from location copying files - use default project
// location for this workspace
URI locationURI = originalProjectDescription.getLocationURI();
// if location is null, project already exists in this location or
// some error condition occured.
IProjectDescription newProjectDescription = originalProjectDescription;
if (locationURI != null) {
importSource = new File(locationURI);
newProjectDescription = workspace
.newProjectDescription(projectName);
newProjectDescription.setBuildSpec(originalProjectDescription.getBuildSpec());
newProjectDescription.setComment(originalProjectDescription.getComment());
newProjectDescription.setDynamicReferences(originalProjectDescription
.getDynamicReferences());
newProjectDescription.setNatureIds(originalProjectDescription.getNatureIds());
newProjectDescription.setReferencedProjects(originalProjectDescription
.getReferencedProjects());
newProjectDescription.setLocation(workspace.getRoot().getLocation().append(destinationRootPath + "/" + projectName + "/"));
}
project.create(newProjectDescription, null);
project.open(IResource.NONE, null);
// import operation to import project files if copy checkbox is selected
if (importSource != null) {
@SuppressWarnings("rawtypes")
List filesToImport = FileSystemStructureProvider.INSTANCE
.getChildren(importSource);
ImportOperation operation = new ImportOperation(project
.getFullPath(), importSource,
FileSystemStructureProvider.INSTANCE, new IOverwriteQuery() {
@Override
public String queryOverwrite(String pathString) {
return IOverwriteQuery.ALL;
}
}, filesToImport);
operation.setContext(null);
operation.setOverwriteResources(true); // need to overwrite
// .project, .classpath
// files
operation.setCreateContainerStructure(false);
operation.run(null);
}
}
};
importOperation.run(null);
return workspace.getRoot().getProject(projectName);
}
public static SWTWorkbenchBot createBot() {
SWTWorkbenchBot bot = new SWTWorkbenchBot();
try {
bot.viewByTitle("Welcome").close();
}
catch(WidgetNotFoundException e) {}
return bot;
}
/**
* Create a mouse event
*
* @param x the x co-ordinate of the mouse event.
* @param y the y co-ordinate of the mouse event.
* @param button the mouse button that was clicked.
* @param stateMask the state of the keyboard modifier keys.
* @param count the number of times the mouse was clicked.
* @return an event that encapsulates {@link #widget} and {@link #display}
* @since 1.2
*/
private static Event createMouseEvent(Widget widget, Display display, int x, int y, int button, int stateMask, int count) {
Event event = new Event();
event.time = (int) System.currentTimeMillis();
event.widget = widget;
event.display = display;
event.x = x;
event.y = y;
event.button = button;
event.stateMask = stateMask;
event.count = count;
return event;
}
/**
* Sends a non-blocking notification of the specified type to the widget.
*
* @param eventType the type of event.
* @param createEvent the event to be sent to the {@link #widget}.
* @param widget the widget to send the event to.
*/
public static void notify(final int eventType, final Event createEvent, final Widget widget, final Display display) {
createEvent.type = eventType;
UIThreadRunnable.asyncExec(display, new VoidResult() {
public void run() {
if ((widget == null) || widget.isDisposed()) {
return;
}
try {
if (! ((Boolean) SWTUtils.invokeMethod(widget, "isEnabled")).booleanValue()) {
return;
}
} catch (Exception e) {
}
widget.notifyListeners(eventType, createEvent);
}
});
UIThreadRunnable.syncExec(new VoidResult() {
public void run() {
// do nothing, just wait for sync.
}
});
long playbackDelay = SWTBotPreferences.PLAYBACK_DELAY;
if (playbackDelay > 0)
SWTUtils.sleep(playbackDelay);
}
public static void resetWorkbench(SWTWorkbenchBot bot) {
bot.closeAllEditors();
bot.resetWorkbench();
}
public static Collection<String> getProjectErrorMarkers(IProject project) throws CoreException {
project.refreshLocal(IResource.DEPTH_INFINITE, null);
IMarker[] allProblems = project.findMarkers(CeylonBuilder.PROBLEM_MARKER_ID, true, IResource.DEPTH_INFINITE);
List<String> errors = new ArrayList<String>();
for (IMarker marker : allProblems) {
if (marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO) >= IMarker.SEVERITY_ERROR) {
String errorToAdd = marker.getResource().getProjectRelativePath() +
" (l."+
marker.getAttribute(IMarker.LINE_NUMBER, 0) +
") : " +
marker.getAttribute(IMarker.MESSAGE, "");
errors.add(errorToAdd);
}
}
return errors;
}
public static class PostBuildListener implements IResourceChangeListener {
static private PostBuildListener _instance = null;
public synchronized static PostBuildListener instance() {
if (_instance == null) {
_instance = new PostBuildListener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(
_instance,
IResourceChangeEvent.POST_BUILD |
IResourceChangeEvent.PRE_BUILD);
}
return _instance;
}
private CountDownLatch buildLatch = null;
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (event.getType() == IResourceChangeEvent.PRE_BUILD) {
buildLatch = new CountDownLatch(1);
}
if (event.getType() == IResourceChangeEvent.POST_BUILD) {
if (buildLatch != null) {
buildLatch.countDown();
buildLatch = null;
}
}
}
public void waitForEndOfCurrentBuild(long timeoutInSeconds) {
CountDownLatch latch = buildLatch;
if (latch != null) {
try {
latch.await(timeoutInSeconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class CeylonBuildSummary extends CeylonBuildHook {
private CeylonBuildHook ceylonBuildHookToRestore;
private boolean installed = false;
private CountDownLatch firstBuildLatch = new CountDownLatch(1);
private int kind;
@SuppressWarnings("rawtypes")
private Map args;
private IProject project;
private Collection<IClasspathContainer> resolvedCpContainers;
private boolean cpContainersSetAndRefreshed = false;
private boolean fullBuild = false;
private boolean ceylonModelParsedDuringFullBuild = false;
private boolean incrementalBuild = false;
private boolean fullTypeCheckDuringIncrementalBuild = false;
private Collection<IFile> incrementalBuildChangedSources;
private Collection<IFile> incrementalBuildfilesToRemove;
private Collection<IFile> incrementalBuildSourcesToCompile;
private Collection<PhasedUnit> incrementalBuildResultPhasedUnits;
private CeylonBuildSummary reentrantBuildSummary;
private boolean scheduledReentrantBuild = false;
private List<CeylonBuildSummary> previousBuilds =new ArrayList<>();
private boolean isReentrant = false;
private List<String> errors = null;
private CeylonBuildSummary summaryToFill = this;
private IBuildConfiguration configuration;
private IBuildConfiguration[] requestConfigurations = new IBuildConfiguration[0];
private IBuildConfiguration[] referencedconfigurations = new IBuildConfiguration[0];
private IBuildConfiguration[] referencingconfigurations = new IBuildConfiguration[0];
public CeylonBuildSummary(IProject project) {
this.project = project;
}
public void install() {
CeylonBuildHook previousHook = CeylonBuilder.replaceHook(this);
installed = true;
if (ceylonBuildHookToRestore == null) {
ceylonBuildHookToRestore = previousHook;
}
}
@Override
protected void startBuild(int kind, @SuppressWarnings("rawtypes") Map args, IProject project,
IBuildConfiguration config, IBuildContext context, IProgressMonitor monitor) {
if (! this.project.equals(project)) {
summaryToFill = new CeylonBuildSummary(project);
previousBuilds.add(summaryToFill);
} else {
summaryToFill = this;
}
summaryToFill.kind = kind;
summaryToFill.args = args;
summaryToFill.configuration = config;
summaryToFill.requestConfigurations = context.getRequestedConfigs();
summaryToFill.referencedconfigurations = context.getAllReferencedBuildConfigs();
summaryToFill.referencingconfigurations = context.getAllReferencingBuildConfigs();
}
protected void resolvingClasspathContainer(
List<IClasspathContainer> cpContainers) {
summaryToFill.resolvedCpContainers = cpContainers;
}
protected void setAndRefreshClasspathContainer() {
summaryToFill.cpContainersSetAndRefreshed = true;
}
protected void doFullBuild() {
summaryToFill.fullBuild = true;
}
protected void parseCeylonModel() {
summaryToFill.ceylonModelParsedDuringFullBuild = true;
}
protected void doIncrementalBuild() {
summaryToFill.incrementalBuild = true;
}
protected void fullTypeCheckDuringIncrementalBuild() {
summaryToFill.fullTypeCheckDuringIncrementalBuild = true;
}
protected void incrementalBuildSources(Set<IFile> changedSources,
List<IFile> filesToRemove, Collection<IFile> sourcesToCompile) {
summaryToFill.incrementalBuildChangedSources = changedSources;
summaryToFill.incrementalBuildfilesToRemove = filesToRemove;
summaryToFill.incrementalBuildSourcesToCompile = sourcesToCompile;
}
protected void incrementalBuildResult(List<PhasedUnit> builtPhasedUnits) {
summaryToFill.incrementalBuildResultPhasedUnits = builtPhasedUnits;
}
protected CeylonBuildSummary createReentrantBuildSummary(IProject project) {
return new CeylonBuildSummary(project);
}
@Override
protected void scheduleReentrantBuild() {
summaryToFill.scheduledReentrantBuild = true;
if (summaryToFill == this) {
reentrantBuildSummary = createReentrantBuildSummary(project);
reentrantBuildSummary.isReentrant = true;
reentrantBuildSummary.ceylonBuildHookToRestore = ceylonBuildHookToRestore;
}
}
@Override
protected void endBuild() {
errors = new ArrayList<>();
if (project != null) {
try {
IMarker[] allProblems = project.findMarkers(CeylonBuilder.PROBLEM_MARKER_ID, true, IResource.DEPTH_INFINITE);
for (IMarker marker : allProblems) {
if (marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO) >= IMarker.SEVERITY_ERROR) {
String errorToAdd = marker.getResource().getProjectRelativePath() +
" (l."+
marker.getAttribute(IMarker.LINE_NUMBER, 0) +
") : " +
marker.getAttribute(IMarker.MESSAGE, "");
errors.add(errorToAdd);
}
}
} catch (CoreException e) {
e.printStackTrace();
}
}
if (summaryToFill == this) {
if (reentrantBuildSummary != null) {
reentrantBuildSummary.install();
} else {
CeylonBuilder.replaceHook(ceylonBuildHookToRestore);
}
firstBuildLatch.countDown();
}
}
@Override
protected void afterReentrantBuild() {
if (summaryToFill == this) {
if (reentrantBuildSummary != null &&
! reentrantBuildSummary.didFullBuild() &&
! reentrantBuildSummary.didIncrementalBuild()) {
reentrantBuildSummary.endBuild();
}
}
}
public boolean waitForBuildEnd(long timeoutInSeconds) throws InterruptedException {
if (!installed) {
throw new RuntimeException("Cannot wait for a build with a non-installed hook !");
}
try {
if (firstBuildLatch.await(timeoutInSeconds, TimeUnit.SECONDS)) {
if (reentrantBuildSummary != null) {
reentrantBuildSummary.waitForBuildEnd(timeoutInSeconds);
}
return true;
} else {
if (reentrantBuildSummary != null) {
reentrantBuildSummary.endBuild();
}
return false;
}
}
finally {
System.out.println(toString());
}
}
public final int getKind() {
return kind;
}
@SuppressWarnings("rawtypes")
public final Map getArgs() {
return args;
}
public final boolean isReentrant() {
return isReentrant;
}
public final IProject getProject() {
return project;
}
public final boolean didTriggerReentrantBuild() {
return scheduledReentrantBuild;
}
public final CeylonBuildSummary getReentrantBuildSummary() {
return reentrantBuildSummary;
}
public final boolean didResolveCpContainers() {
return resolvedCpContainers != null;
}
public final Collection<IClasspathContainer> getResolvedCpContainers() {
return resolvedCpContainers;
}
public final boolean didSetAndRefreshedCpContainers() {
return cpContainersSetAndRefreshed;
}
public final boolean didFullBuild() {
return fullBuild;
}
public final boolean didParseCeylonModelDuringFullBuild() {
return ceylonModelParsedDuringFullBuild;
}
public final boolean didIncrementalBuild() {
return incrementalBuild;
}
public final boolean didFullTypeCheckDuringIncrementalBuild() {
return fullTypeCheckDuringIncrementalBuild;
}
public final Collection<IFile> getIncrementalBuildChangedSources() {
return incrementalBuildChangedSources;
}
public final Collection<IFile> getIncrementalBuildfilesToRemove() {
return incrementalBuildfilesToRemove;
}
public final Collection<IFile> getIncrementalBuildSourcesToCompile() {
return incrementalBuildSourcesToCompile;
}
public final Collection<PhasedUnit> getIncrementalBuildResultPhasedUnits() {
return incrementalBuildResultPhasedUnits;
}
public List<CeylonBuildSummary> getPreviousBuilds() {
return previousBuilds;
}
public IBuildConfiguration getConfiguration() {
return configuration;
}
public IBuildConfiguration[] getRequestConfigurations() {
return requestConfigurations;
}
public IBuildConfiguration[] getReferencedconfigurations() {
return referencedconfigurations;
}
public IBuildConfiguration[] getReferencingconfigurations() {
return referencingconfigurations;
}
public void setPreviousBuilds(List<CeylonBuildSummary> previousBuilds) {
this.previousBuilds = previousBuilds;
}
public List<String> getErrors() {
return errors;
}
@Override
public String toString() {
String result = "";
for (CeylonBuildSummary previousSummary : getPreviousBuilds()) {
result += previousSummary + "\n";
}
result +=
"Project : " + project + "\n" +
( ! didFullBuild() && ! didIncrementalBuild() ?
" No Build performed !\n"
:
" Kind : " + kind + "\n" +
" Args : " + args + "\n" +
" Current Build Configuration : " + configuration + "\n" +
" Requested Build Configurations : " + Arrays.asList(requestConfigurations) + "\n" +
" Referenced Build Configurations : " + Arrays.asList(referencedconfigurations) + "\n" +
" Referencing Build Configurations : " + Arrays.asList(referencingconfigurations) + "\n" +
" Build Type : " + ( didFullBuild() ? "Full" : "Incremental" ) + "\n" +
" Resolved Classpath Containers during Build : " + didResolveCpContainers() + "\n" +
" Set and Refreshed Classpath Containers during Build : " + didSetAndRefreshedCpContainers() + "\n" +
( didFullBuild() ?
" Parsed Ceylon Model during Full Build : " + didParseCeylonModelDuringFullBuild() + "\n"
:
" Did a Full TypeCheck during Incremental Build : " + didFullTypeCheckDuringIncrementalBuild() + "\n" +
" Changed Sources : " + getIncrementalBuildChangedSources() + "\n" +
" Files To Remove : " + getIncrementalBuildfilesToRemove() + "\n" +
" Sources To Compile : " + getIncrementalBuildSourcesToCompile() + "\n" +
" Result Phased Units : " + getIncrementalBuildResultPhasedUnits() + "\n"
) +
" Error Markers : " + getErrors() + "\n" +
" Triggered a reentrant Build : " + didTriggerReentrantBuild() + "\n"
);
if (didTriggerReentrantBuild() && installed) {
result += "\nReentrant Builds :\n";
for (CeylonBuildSummary previousSummary : getPreviousBuilds()) {
if (previousSummary.didTriggerReentrantBuild()) {
result += previousSummary.getReentrantBuildSummary() + "\n";
}
}
result += getReentrantBuildSummary() + "\n";
}
return result;
}
}
public static CeylonBuildSummary buildProject(IProject project) throws CoreException,
InterruptedException {
PostBuildListener buildListener = PostBuildListener.instance();
CeylonBuildSummary summary = new CeylonBuildSummary(project);
summary.install();
project.build(IncrementalProjectBuilder.FULL_BUILD, null);
summary.waitForBuildEnd(60);
buildListener.waitForEndOfCurrentBuild(5);
return summary;
}
public static void ctrlClick(final SWTBotEclipseEditor editor) {
Rectangle caretCoordinates = UIThreadRunnable.syncExec(editor.getStyledText().display, new Result<Rectangle>() {
@Override
public Rectangle run() {
int offset = editor.getStyledText().widget.getCaretOffset();
return editor.getStyledText().widget.getTextBounds(offset, offset + 1);
}
});
final Point pointToMoveAndClick = new Point(caretCoordinates.x + (caretCoordinates.width / 2), caretCoordinates.y + caretCoordinates.height / 2);
editor.setFocus();
asyncExec(editor.getStyledText().display, new VoidResult() {
@Override
public void run() {
Point absoluteCoordinates = editor.getStyledText().widget.toDisplay(pointToMoveAndClick);
editor.getStyledText().display.setCursorLocation(absoluteCoordinates);
}
});
Event event = Utils.createMouseEvent(editor.getStyledText().widget, editor.getStyledText().display, pointToMoveAndClick.x, pointToMoveAndClick.y, 0, SWT.CTRL, 1);
Utils.notify(SWT.MouseMove, event, editor.getStyledText().widget, editor.getStyledText().display);
event = Utils.createMouseEvent(editor.getStyledText().widget, editor.getStyledText().display, pointToMoveAndClick.x, pointToMoveAndClick.y, 0, SWT.CTRL, 1);
Utils.notify(SWT.MouseMove, event, editor.getStyledText().widget, editor.getStyledText().display);
event = Utils.createMouseEvent(editor.getStyledText().widget, editor.getStyledText().display, pointToMoveAndClick.x, pointToMoveAndClick.y, 1, SWT.CTRL, 1);
Utils.notify(SWT.MouseDown, event, editor.getStyledText().widget, editor.getStyledText().display);
event = Utils.createMouseEvent(editor.getStyledText().widget, editor.getStyledText().display, pointToMoveAndClick.x, pointToMoveAndClick.y, 1, SWT.CTRL, 1);
Utils.notify(SWT.MouseUp, event, editor.getStyledText().widget, editor.getStyledText().display);
}
public static SWTBotEclipseEditor showEditorByTitle(SWTWorkbenchBot bot, String title) {
try {
SWTBotEditor editor = bot.editorByTitle(title);
Assert.assertNotNull("No opened editor found with title '" + title + "'", editor);
SWTBotEclipseEditor textEditor = editor.toTextEditor();
textEditor.show();
return textEditor;
} catch(WidgetNotFoundException e) {
Assert.fail("No opened editor found with title '" + title + "'");
}
return null;
}
public static Position positionInTextEditor(SWTBotEclipseEditor editor, String match, int offset) {
return positionInTextEditor(editor, Pattern.compile(match, Pattern.LITERAL), offset);
}
public static Position positionInTextEditor(SWTBotEclipseEditor editor, Pattern pattern, int offset) {
for (int line=0; line < editor.getLineCount(); line++) {
String lineText = editor.getTextOnLine(line);
Matcher matcher = pattern.matcher(lineText);
if (matcher.find()) {
return new Position(line, matcher.start() + offset);
}
}
Assert.fail("The editor of file '" + editor.getTitle() + "' doesn't contain any string matching '" + pattern + "'");
return null;
}
}