/*==========================================================================*\ | $Id$ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2009 Virginia Tech | | This file is part of Web-CAT Eclipse Plugins. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU General Public License as published by | the Free Software Foundation; either version 2 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package net.sf.webcat.eclipse.cxxtest.ui; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import net.sf.webcat.eclipse.cxxtest.ICxxTestConstants; import net.sf.webcat.eclipse.cxxtest.i18n.Messages; import net.sf.webcat.eclipse.cxxtest.model.ICxxTestAssertion; import net.sf.webcat.eclipse.cxxtest.model.ICxxTestBase; import net.sf.webcat.eclipse.cxxtest.model.ICxxTestMethod; import net.sf.webcat.eclipse.cxxtest.model.ICxxTestSuite; import net.sf.webcat.eclipse.cxxtest.model.ICxxTestSuiteChild; import net.sf.webcat.eclipse.cxxtest.model.ICxxTestSuiteError; import net.sf.webcat.eclipse.cxxtest.xml.ContextualSAXHandler; import net.sf.webcat.eclipse.cxxtest.xml.testresults.DocumentContext; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.internal.ui.util.EditorUtility; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ListenerList; import org.eclipse.debug.core.ILaunch; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.util.OpenStrategy; import org.eclipse.jface.viewers.IFontProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledFormText; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.MarkerUtilities; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; /** * A tab that displays a hierarchical view of the test suites that were * executed by CxxTest. * * Greatly influenced by the same JUnit class. * * @author Tony Allevato (Virginia Tech Computer Science) * @author latest changes by: $Author$ * @version $Revision$ $Date$ */ @SuppressWarnings("restriction") public class TestHierarchyTab extends TestRunTab implements IMenuListener, ISelectionProvider { private StackLayout stackLayout; private TreeViewer viewer; private FormToolkit toolkit; private ScrolledFormText errorMsgField; private TestSuiteContentProvider viewerContent; private ICProject project; private ICxxTestSuite[] suites; private ListenerList selectionListeners= new ListenerList(); private TestRunnerViewPart testRunnerView; private final Image testOkIcon = TestRunnerViewPart.createImage("obj16/testok.gif"); //$NON-NLS-1$ private final Image testErrorIcon = TestRunnerViewPart.createImage("obj16/testerr.gif"); //$NON-NLS-1$ private final Image testFailureIcon = TestRunnerViewPart.createImage("obj16/testfail.gif"); //$NON-NLS-1$ private final Image testWarnIcon = TestRunnerViewPart.createImage("obj16/testwarn.gif"); //$NON-NLS-1$ private final Image hierarchyIcon = TestRunnerViewPart.createImage("obj16/testhier.gif"); //$NON-NLS-1$ private final Image suiteIcon = TestRunnerViewPart.createImage("obj16/tsuite.gif"); //$NON-NLS-1$ private final Image suiteErrorIcon = TestRunnerViewPart.createImage("obj16/tsuiteerror.gif"); //$NON-NLS-1$ private final Image suiteFailIcon = TestRunnerViewPart.createImage("obj16/tsuitefail.gif"); //$NON-NLS-1$ private final Image suiteWarnIcon = TestRunnerViewPart.createImage("obj16/tsuitewarn.gif"); //$NON-NLS-1$ private final Image testIcon = TestRunnerViewPart.createImage("obj16/test.gif"); //$NON-NLS-1$ private class TestSuiteContentProvider implements ITreeContentProvider { public Object[] getChildren(Object parent) { if(parent instanceof ICxxTestSuite) return ((ICxxTestSuite)parent).getChildren(false); else return null; } public Object getParent(Object element) { return ((ICxxTestBase)element).getParent(); } public boolean hasChildren(Object element) { if(element instanceof ICxxTestSuite) return true; else return false; } public Object[] getElements(Object inputElement) { return (Object[])inputElement; } public void dispose() { } public void inputChanged( Viewer viewer, Object oldInput, Object newInput) { } } private class TestSuiteLabelProvider extends LabelProvider implements IFontProvider { public String getText(Object element) { return element.toString(); } public Image getImage(Object element) { int status = ((ICxxTestBase)element).getStatus(); if(element instanceof ICxxTestSuite) { switch(status) { case ICxxTestBase.STATUS_OK: return suiteIcon; case ICxxTestBase.STATUS_WARNING: return suiteWarnIcon; case ICxxTestBase.STATUS_FAILED: return suiteFailIcon; case ICxxTestBase.STATUS_ERROR: return suiteErrorIcon; } } else if(element instanceof ICxxTestSuiteChild) { switch(status) { case ICxxTestBase.STATUS_OK: return testOkIcon; case ICxxTestBase.STATUS_WARNING: return testWarnIcon; case ICxxTestBase.STATUS_FAILED: return testFailureIcon; case ICxxTestBase.STATUS_ERROR: return testErrorIcon; } } return null; } public Font getFont(Object element) { if(element instanceof ICxxTestSuiteError) { FontData[] fd = JFaceResources.getDefaultFont().getFontData(); fd[0].setStyle(fd[0].getStyle() | SWT.BOLD); return new Font(Display.getCurrent(), fd); } else { return null; } } } private class ExpandAllAction extends Action { public ExpandAllAction() { setText(Messages.TestHierarchyTab_ExpandAllLabel); setToolTipText(Messages.TestHierarchyTab_ExpandAllTooltip); } public void run() { expandAll(); } } public void createTabControl(CTabFolder tabFolder, Clipboard clipboard, TestRunnerViewPart runner) { testRunnerView = runner; CTabItem hierarchyTab= new CTabItem(tabFolder, SWT.NONE); hierarchyTab.setText(getName()); hierarchyTab.setImage(hierarchyIcon); Composite testTreePanel= new Composite(tabFolder, SWT.NONE); stackLayout = new StackLayout(); testTreePanel.setLayout(stackLayout); GridData gridData = new GridData(SWT.FILL, SWT.FILL, false, false); testTreePanel.setLayoutData(gridData); hierarchyTab.setControl(testTreePanel); hierarchyTab.setToolTipText(Messages.TestHierarchyTab_TabTooltip); viewer = new TreeViewer(testTreePanel, SWT.V_SCROLL | SWT.SINGLE); gridData = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); viewerContent = new TestSuiteContentProvider(); viewer.setContentProvider(viewerContent); viewer.setLabelProvider(new TestSuiteLabelProvider()); viewer.getTree().setLayoutData(gridData); OpenStrategy handler = new OpenStrategy(viewer.getTree()); handler.addPostSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { fireSelectionChanged(); } }); Display display = tabFolder.getDisplay(); toolkit = new FormToolkit(display); errorMsgField = new ScrolledFormText(testTreePanel, true); errorMsgField.setBackground(toolkit.getColors().getBackground()); errorMsgField.getFormText().setColor(ERROR_COLOR_KEY, toolkit.getColors().createColor(ERROR_COLOR_KEY, 255, 0, 0)); errorMsgField.getFormText().addHyperlinkListener(new HyperlinkAdapter() { public void linkActivated(HyperlinkEvent e) { String link = e.getHref().toString(); openLink(link); } }); stackLayout.topControl = viewer.getControl(); initMenu(); addListeners(); } private void openLink(String link) { if(link.startsWith(ICxxTestConstants.TEST_RESULTS_FILE)) { String[] parts = link.split(":"); //$NON-NLS-1$ int lineNumber = 1; if(parts.length == 2) lineNumber = Integer.parseInt(parts[1]); ICProject project = testRunnerView.getLaunchedProject(); IFile file = project.getProject().getFile(ICxxTestConstants.TEST_RESULTS_FILE); try { ITextEditor editor = (ITextEditor)EditorUtility.openInEditor(file, true); try { IDocument document= editor.getDocumentProvider(). getDocument(editor.getEditorInput()); editor.selectAndReveal( document.getLineOffset(lineNumber - 1), document.getLineLength(lineNumber - 1)); } catch(BadLocationException ex) { } } catch (PartInitException ex) { } catch (CModelException ex) { } } } private void disposeIcons() { testOkIcon.dispose(); testWarnIcon.dispose(); testFailureIcon.dispose(); testErrorIcon.dispose(); hierarchyIcon.dispose(); testIcon.dispose(); suiteIcon.dispose(); suiteWarnIcon.dispose(); suiteFailIcon.dispose(); suiteErrorIcon.dispose(); toolkit.dispose(); } private void initMenu() { MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(this); testRunnerView.getSite().registerContextMenu(menuMgr, this); Menu menu= menuMgr.createContextMenu(viewer.getTree()); viewer.getTree().setMenu(menu); } public Object getSelectedObject() { IStructuredSelection sel = (IStructuredSelection)viewer.getSelection(); if(sel.size() == 0) return null; else return sel.getFirstElement(); } public String getName() { return Messages.TestHierarchyTab_TabName; } public void setSelectedTest(ICxxTestBase testObject) { viewer.setSelection(new StructuredSelection(testObject)); } private void expandFailedTests() { if(suites == null) return; for(int i = 0; i < suites.length; i++) { boolean expand = (suites[i].getStatus() != ICxxTestBase.STATUS_OK); viewer.setExpandedState(suites[i], expand); } } public void activate() { testRunnerView.handleObjectSelected(getSelectedObject()); } public void setFocus() { viewer.getTree().setFocus(); } private void addListeners() { viewer.getTree().addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { activate(); } public void widgetDefaultSelected(SelectionEvent e) { handleDoubleClick(null); } }); viewer.getTree().addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { disposeIcons(); } }); viewer.getTree().addMouseListener(new MouseAdapter() { public void mouseDoubleClick(MouseEvent e) { handleDoubleClick(e); } }); } private void handleDoubleClick(MouseEvent e) { ICxxTestBase test = (ICxxTestBase)getSelectedObject(); if(test == null) return; OpenTestAction action = new OpenTestAction(testRunnerView, test); if(action.isEnabled()) action.run(); } public void menuAboutToShow(IMenuManager manager) { IStructuredSelection selection = (IStructuredSelection)viewer.getSelection(); if(selection != null && selection.size() > 0) { ICxxTestBase test = (ICxxTestBase)selection.getFirstElement(); manager.add(new OpenTestAction(testRunnerView, test)); manager.add(new Separator()); manager.add(new Separator()); manager.add(new ExpandAllAction()); } manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS + "-end")); //$NON-NLS-1$ } private void setSuites(ICxxTestSuite[] suites) { this.suites = suites; viewer.setInput(suites); expandFailedTests(); errorMsgField.setText(""); //$NON-NLS-1$ stackLayout.topControl = viewer.getControl(); viewer.getControl().getParent().layout(); } private String escapeXMLString(String str) { StringBuffer buf = new StringBuffer(); for(int i = 0; i < str.length(); i++) { char ch = str.charAt(i); switch(ch) { case '&': buf.append("&"); break; //$NON-NLS-1$ case '\'': buf.append("'"); break; //$NON-NLS-1$ case '"': buf.append("""); break; //$NON-NLS-1$ case '<': buf.append("<"); break; //$NON-NLS-1$ case '>': buf.append(">"); break; //$NON-NLS-1$ default: buf.append(ch); break; } } return buf.toString(); } private void setParseError(Exception e) { StringBuffer msg = new StringBuffer(); msg.append("<form>"); //$NON-NLS-1$ msg.append("<p><b>"); //$NON-NLS-1$ msg.append(Messages.TestHierarchyTab_ErrorTitle); msg.append("</b></p>"); //$NON-NLS-1$ if(e instanceof SAXParseException) { SAXParseException spe = (SAXParseException) e; msg.append(MessageFormat.format( Messages.TestHierarchyTab_ErrorDescriptionWithLineNumber, ICxxTestConstants.TEST_RESULTS_FILE, spe.getLineNumber(), escapeXMLString(e.getMessage()))); } else { msg.append(MessageFormat.format( Messages.TestHierarchyTab_ErrorDescription, ICxxTestConstants.TEST_RESULTS_FILE, escapeXMLString(e.getMessage()))); } msg.append("</form>"); //$NON-NLS-1$ errorMsgField.setText(msg.toString()); stackLayout.topControl = errorMsgField; errorMsgField.getParent().layout(); } protected void expandAll() { viewer.expandAll(); } public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionListeners.add(listener); } public ISelection getSelection() { return viewer.getSelection(); } public void removeSelectionChangedListener(ISelectionChangedListener listener) { selectionListeners.remove(listener); } public void setSelection(ISelection selection) { viewer.setSelection(selection, true); } private void fireSelectionChanged() { SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection()); Object[] listeners = selectionListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { ISelectionChangedListener listener = (ISelectionChangedListener)listeners[i]; listener.selectionChanged(event); } } public void testRunStarted(ICProject project, ILaunch launch) { this.project = project; setSuites(null); } @SuppressWarnings("deprecation") public void testRunEnded() { try { IFile resultsFile = project.getProject().getFile( ICxxTestConstants.TEST_RESULTS_FILE); File resultsPath = resultsFile.getLocation().toFile(); // When the CxxTest results file is generated, Eclipse // autodetects this change in the project contents and // attempts to run the managed make process again // (which has no effect because there's nothing new to // make, but it does delete warning markers). But it // appears setting the derived flag (which does apply // here, as it's derived from the runner) will prevent // managed make from doing an extraneous run. try { resultsFile.setDerived(true); } catch (CoreException e1) { } FileInputStream stream = new FileInputStream(resultsPath); InputSource source = new InputSource(stream); DocumentContext docContext = new DocumentContext(); final ContextualSAXHandler handler = new ContextualSAXHandler(docContext); try { XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(handler); reader.parse(source); } catch (Exception e) { setParseError(e); return; } finally { stream.close(); } ICxxTestSuite[] suiteArray = docContext.getSuites(); setSuites(suiteArray); testRunnerView.setSummary(suiteArray); setFailureMarkers(suiteArray); } catch(IOException e) { setParseError(e); } } private void setFailureMarkers(ICxxTestSuite[] suites) { for(int i = 0; i < suites.length; i++) { ICxxTestSuite suite = suites[i]; ICxxTestSuiteChild[] children = suite.getChildren(true); for(int j = 0; j < children.length; j++) { ICxxTestMethod test = (ICxxTestMethod)children[j]; IFile file = project.getProject().getFile(suite.getFile()); for(int k = 0; k < test.getFailedAssertions().length; k++) { ICxxTestAssertion assertion = test.getFailedAssertions()[k]; setAssertionMarker(file, assertion); } } } } private void setAssertionMarker(IFile file, ICxxTestAssertion assertion) { try { HashMap<String, Object> attrs = new HashMap<String, Object>(); attrs.put(IMarker.MESSAGE, assertion.getMessage(false)); attrs.put(IMarker.LINE_NUMBER, new Integer(assertion.getLineNumber())); attrs.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO)); attrs.put(ICxxTestConstants.ATTR_ASSERTIONTYPE, new Integer(assertion.getStatus())); MarkerUtilities.createMarker(file, attrs, ICxxTestConstants.MARKER_FAILED_TEST); } catch(CoreException e) { } } private static final String ERROR_COLOR_KEY = "error"; //$NON-NLS-1$ }