package jetbrains.mps.lang.test.runtime; /*Generated by MPS */ import org.apache.log4j.Logger; import org.apache.log4j.LogManager; import com.intellij.ide.DataManager; import com.intellij.ide.impl.DataManagerImpl; import jetbrains.mps.openapi.editor.EditorComponent; import jetbrains.mps.openapi.editor.Editor; import jetbrains.mps.ide.editor.MPSFileNodeEditor; import org.jetbrains.mps.openapi.model.SNode; import jetbrains.mps.ide.ThreadUtils; import java.lang.reflect.InvocationTargetException; import jetbrains.mps.nodeEditor.NodeEditorComponent; import java.util.List; import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import org.jetbrains.mps.openapi.language.SAbstractConcept; import jetbrains.mps.internal.collections.runtime.ListSequence; import jetbrains.mps.baseLanguage.closures.runtime.Wrappers; import jetbrains.mps.lang.test.matcher.NodesMatcher; import jetbrains.mps.lang.test.matcher.NodeDifference; import java.util.Collections; import junit.framework.Assert; import java.util.Map; import jetbrains.mps.testbench.util.CachingAppender; import jetbrains.mps.testbench.junit.UncleanTestExecutionException; import com.intellij.openapi.command.impl.UndoManagerImpl; import com.intellij.openapi.command.undo.UndoManager; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.nodefs.MPSNodeVirtualFile; import jetbrains.mps.nodefs.NodeVirtualFileSystem; import jetbrains.mps.project.MPSProject; import jetbrains.mps.project.Project; import java.awt.Component; import org.jetbrains.mps.util.Condition; import jetbrains.mps.openapi.intentions.IntentionExecutable; import jetbrains.mps.openapi.editor.EditorContext; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.AnActionEvent; import jetbrains.mps.workbench.action.ActionUtils; import com.intellij.openapi.actionSystem.ActionPlaces; import javax.swing.SwingUtilities; import jetbrains.mps.smodel.ModelAccess; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.EditorCell_Collection; import jetbrains.mps.nodeEditor.cells.CellFinderUtil; import jetbrains.mps.nodeEditor.inspector.InspectorEditorComponent; import com.intellij.openapi.command.impl.CurrentEditorProvider; import com.intellij.openapi.fileEditor.FileEditor; import org.apache.log4j.Level; /** * Common ancestor for all generated EditorTestCase instances * TODO must be moved up to the platform level: the MPSNodeEditor is availalbe only in ide */ public abstract class BaseEditorTestBody extends BaseTestBody { private static final Logger LOG = LogManager.getLogger(BaseEditorTestBody.class); private static DataManager DATA_MANAGER = new DataManagerImpl(); private EditorComponent myCurrentEditorComponent; protected Editor myEditor; private MPSFileNodeEditor myFileNodeEditor; private SNode myBefore; private SNode myResult; protected CellReference myStart; protected CellReference myFinish; public abstract void testMethodImpl() throws Exception; /** * * @deprecated use #initEditorComponent instead */ @Deprecated protected Editor initEditor(final String before, final String after) { if (LOG.isInfoEnabled()) { LOG.info("Initializing editor"); } final Throwable[] ts = new Throwable[1]; ThreadUtils.runInUIThreadAndWait(new Runnable() { @Override public void run() { try { BaseEditorTestBody.this.doInitEditor(before, after); } catch (Throwable t) { ts[0] = t; } } }); try { flushEDTEvents(); } catch (InvocationTargetException e) { ts[0] = e; } catch (InterruptedException e) { ts[0] = e; } if (ts[0] != null) { throw new RuntimeException("Exception while initializing the editor", ts[0]); } return myEditor; } protected EditorComponent initEditorComponent(String before, String after) { initEditor(before, after); return myCurrentEditorComponent; } private void doInitEditor(final String before, final String after) throws Exception { addNodeById(before); if (!(after.equals(""))) { addNodeById(after); } myProject.getModelAccess().runWriteAction(new Runnable() { public void run() { myBefore = getNodeById(before); myStart = findCellReference(getRealNodeById(before)); if (myStart == null) { throw new IllegalStateException("Cannot find cell reference in the test case 'before'"); } if (!(after.equals(""))) { myResult = getNodeById(after); myFinish = findCellReference(getRealNodeById(after)); } myFileNodeEditor = openEditor(); myEditor = myFileNodeEditor.getNodeEditor(); myCurrentEditorComponent = myEditor.getCurrentEditorComponent(); if (!(myCurrentEditorComponent instanceof NodeEditorComponent)) { throw new IllegalArgumentException("The component is not an instance of NodeEditorComponent: " + myCurrentEditorComponent); } NodeEditorComponent component = (NodeEditorComponent) myCurrentEditorComponent; component.addNotify(); component.setSize(component.getPreferredSize()); component.validate(); myCurrentEditorComponent = myStart.setupSelection(component); } }); } private CellReference findCellReference(SNode node) { List<SNode> annotations = SNodeOperations.getNodeDescendants(node, MetaAdapterFactory.getConcept(0x8585453e6bfb4d80L, 0x98deb16074f1d86cL, 0x11e31babe12L, "jetbrains.mps.lang.test.structure.AnonymousCellAnnotation"), false, new SAbstractConcept[]{}); if (ListSequence.fromList(annotations).isEmpty()) { return null; } return new CellReference(getNodeById(SNodeOperations.getParent(ListSequence.fromList(annotations).first()).getNodeId().toString()), ListSequence.fromList(annotations).first(), myMap); } protected void checkAssertion() throws Throwable { final Wrappers._T<Throwable> throwable = new Wrappers._T<Throwable>(null); flushEvents(); // FIXME why do we need model write here? ThreadUtils.runInUIThreadAndWait(new Runnable() { public void run() { myProject.getModelAccess().runWriteAction(new Runnable() { public void run() { if (myResult != null) { try { SNode editedNode = myBefore; NodesMatcher nm = new NodesMatcher(); List<NodeDifference> diff = nm.match(Collections.singletonList(editedNode), Collections.singletonList(myResult)); Assert.assertEquals(null, diff); if (myFinish != null) { myFinish.assertSelectionIsTheSame(myCurrentEditorComponent, (Map<SNode, SNode>) nm.getMap()); } } catch (Throwable t) { throwable.value = t; } } } }); } }); flushEvents(); if (throwable.value != null) { throw throwable.value; } } public void testMethod() throws Throwable { CachingAppender appender = installAppender(); try { testMethodImpl(); checkAssertion(); dispose(); appender.sealEvents(); if (appender.isNotEmpty()) { throw new UncleanTestExecutionException(appender); } } finally { uninstallAppender(appender); final Throwable[] ts = new Throwable[1]; myProject.getModelAccess().runWriteInEDT(new Runnable() { public void run() { try { UndoManagerImpl undoManager = (UndoManagerImpl) UndoManager.getInstance(ProjectHelper.toIdeaProject(myProject)); MPSNodeVirtualFile file = NodeVirtualFileSystem.getInstance().getFileFor(myProject.getRepository(), BaseEditorTestBody.this.myBefore); undoManager.clearUndoRedoQueueInTests(file); } catch (Throwable t) { ts[0] = t; } } }); if (ts[0] != null) { throw new RuntimeException("Failure during editor test execution", ts[0]); } } } private void dispose() throws InterruptedException, InvocationTargetException { final Throwable[] ts = new Throwable[1]; ThreadUtils.runInUIThreadAndWait(new Runnable() { public void run() { myProject.getModelAccess().runWriteAction(new Runnable() { public void run() { try { myFileNodeEditor.dispose(); myFileNodeEditor = null; } catch (Throwable t) { ts[0] = t; } } }); } }); if (ts[0] != null) { throw new RuntimeException("Failure during test disposal", ts[0]); } } private MPSFileNodeEditor openEditor() { assert ThreadUtils.isInEDT(); MPSNodeVirtualFile file = NodeVirtualFileSystem.getInstance().getFileFor(myProject.getRepository(), myBefore); return new MPSFileNodeEditor((MPSProject) myProject, file); } protected jetbrains.mps.nodeEditor.EditorComponent getEditorComponent() { return (jetbrains.mps.nodeEditor.EditorComponent) myCurrentEditorComponent; } /** * * @deprecated no need to refer the editor instance here -- EditorComponent is enough */ @Deprecated protected Editor getEditor() { return myEditor; } protected Project getProject() { return myProject; } protected void typeString(final String text) throws InterruptedException, InvocationTargetException { new KeyEventsDispatcher(this).typeString(text); } protected void pressKeys(final List<String> keyStrokes) throws InterruptedException, InvocationTargetException { new KeyEventsDispatcher(this).pressKeys(keyStrokes); } protected Component processMouseEvent(int x, int y, int eventType) throws InvocationTargetException, InterruptedException { return new MouseEventsDispatcher(this).processMouseEvent(x, y, eventType); } protected void processSecondaryMouseEvent(final Component targetComponent, int x, int y, int eventType) throws InvocationTargetException, InterruptedException { new MouseEventsDispatcher(this).processSecondaryMouseEvent(targetComponent, x, y, eventType); } protected void invokeIntention(String id, SNode node) throws InterruptedException, InvocationTargetException { invokeMatchingIntention(node, new MatchIntentionById(id)); } protected void invokeParameterizedIntention(String id, Object parameter, SNode node) throws InterruptedException, InvocationTargetException { invokeMatchingIntention(node, new MatchIntentionByIdAndParameter(id, parameter)); } protected void invokeMatchingIntention(final SNode node, Condition<IntentionExecutable> intentionCondition) throws InterruptedException, InvocationTargetException { new IntentionTester(this).invokeMatchingIntention(node, intentionCondition); } protected boolean isIntentionApplicable(String intentionId, SNode node) throws InterruptedException, InvocationTargetException { return new IntentionTester(this).isIntentionApplicable(intentionId, node); } public EditorContext getEditorContext() { return myCurrentEditorComponent.getEditorContext(); } protected void invokeAction(final String actionId) throws InvocationTargetException, InterruptedException { final AnAction action = ActionManager.getInstance().getAction(actionId); final AnActionEvent event = ActionUtils.createEvent(ActionPlaces.MAIN_MENU, DATA_MANAGER.getDataContext(getEditorComponent())); runUndoableInEDTAndWait(new Runnable() { public void run() { action.actionPerformed(event); } }); } private void flushEDTEvents() throws InvocationTargetException, InterruptedException { // wait for all events currently in EDT queue SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { // empty task } }); // flushing model events flushEvents(); } private void flushEvents() { ModelAccess.instance().flushEventQueue(); } protected void switchToInspector() throws InvocationTargetException, InterruptedException { if (!(myCurrentEditorComponent instanceof NodeEditorComponent)) { throw new IllegalArgumentException("Impossible to switch to inspector: the component is not a NodeEditorComponent: " + myCurrentEditorComponent); } myCurrentEditorComponent = ((NodeEditorComponent) myCurrentEditorComponent).getInspector(); if (myCurrentEditorComponent.getSelectionManager().getSelection() == null) { selectFirstCellInInspector(myCurrentEditorComponent); } } private void selectFirstCellInInspector(EditorComponent myCurrentEditorComponent) { EditorCell toSelect = null; EditorCell rootCell = myCurrentEditorComponent.getRootCell(); if (rootCell instanceof EditorCell_Collection) { toSelect = CellFinderUtil.findChildByManyFinders(rootCell, CellFinderUtil.Finder.FIRST_EDITABLE, CellFinderUtil.Finder.FIRST_SELECTABLE_LEAF); if (toSelect == null) { toSelect = rootCell; } } else if (rootCell != null && rootCell.isSelectable()) { toSelect = rootCell; } if (toSelect != null) { myCurrentEditorComponent.changeSelection(toSelect); } } protected void switchBackFromInspector() { if (!(myCurrentEditorComponent instanceof InspectorEditorComponent)) { throw new IllegalArgumentException("Impossible to switch back from inspector: the component is not a InspectorEditorComponent: " + myCurrentEditorComponent); } myCurrentEditorComponent = myFileNodeEditor.getNodeEditor().getCurrentEditorComponent(); } public void runUndoableCommandInEDTAndWait(final Runnable runnable) throws InterruptedException, InvocationTargetException { runUndoableInEDTAndWait(new Runnable() { public void run() { getProject().getModelAccess().executeCommand(runnable); } }); } public void runUndoableInEDTAndWait(final Runnable runnable) throws InvocationTargetException, InterruptedException { UndoManagerImpl undoManager = (UndoManagerImpl) UndoManager.getInstance(ProjectHelper.toIdeaProject(myProject)); CurrentEditorProvider oldEditorProvider = undoManager.getEditorProvider(); undoManager.setEditorProvider(new CurrentEditorProvider() { public FileEditor getCurrentEditor() { return myFileNodeEditor; } }); Exception exception = ThreadUtils.runInUIThreadAndWait(runnable); if (exception != null) { throw new RuntimeException("Failure during editor test execution", exception); } flushEDTEvents(); // some actions (Copy/Paste) are running one more command later flushEDTEvents(); undoManager.setEditorProvider(oldEditorProvider); } private CachingAppender installAppender() { Logger rootLogger = Logger.getRootLogger(); CachingAppender appender = new CachingAppender(Level.ERROR); populateExpectedEvents(appender); rootLogger.addAppender(appender); return appender; } protected void populateExpectedEvents(CachingAppender appender) { } private void uninstallAppender(CachingAppender appender) { Logger.getRootLogger().removeAppender(appender); } }