| /******************************************************************************* |
| * Copyright (c) 2020 Red Hat Inc. and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Red Hat Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.cdt.unittest.ui; |
| |
| import java.util.HashSet; |
| |
| import org.eclipse.cdt.core.CCorePlugin; |
| import org.eclipse.cdt.core.browser.ITypeInfo; |
| import org.eclipse.cdt.core.browser.ITypeReference; |
| import org.eclipse.cdt.core.browser.IndexTypeInfo; |
| import org.eclipse.cdt.core.browser.QualifiedTypeName; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.index.IIndex; |
| import org.eclipse.cdt.core.index.IIndexBinding; |
| import org.eclipse.cdt.core.index.IIndexMacro; |
| import org.eclipse.cdt.core.index.IIndexManager; |
| import org.eclipse.cdt.core.index.IndexFilter; |
| import org.eclipse.cdt.core.model.CModelException; |
| import org.eclipse.cdt.core.model.CoreModel; |
| import org.eclipse.cdt.core.model.ICElement; |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.cdt.internal.core.browser.IndexModelUtil; |
| import org.eclipse.cdt.internal.ui.search.CSearchUtil; |
| import org.eclipse.cdt.internal.ui.util.EditorUtility; |
| import org.eclipse.cdt.unittest.CDTUnitTestPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| import org.eclipse.unittest.model.ITestCaseElement; |
| import org.eclipse.unittest.model.ITestElement; |
| import org.eclipse.unittest.model.ITestSuiteElement; |
| |
| public class OpenTestAction extends Action { |
| protected String fClassName; |
| protected String fMethodName; |
| protected Shell shell; |
| private String fSearchPrefix; |
| |
| /** |
| * Job to update the element list in the background. |
| */ |
| private class UpdateElementsJob extends Job { |
| /** |
| * The last used prefix to query the index. <code>null</code> means |
| * the query result should be empty. |
| */ |
| private volatile char[] fCurrentPrefix; |
| private Object[] result; |
| |
| public UpdateElementsJob(String name) { |
| super(name); |
| setSystem(true); |
| setUser(false); |
| setPriority(Job.LONG); |
| } |
| |
| public Object[] runQuery(String prefix) { |
| fCurrentPrefix = toPrefixChars(prefix); |
| result = null; |
| schedule(); |
| try { |
| join(); |
| } catch (InterruptedException e) { |
| return null; |
| } |
| return result; |
| } |
| |
| @Override |
| public IStatus run(final IProgressMonitor monitor) { |
| monitor.beginTask(ActionsMessages.OpenEditorAction_UpdateElementsJob_inProgress, IProgressMonitor.UNKNOWN); |
| final ITypeInfo[] elements = getElementsByPrefix(fCurrentPrefix, monitor); |
| if (elements != null && !monitor.isCanceled()) { |
| result = elements; |
| monitor.done(); |
| return Status.OK_STATUS; |
| } |
| return Status.CANCEL_STATUS; |
| } |
| |
| private char[] toPrefixChars(String filterPrefix) { |
| filterPrefix = CSearchUtil.adjustSearchStringForOperators(filterPrefix); |
| return toPrefix(filterPrefix); |
| } |
| |
| private char[] toPrefix(String userFilter) { |
| QualifiedTypeName qualifiedName = new QualifiedTypeName(userFilter); |
| if (qualifiedName.segmentCount() > 1) { |
| userFilter = qualifiedName.lastSegment(); |
| } |
| if (userFilter.endsWith("<")) { //$NON-NLS-1$ |
| userFilter = userFilter.substring(0, userFilter.length() - 1); |
| } |
| int asterisk = userFilter.indexOf("*"); //$NON-NLS-1$ |
| int questionMark = userFilter.indexOf("?"); //$NON-NLS-1$ |
| int prefixEnd = asterisk < 0 ? questionMark |
| : questionMark < 0 ? asterisk : Math.min(asterisk, questionMark); |
| return (prefixEnd == -1 ? userFilter : userFilter.substring(0, prefixEnd)).toCharArray(); |
| } |
| |
| /** |
| * Queries the elements for the given prefix. |
| * @param prefix a prefix |
| * @param monitor a progress monitor |
| * @return an array of {@link ITypeInfo}s |
| */ |
| protected ITypeInfo[] getElementsByPrefix(char[] prefix, IProgressMonitor monitor) { |
| if (monitor.isCanceled()) { |
| return null; |
| } |
| HashSet<IndexTypeInfo> types = new HashSet<>(); |
| if (prefix != null) { |
| final IndexFilter filter = new IndexFilter() { |
| @Override |
| public boolean acceptBinding(IBinding binding) throws CoreException { |
| if (isVisibleType(IndexModelUtil.getElementType(binding))) { |
| return IndexFilter.ALL_DECLARED.acceptBinding(binding); |
| } |
| return false; |
| } |
| }; |
| try { |
| IIndex index = CCorePlugin.getIndexManager().getIndex( |
| CoreModel.getDefault().getCModel().getCProjects(), |
| IIndexManager.ADD_EXTENSION_FRAGMENTS_NAVIGATION); |
| index.acquireReadLock(); |
| try { |
| IIndexBinding[] bindings = index.findBindingsForPrefix(prefix, false, filter, monitor); |
| for (int i = 0; i < bindings.length; i++) { |
| if (i % 0x1000 == 0 && monitor.isCanceled()) { |
| return null; |
| } |
| final IndexTypeInfo typeinfo = IndexTypeInfo.create(index, bindings[i]); |
| types.add(typeinfo); |
| } |
| |
| if (isVisibleType(ICElement.C_MACRO)) { |
| IIndexMacro[] macros = index.findMacrosForPrefix(prefix, IndexFilter.ALL_DECLARED, monitor); |
| for (int i = 0; i < macros.length; i++) { |
| if (i % 0x1000 == 0 && monitor.isCanceled()) { |
| return null; |
| } |
| final IndexTypeInfo typeinfo = IndexTypeInfo.create(index, macros[i]); |
| types.add(typeinfo); |
| } |
| } |
| } finally { |
| index.releaseReadLock(); |
| } |
| } catch (CoreException | InterruptedException e) { |
| CDTUnitTestPlugin.log(e); |
| } |
| } |
| return types.toArray(new ITypeInfo[types.size()]); |
| } |
| |
| /** |
| * Answer whether the given type is a class or a struct. |
| * |
| * @param type the type constant, see {@link ICElement} |
| * @return <code>true</code> if the given type is visible, |
| * <code>false</code> otherwise |
| */ |
| protected boolean isVisibleType(int type) { |
| return ICElement.C_CLASS == type || ICElement.C_STRUCT == type; |
| } |
| } |
| |
| public OpenTestAction(Shell shell, ITestSuiteElement testSuite) { |
| this(shell, testSuite, null); |
| } |
| |
| public OpenTestAction(Shell shell, ITestSuiteElement testSuite, ITestCaseElement testCase) { |
| super(ActionsMessages.OpenEditorAction_action_label); |
| this.shell = shell; |
| this.fClassName = getClassName(testSuite); |
| this.fMethodName = testCase != null ? getTestMethodName(testCase) : "*"; //$NON-NLS-1$ |
| this.fSearchPrefix = new StringBuilder(fClassName).append('_').append(fMethodName).append("_Test").toString(); //$NON-NLS-1$ |
| } |
| |
| /* |
| * @see IAction#run() |
| */ |
| @Override |
| public void run() { |
| UpdateElementsJob searchJob = new UpdateElementsJob(ActionsMessages.OpenEditorAction_UpdateElementsJob_name); |
| Object[] result = searchJob.runQuery(fSearchPrefix); |
| |
| Object infoObject = result != null && result.length > 0 ? result[0] : null; |
| ITypeInfo info = infoObject instanceof ITypeInfo ? (ITypeInfo) infoObject : null; |
| ITypeReference location = info != null ? info.getResolvedReference() : null; |
| |
| if (location == null) { |
| MessageDialog.openError(getShell(), ActionsMessages.OpenEditorAction_error_cannotopen_title, |
| ActionsMessages.OpenEditorAction_error_cannotopen_message); |
| } else if (!openTypeInEditor(location)) { |
| // Error opening editor. |
| String title = ActionsMessages.OpenEditorAction_error_cannotopen_title; |
| String message = NLS.bind(ActionsMessages.OpenEditorAction_errorOpenEditor, location.getPath().toString()); |
| MessageDialog.openError(getShell(), title, message); |
| } |
| } |
| |
| protected Shell getShell() { |
| return shell; |
| } |
| |
| /** |
| * Opens an editor and displays the selected type. |
| * |
| * @param location a location reference |
| * @return true if successfully displayed. |
| */ |
| private boolean openTypeInEditor(ITypeReference location) { |
| ICElement[] cElements = location.getCElements(); |
| try { |
| if (cElements != null && cElements.length > 0) { |
| IEditorPart editor = EditorUtility.openInEditor(cElements[0]); |
| EditorUtility.revealInEditor(editor, cElements[0]); |
| return true; |
| } |
| ITranslationUnit unit = location.getTranslationUnit(); |
| IEditorPart editorPart = null; |
| |
| if (unit != null) |
| editorPart = EditorUtility.openInEditor(unit); |
| if (editorPart == null) { |
| // Open as external file. |
| editorPart = EditorUtility.openInEditor(location.getLocation(), null); |
| } |
| |
| // Highlight the type in the editor. |
| if (editorPart != null && editorPart instanceof ITextEditor) { |
| ITextEditor editor = (ITextEditor) editorPart; |
| if (location.isLineNumber()) { |
| IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); |
| try { |
| int startOffset = document.getLineOffset(location.getOffset() - 1); |
| int length = document.getLineLength(location.getOffset() - 1); |
| editor.selectAndReveal(startOffset, length); |
| return true; |
| } catch (BadLocationException ble) { |
| return false; |
| } |
| } |
| editor.selectAndReveal(location.getOffset(), location.getLength()); |
| return true; |
| } |
| } catch (CModelException ex) { |
| CDTUnitTestPlugin.log(ex); |
| return false; |
| } catch (PartInitException ex) { |
| CDTUnitTestPlugin.log(ex); |
| return false; |
| } |
| |
| return false; |
| } |
| |
| public String getClassName(ITestElement test) { |
| return extractClassName(test.getTestName()); |
| } |
| |
| private static String extractClassName(String testNameString) { |
| testNameString = extractRawClassName(testNameString); |
| testNameString = testNameString.replace('$', '.'); // see bug 178503 |
| return testNameString; |
| } |
| |
| /** |
| * Extracts and returns a raw class name from a test element name |
| * |
| * @param testNameString a test element name |
| * |
| * @return an extracted raw class name |
| */ |
| public static String extractRawClassName(String testNameString) { |
| if (testNameString.startsWith("[") && testNameString.endsWith("]")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| // a group of parameterized tests, see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 |
| return testNameString; |
| } |
| int index = testNameString.lastIndexOf('('); |
| if (index < 0) |
| return testNameString; |
| int end = testNameString.lastIndexOf(')'); |
| testNameString = testNameString.substring(index + 1, end > index ? end : testNameString.length()); |
| return testNameString; |
| } |
| |
| private String getTestMethodName(ITestElement test) { |
| String testName = test.getTestName(); |
| int index = testName.lastIndexOf('('); |
| if (index > 0) |
| return testName.substring(0, index); |
| index = testName.indexOf('@'); |
| if (index > 0) |
| return testName.substring(0, index); |
| return testName; |
| } |
| |
| } |