blob: e2d25171762c58c031a1918d6a960dac15967151 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}