/******************************************************************************* * Copyright (c) 2005, 2017 IBM Corporation 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: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.wst.sse.ui.tests; import java.io.ByteArrayInputStream; import java.lang.reflect.Method; import java.util.Arrays; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.ITextFileBufferManager; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.swt.SWT; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.wst.sse.ui.StructuredTextEditor; import org.eclipse.wst.sse.ui.internal.SSEUIPlugin; import org.eclipse.wst.sse.ui.internal.StructuredTextViewer; import org.eclipse.wst.sse.ui.internal.reconcile.DocumentRegionProcessor; import org.eclipse.wst.sse.ui.reconcile.ISourceReconcilingListener; import junit.framework.TestCase; public class TestStructuredTextEditor extends TestCase { private final String PROJECT_NAME = "TestStructuredTextEditor"; private final String FILE_NAME = "testStructuredTextEditor.xml"; private static final int MAX_WAIT = 10000; private static final int TIME_DELTA = 50; private static StructuredTextEditor fEditor; private static IFile fFile; private static boolean fIsSetup = false; public TestStructuredTextEditor() { super("TestStructuredTextEditor"); } protected void setUp() throws Exception { if (!fIsSetup) { // create project createProject(PROJECT_NAME); fFile = getOrCreateFile(PROJECT_NAME + "/" + FILE_NAME); fIsSetup = true; } if (fIsSetup && fEditor == null) { IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); IWorkbenchPage page = workbenchWindow.getActivePage(); IEditorInput input = new FileEditorInput(fFile); /* * This should take care of testing init, createPartControl, * beginBackgroundOperation, endBackgroundOperation methods */ IEditorPart part = page.openEditor(input, "org.eclipse.wst.sse.ui.StructuredTextEditor.test", true); if (part instanceof StructuredTextEditor) fEditor = (StructuredTextEditor) part; else assertTrue("Unable to open structured text editor", false); } } protected void tearDown() throws Exception { if (fEditor != null) { /* * This should take care of testing close and dispose methods */ fEditor.close(false); assertTrue("Unable to close editor", true); fEditor = null; } } private void createProject(String projName) { IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(projName); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName); try { project.create(description, new NullProgressMonitor()); project.open(new NullProgressMonitor()); } catch (CoreException e) { e.printStackTrace(); } } protected IFile getOrCreateFile(String filePath) { IFile blankJspFile = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(filePath)); if (blankJspFile != null && !blankJspFile.exists()) { try { blankJspFile.create(new ByteArrayInputStream(new byte[0]), true, new NullProgressMonitor()); } catch (CoreException e) { e.printStackTrace(); } } return blankJspFile; } public void testDoSaving() { fEditor.doRevertToSaved(); assertTrue("Unable to revert to saved", true); fEditor.doSave(new NullProgressMonitor()); assertTrue("Unable to save", true); } public void testEditorContextMenuAboutToShow() { IMenuManager menu = new MenuManager(); fEditor.editorContextMenuAboutToShow(menu); assertTrue("Unable to prepare for context menu about to show", true); menu.dispose(); menu = null; } public void testGetAdapter() { Object adapter = fEditor.getAdapter(IShowInTargetList.class); assertTrue("Get adapter for show in target failed", adapter instanceof IShowInTargetList); } public void testGetSetEditorPart() { fEditor.setEditorPart(null); assertTrue("Unable to set editor part", true); IEditorPart part = fEditor.getEditorPart(); assertTrue("Did not get expected editor part", part instanceof StructuredTextEditor); } public void testInitializeDocumentProvider() { fEditor.initializeDocumentProvider(null); assertTrue("Unable to initialize document provider", true); } public void testGetOrientation() { int or = fEditor.getOrientation(); assertEquals(SWT.LEFT_TO_RIGHT, or); } public void testGetSelectionProvider() { ISelectionProvider provider = fEditor.getSelectionProvider(); assertNotNull("Editor's selection provider was null", provider); } public void testGetTextViewer() { StructuredTextViewer viewer = fEditor.getTextViewer(); assertNotNull("Editor's text viewer was null", viewer); } public void testRememberRestoreSelection() { fEditor.rememberSelection(); assertTrue("Unable to remember editor selection", true); fEditor.restoreSelection(); assertTrue("Unable to restore editor selection", true); } public void testSafelySanityCheck() { fEditor.safelySanityCheckState(fEditor.getEditorInput()); assertTrue("Unable to safely sanity check editor state", true); } public void testShowBusy() { fEditor.showBusy(false); assertTrue("Unable to show editor is busy", true); } public void testUpdate() { fEditor.update(); assertTrue("Unable to update editor", true); } public void testSingleNonUIThreadUpdatesToEditorDocument() throws Exception { IFile file = getOrCreateFile(PROJECT_NAME + "/" + "testBackgroundChanges.xml"); ITextFileBufferManager textFileBufferManager = FileBuffers.getTextFileBufferManager(); textFileBufferManager.connect(file.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); ITextFileBuffer textFileBuffer = textFileBufferManager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); final IDocument document = textFileBuffer.getDocument(); document.replace(0, 0, "<?xml encoding=\"UTF-8\" version=\"1.0\"?>\n"); textFileBuffer.commit(new NullProgressMonitor(), true); String testText = document.get() + "<c/><b/><a/>"; final int end = document.getLength(); IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IEditorPart openedEditor = IDE.openEditor(activePage, file); final boolean state[] = new boolean[]{false}; Job changer = new Job("text changer") { protected IStatus run(IProgressMonitor monitor) { try { document.replace(end, 0, "<a/>"); document.replace(end, 0, "<b/>"); document.replace(end, 0, "<c/>"); } catch (Exception e) { return new Status(IStatus.ERROR, SSEUIPlugin.ID, e.getMessage()); } finally { state[0] = true; } return Status.OK_STATUS; } }; changer.setUser(true); changer.setSystem(false); changer.schedule(); while (!state[0]) { openedEditor.getSite().getShell().getDisplay().readAndDispatch(); } String finalText = document.get(); textFileBuffer.commit(new NullProgressMonitor(), true); textFileBufferManager.disconnect(file.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); activePage.closeEditor(openedEditor, false); assertEquals("Non-UI changes did not apply", testText, finalText); } /** * Note: This test takes a while to run, but it's testing scalability after all. * @throws Exception */ public void testManyNonUIThreadsUpdatingEditorDocument() throws Exception { final int numberOfJobs = 3; /** * 15 minute timeout before we stop waiting for the change jobs to * complete */ long timeout = 15*60*1000; long startTime = System.currentTimeMillis(); IFile file = getOrCreateFile(PROJECT_NAME + "/" + "testManyBackgroundChanges.xml"); ITextFileBufferManager textFileBufferManager = FileBuffers.getTextFileBufferManager(); textFileBufferManager.connect(file.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); ITextFileBuffer textFileBuffer = textFileBufferManager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); final IDocument document = textFileBuffer.getDocument(); document.replace(0, 0, "<?xml encoding=\"UTF-8\" version=\"1.0\"?>\n"); textFileBuffer.commit(new NullProgressMonitor(), true); final int insertionPoint = document.getLength(); final char names[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); // numberOfJobs Jobs, inserting 26 tags, each 4 characters long int expectedLength = insertionPoint + numberOfJobs * 4 * names.length; IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); activePage.showView(IPageLayout.ID_PROGRESS_VIEW); IEditorPart openedEditor = IDE.openEditor(activePage, file); final int finished[] = new int[]{numberOfJobs}; Job changers[] = new Job[numberOfJobs]; for (int i = 0; i < changers.length; i++) { changers[i] = new Job("Text Changer " + Integer.toString(i)) { protected IStatus run(IProgressMonitor monitor) { try { for (int j = 0; j < names.length; j++) { document.replace(insertionPoint + 4 * j, 0, "<" + names[j] + "/>"); } } catch (Exception e) { return new Status(IStatus.ERROR, SSEUIPlugin.ID, e.getMessage()); } finally { finished[0]--; } return Status.OK_STATUS; } }; changers[i].setUser(true); changers[i].setSystem(false); } for (int i = 0; i < changers.length; i++) { changers[i].schedule(); } long runtime = 0; while (finished[0] > 0 && (runtime = System.currentTimeMillis()) - startTime < timeout) { openedEditor.getSite().getShell().getDisplay().readAndDispatch(); } assertTrue("Test timed out: ("+timeout+" was allowed)", runtime - startTime < timeout); int finalLength = document.getLength(); textFileBuffer.commit(new NullProgressMonitor(), true); textFileBufferManager.disconnect(file.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); activePage.closeEditor(openedEditor, false); assertEquals("Some non-UI changes did not apply", expectedLength, finalLength); } /** * Test receiving the initial reconcile notification when the editor opens */ public void testInitialReconciling() throws Exception { IFile file = getOrCreateFile(PROJECT_NAME + "/" + "reconcilingtest.xml"); IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); final int[] state = new int[2]; Arrays.fill(state, -1); ISourceReconcilingListener listener = new ISourceReconcilingListener() { int mod = 0; public void reconciled(IDocument document, IAnnotationModel model, boolean forced, IProgressMonitor progressMonitor) { state[1] = mod++; } public void aboutToBeReconciled() { state[0] = mod++; } }; IEditorPart editor = IDE.openEditor(activePage, file, "org.eclipse.wst.sse.ui.StructuredTextEditor.test"); try { assertTrue("Not a StructuredTextEditor", editor instanceof StructuredTextEditor); addReconcilingListener((StructuredTextEditor) editor, listener); waitForReconcile(state); assertTrue("Initial: Reconciling did not complete in a timely fashion", state[0] != -1 && state[1] != -1); assertTrue("Initial: aboutToBeReconciled not invoked first (" + state[0] +")", state[0] == 0); assertTrue("Initial: reconciled not invoked after aboutToBeReconciled (" + state[1] +")", state[1] == 1); } finally { if (editor != null && activePage != null) { activePage.closeEditor(editor, false); } } } /** * Test that modifications to the editor's content will notify reconciling listeners */ public void testModificationReconciling() throws Exception { IFile file = getOrCreateFile(PROJECT_NAME + "/" + "reconcilingmodificationtest.xml"); IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); final int[] state = new int[2]; Arrays.fill(state, -1); ISourceReconcilingListener listener = new ISourceReconcilingListener() { int mod = 0; public void reconciled(IDocument document, IAnnotationModel model, boolean forced, IProgressMonitor progressMonitor) { state[1] = mod++; } public void aboutToBeReconciled() { state[0] = mod++; } }; IEditorPart editor = IDE.openEditor(activePage, file, "org.eclipse.wst.sse.ui.StructuredTextEditor.test"); try { assertTrue("Not a StructuredTextEditor", editor instanceof StructuredTextEditor); addReconcilingListener((StructuredTextEditor) editor, listener); waitForReconcile(state); assertTrue("Initial: Reconciling did not complete in a timely fashion", state[0] != -1 && state[1] != -1); assertTrue("Initial: aboutToBeReconciled not invoked first (" + state[0] +")", state[0] == 0); assertTrue("Initial: reconciled not invoked after aboutToBeReconciled (" + state[1] +")", state[1] == 1); IDocument document = ((StructuredTextEditor) editor).getDocumentProvider().getDocument(editor.getEditorInput()); assertTrue("Editor doesn't have a document", document != null); document.set("<?xml version=\"1.0\" ?>"); Arrays.fill(state, -1); waitForReconcile(state); assertTrue("Modified: Reconciling did not complete in a timely fashion", state[0] != -1 && state[1] != -1); assertTrue("Modified: aboutToBeReconciled not invoked first (" + state[0] +")", state[0] == 2); assertTrue("Modified: reconciled not invoked after aboutToBeReconciled (" + state[1] +")", state[1] == 3); } finally { if (editor != null && activePage != null) { activePage.closeEditor(editor, false); } } } /** * Test that an editor notifies reconciling listeners when the editor gets focus. */ public void testFocusedReconciling() throws Exception { IFile file = getOrCreateFile(PROJECT_NAME + "/" + "focustest.xml"); IFile fileAlt = getOrCreateFile(PROJECT_NAME + "/" + "focustestAlt.xml"); IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); final int[] state = new int[2]; Arrays.fill(state, -1); ISourceReconcilingListener listener = new ISourceReconcilingListener() { int mod = 0; public void reconciled(IDocument document, IAnnotationModel model, boolean forced, IProgressMonitor progressMonitor) { state[1] = mod++; } public void aboutToBeReconciled() { state[0] = mod++; } }; IEditorPart editor = IDE.openEditor(activePage, file, "org.eclipse.wst.sse.ui.StructuredTextEditor.test"); try { assertTrue("Not a StructuredTextEditor", editor instanceof StructuredTextEditor); addReconcilingListener((StructuredTextEditor) editor, listener); waitForReconcile(state); assertTrue("Initial: Reconciling did not complete in a timely fashion", state[0] != -1 && state[1] != -1); assertTrue("Initial: aboutToBeReconciled not invoked first (" + state[0] +")", state[0] == 0); assertTrue("Initial: reconciled not invoked after aboutToBeReconciled (" + state[1] +")", state[1] == 1); IDE.openEditor(activePage, fileAlt, "org.eclipse.wst.sse.ui.StructuredTextEditor.test"); Arrays.fill(state, -1); IEditorPart editorPart = IDE.openEditor(activePage, file, "org.eclipse.wst.sse.ui.StructuredTextEditor.test"); assertEquals("Didn't get the original editor back.", editor, editorPart); waitForReconcile(state); assertTrue("Modified: Reconciling did not complete in a timely fashion", state[0] != -1 && state[1] != -1); assertTrue("Modified: aboutToBeReconciled not invoked first (" + state[0] +")", state[0] == 2); assertTrue("Modified: reconciled not invoked after aboutToBeReconciled (" + state[1] +")", state[1] == 3); } finally { if (editor != null && activePage != null) { activePage.closeEditor(editor, false); } } } private void addReconcilingListener(StructuredTextEditor editor, ISourceReconcilingListener listener) throws Exception { Method mConfig = AbstractTextEditor.class.getDeclaredMethod("getSourceViewerConfiguration", null); mConfig.setAccessible(true); Object config = mConfig.invoke(editor, null); assertTrue("Did not get a source viewer configuration", config instanceof SourceViewerConfiguration); IReconciler reconciler = ((SourceViewerConfiguration) config).getReconciler(fEditor.getTextViewer()); assertTrue("Reconciler is not a DirtyRegionProcessor", reconciler instanceof DocumentRegionProcessor); ((DocumentRegionProcessor) reconciler).addReconcilingListener(listener); } public void waitForReconcile(int[] state) throws Exception { int time = 0; while ((state[0] == -1 || state[1] == -1) && time < MAX_WAIT) { Thread.sleep(TIME_DELTA); time += TIME_DELTA; } } }