mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'master' of git@git.labs.intellij.net:idea/community
This commit is contained in:
@@ -45,6 +45,7 @@ import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import java.awt.*;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.*;
|
||||
@@ -394,6 +395,9 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
finally {
|
||||
if (needUpdate) {
|
||||
updateOrderEntriesInEditors();
|
||||
@@ -433,6 +437,9 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
finally {
|
||||
if (needUpdate) {
|
||||
updateOrderEntriesInEditors();
|
||||
@@ -450,12 +457,17 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable {
|
||||
}
|
||||
|
||||
public Object invoke(Object object, Method method, Object[] params) throws Throwable {
|
||||
final Object result = method.invoke(myDelegateLibrary, unwrapParams(params));
|
||||
if (result instanceof Library.ModifiableModel) {
|
||||
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.ModifiableModel.class},
|
||||
new LibraryModifiableModelInvocationHandler((Library.ModifiableModel)result));
|
||||
try {
|
||||
final Object result = method.invoke(myDelegateLibrary, unwrapParams(params));
|
||||
if (result instanceof Library.ModifiableModel) {
|
||||
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Library.ModifiableModel.class},
|
||||
new LibraryModifiableModelInvocationHandler((Library.ModifiableModel)result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object getDelegate() {
|
||||
@@ -475,6 +487,9 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable {
|
||||
try {
|
||||
return method.invoke(myDelegateModel, unwrapParams(params));
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
finally {
|
||||
if (needUpdate) {
|
||||
updateOrderEntriesInEditors();
|
||||
@@ -510,6 +525,9 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
finally {
|
||||
if (needUpdate) {
|
||||
updateOrderEntriesInEditors();
|
||||
|
||||
+15
-1
@@ -30,12 +30,17 @@ import com.intellij.openapi.project.ProjectBundle;
|
||||
import com.intellij.openapi.projectRoots.ui.Util;
|
||||
import com.intellij.openapi.roots.AnnotationOrderRootType;
|
||||
import com.intellij.openapi.roots.JavadocOrderRootType;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
|
||||
import com.intellij.openapi.roots.impl.libraries.LibraryImpl;
|
||||
import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil;
|
||||
import com.intellij.openapi.roots.libraries.Library;
|
||||
import com.intellij.openapi.roots.libraries.LibraryTable;
|
||||
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
|
||||
import com.intellij.openapi.roots.libraries.LibraryUtil;
|
||||
import com.intellij.openapi.roots.ui.configuration.LibraryTableModifiableModelProvider;
|
||||
import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
|
||||
import com.intellij.openapi.roots.ui.configuration.PathUIUtils;
|
||||
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
|
||||
import com.intellij.openapi.roots.ui.configuration.projectRoot.BaseLibrariesConfigurable;
|
||||
@@ -72,6 +77,8 @@ import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
@@ -1057,7 +1064,14 @@ public class LibraryTableEditor implements Disposable, LibraryEditorListener {
|
||||
final NullableComputable<Library.ModifiableModel> computable = new NullableComputable<Library.ModifiableModel>() {
|
||||
public Library.ModifiableModel compute() {
|
||||
if (myTreeBuilder == null) {
|
||||
final Library.ModifiableModel model = library.getModifiableModel();
|
||||
// The following lines were born in severe pain & suffering, please respect
|
||||
final InvocationHandler invocationHandler = Proxy.isProxyClass(library.getClass())? Proxy.getInvocationHandler(library) : null;
|
||||
final Library realLibrary = invocationHandler instanceof ModuleEditor.ProxyDelegateAccessor? (Library)((ModuleEditor.ProxyDelegateAccessor)invocationHandler)
|
||||
.getDelegate() : library;
|
||||
final Module module = realLibrary instanceof LibraryImpl && ((LibraryImpl)realLibrary).isDisposed()? ((LibraryImpl)realLibrary).getModule() : null;
|
||||
if (module != null && module.isDisposed()) return null; // no way
|
||||
final Library targetLibrary = module != null? LibraryUtil.findLibrary(module, realLibrary.getName()) : realLibrary;
|
||||
final Library.ModifiableModel model = targetLibrary.getModifiableModel();
|
||||
modelRef.set(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
+7
-1
@@ -8,9 +8,12 @@ import com.intellij.openapi.roots.impl.libraries.LibraryImpl;
|
||||
import com.intellij.openapi.roots.libraries.Library;
|
||||
import com.intellij.openapi.roots.libraries.LibraryTable;
|
||||
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
|
||||
import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
|
||||
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -58,7 +61,10 @@ public class LibraryProjectStructureElement extends ProjectStructureElement {
|
||||
|
||||
@NotNull
|
||||
private Library getSourceOrThis() {
|
||||
final Library source = ((LibraryImpl)myLibrary).getSource();
|
||||
final InvocationHandler invocationHandler = Proxy.isProxyClass(myLibrary.getClass()) ? Proxy.getInvocationHandler(myLibrary) : null;
|
||||
final Library realLibrary = invocationHandler instanceof ModuleEditor.ProxyDelegateAccessor ?
|
||||
(Library)((ModuleEditor.ProxyDelegateAccessor)invocationHandler).getDelegate() : myLibrary;
|
||||
final Library source = realLibrary instanceof LibraryImpl? ((LibraryImpl)realLibrary).getSource() : null;
|
||||
return source != null ? source : myLibrary;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -98,12 +98,12 @@ public class IdentifierHighlighterPass extends TextEditorHighlightingPass {
|
||||
final PsiElement finalMyTarget = myTarget;
|
||||
ReferencesSearch.search(myTarget, new LocalSearchScope(myFile)).forEach(new Processor<PsiReference>() {
|
||||
public boolean process(final PsiReference psiReference) {
|
||||
final TextRange textRange = HighlightUsagesHandler.getRangeToHighlight(psiReference);
|
||||
final List<TextRange> textRanges = HighlightUsagesHandler.getRangesToHighlight(psiReference);
|
||||
if (detector == null || detector.getReferenceAccess(finalMyTarget, psiReference) == ReadWriteAccessDetector.Access.Read) {
|
||||
myReadAccessRanges.add(textRange);
|
||||
myReadAccessRanges.addAll(textRanges);
|
||||
}
|
||||
else {
|
||||
myWriteAccessRanges.add(textRange);
|
||||
myWriteAccessRanges.addAll(textRanges);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+12
-9
@@ -365,19 +365,22 @@ public class HighlightUsagesHandler extends HighlightHandlerBase {
|
||||
TextAttributes attributes, boolean clearHighlights) {
|
||||
List<TextRange> textRanges = new ArrayList<TextRange>(refs.size());
|
||||
for (PsiReference ref : refs) {
|
||||
TextRange range = getRangeToHighlight(ref);
|
||||
textRanges.add(range);
|
||||
textRanges.addAll(getRangesToHighlight(ref));
|
||||
}
|
||||
highlightRanges(highlightManager, editor, attributes, clearHighlights, textRanges);
|
||||
}
|
||||
|
||||
public static TextRange getRangeToHighlight(final PsiReference ref) {
|
||||
PsiElement element = ref.getElement();
|
||||
TextRange rangeInElement = ref.getRangeInElement();
|
||||
TextRange range = element.getTextRange().cutOut(rangeInElement);
|
||||
// injection occurs
|
||||
range = InjectedLanguageManager.getInstance(element.getProject()).injectedToHost(element, range);
|
||||
return range;
|
||||
public static List<TextRange> getRangesToHighlight(final PsiReference ref) {
|
||||
final List<TextRange> relativeRanges = ReferenceRange.getRanges(ref);
|
||||
List<TextRange> answer = new ArrayList<TextRange>(relativeRanges.size());
|
||||
for (TextRange relativeRange : relativeRanges) {
|
||||
PsiElement element = ref.getElement();
|
||||
TextRange range = element.getTextRange().cutOut(relativeRange);
|
||||
// injection occurs
|
||||
answer.add(InjectedLanguageManager.getInstance(element.getProject()).injectedToHost(element, range));
|
||||
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+29
-12
@@ -37,7 +37,6 @@ import com.intellij.execution.ui.ConsoleView;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Roman Chernyatchik
|
||||
@@ -57,25 +56,34 @@ public class SMTestRunnerConnectionUtil {
|
||||
* just override "execute" method of your custom command line state and return
|
||||
* test runner's console.
|
||||
*
|
||||
* NB: For debug purposes please enable "debug mode". In this mode test runner will also validate
|
||||
* consistency of test events communication protocol and throw assertion errors. To enable debug mode
|
||||
* please set system property idea.smrunner.debug=true
|
||||
*
|
||||
* @param processHandler Process handler
|
||||
* @param consoleProperties Console properties for test console actions
|
||||
* @param testFrameworkName Is used to store(project level) latest value of testTree/consoleTab splitter and other settings
|
||||
* and also will be mentioned in debug diagnostics
|
||||
* @return Console view
|
||||
* @throws ExecutionException If IDEA cannot execute process this Exception will
|
||||
* be catched and shown in error message box
|
||||
* be caught and shown in error message box
|
||||
*/
|
||||
public static BaseTestsOutputConsoleView attachRunner(@NotNull final ProcessHandler processHandler,
|
||||
@NotNull final TestConsoleProperties consoleProperties,
|
||||
final RunnerSettings runnerSettings,
|
||||
final ConfigurationPerRunnerSettings configurationSettings,
|
||||
final String splitterPropertyName) throws ExecutionException {
|
||||
@NotNull final String testFrameworkName) throws ExecutionException {
|
||||
|
||||
// Console
|
||||
final String splitterPropertyName = testFrameworkName + ".Splitter.Proportion";
|
||||
final SMTRunnerConsoleView testRunnerConsole = new SMTRunnerConsoleView(consoleProperties, runnerSettings, configurationSettings, splitterPropertyName);
|
||||
testRunnerConsole.initUI();
|
||||
final SMTestRunnerResultsForm resultsViewer = testRunnerConsole.getResultsViewer();
|
||||
|
||||
// attach listeners
|
||||
attachEventsProcessors(consoleProperties, resultsViewer, resultsViewer.getStatisticsPane(), processHandler);
|
||||
attachEventsProcessors(consoleProperties, resultsViewer,
|
||||
resultsViewer.getStatisticsPane(),
|
||||
processHandler, testFrameworkName);
|
||||
testRunnerConsole.attachToProcess(processHandler);
|
||||
|
||||
return testRunnerConsole;
|
||||
@@ -113,22 +121,29 @@ public class SMTestRunnerConnectionUtil {
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* NB: For debug purposes please enable "debug mode". In this mode test runner will also validate
|
||||
* consistency of test events communication protocol and throw assertion errors. To enable debug mode
|
||||
* please set system property idea.smrunner.debug=true
|
||||
*
|
||||
* @param processHandler Process handler
|
||||
* @param commandLineState Command line state
|
||||
* @param config User run configuration settings
|
||||
* @param splitterPropertyName This property will be used for storing splitter position.
|
||||
* @param testFrameworkName Is used to store(project level) latest value of testTree/consoleTab splitter and other settings
|
||||
* @return Console view
|
||||
* @throws ExecutionException If IDEA cannot execute process this Exception will
|
||||
* be catched and shown in error message box
|
||||
* be caught and shown in error message box
|
||||
*/
|
||||
public static ConsoleView attachRunner(@NotNull final ProcessHandler processHandler,
|
||||
@NotNull final CommandLineState commandLineState,
|
||||
@NotNull final RuntimeConfiguration config,
|
||||
@Nullable final String splitterPropertyName) throws ExecutionException {
|
||||
final TestConsoleProperties consoleProperties = new SMTRunnerConsoleProperties(config);
|
||||
@NotNull final String testFrameworkName) throws ExecutionException {
|
||||
// final String testFrameworkName
|
||||
final TestConsoleProperties consoleProperties = new SMTRunnerConsoleProperties(config, testFrameworkName);
|
||||
|
||||
return attachRunner(processHandler, consoleProperties,
|
||||
commandLineState.getRunnerSettings(), commandLineState.getConfigurationSettings(), splitterPropertyName);
|
||||
commandLineState.getRunnerSettings(),
|
||||
commandLineState.getConfigurationSettings(), testFrameworkName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,14 +159,16 @@ public class SMTestRunnerConnectionUtil {
|
||||
private static ProcessHandler attachEventsProcessors(@NotNull final TestConsoleProperties consoleProperties,
|
||||
final SMTestRunnerResultsForm resultsViewer,
|
||||
final StatisticsPanel statisticsPane,
|
||||
final ProcessHandler processHandler)
|
||||
final ProcessHandler processHandler,
|
||||
@NotNull final String testFrameworkName)
|
||||
throws ExecutionException {
|
||||
|
||||
//build messages consumer
|
||||
final OutputToGeneralTestEventsConverter outputConsumer = new OutputToGeneralTestEventsConverter();
|
||||
final OutputToGeneralTestEventsConverter outputConsumer = new OutputToGeneralTestEventsConverter(testFrameworkName);
|
||||
|
||||
//events processor
|
||||
final GeneralToSMTRunnerEventsConvertor eventsProcessor = new GeneralToSMTRunnerEventsConvertor(resultsViewer.getTestsRootNode());
|
||||
final GeneralToSMTRunnerEventsConvertor eventsProcessor = new GeneralToSMTRunnerEventsConvertor(resultsViewer.getTestsRootNode(),
|
||||
testFrameworkName);
|
||||
|
||||
// ui actions
|
||||
final SMTRunnerUIActionsHandler uiActionsHandler = new SMTRunnerUIActionsHandler(consoleProperties);
|
||||
|
||||
+28
-10
@@ -31,18 +31,35 @@ import org.jetbrains.annotations.Nullable;
|
||||
public interface GeneralTestEventsProcessor extends Disposable {
|
||||
void onTestsCountInSuite(final int count);
|
||||
|
||||
void onTestStarted(final String testName, @Nullable final String locationUrl);
|
||||
void onTestFinished(final String testName, final int duration);
|
||||
void onTestFailure(final String testName, final String localizedMessage, final String stackTrace,
|
||||
void onTestStarted(@NotNull final String testName,
|
||||
@Nullable final String locationUrl);
|
||||
|
||||
void onTestFinished(@NotNull final String testName,
|
||||
final int duration);
|
||||
|
||||
void onTestFailure(@NotNull final String testName,
|
||||
@NotNull final String localizedMessage,
|
||||
@Nullable final String stackTrace,
|
||||
final boolean testError);
|
||||
void onTestIgnored(final String testName, final String ignoreComment, @Nullable final String stackTrace);
|
||||
void onTestOutput(final String testName, final String text, final boolean stdOut);
|
||||
|
||||
void onSuiteStarted(final String suiteName, @Nullable final String locationUrl);
|
||||
void onSuiteFinished(final String suiteName);
|
||||
void onTestIgnored(@NotNull final String testName,
|
||||
@NotNull final String ignoreComment,
|
||||
@Nullable final String stackTrace);
|
||||
|
||||
void onUncapturedOutput(final String text, final Key outputType);
|
||||
void onError(@NotNull final String localizedMessage, @Nullable final String stackTrace);
|
||||
void onTestOutput(@NotNull final String testName,
|
||||
@NotNull final String text,
|
||||
final boolean stdOut);
|
||||
|
||||
void onSuiteStarted(@NotNull final String suiteName,
|
||||
@Nullable final String locationUrl);
|
||||
|
||||
void onSuiteFinished(@NotNull final String suiteName);
|
||||
|
||||
void onUncapturedOutput(@NotNull final String text,
|
||||
final Key outputType);
|
||||
|
||||
void onError(@NotNull final String localizedMessage,
|
||||
@Nullable final String stackTrace);
|
||||
|
||||
// Custom progress statistics
|
||||
|
||||
@@ -51,7 +68,8 @@ public interface GeneralTestEventsProcessor extends Disposable {
|
||||
* If name is null statistics will be switched to normal mode
|
||||
* @param testCount - 0 will be considered as unknown tests number
|
||||
*/
|
||||
void onCustomProgressTestsCategory(@Nullable final String categoryName, final int testCount);
|
||||
void onCustomProgressTestsCategory(@Nullable final String categoryName,
|
||||
final int testCount);
|
||||
void onCustomProgressTestStarted();
|
||||
void onCustomProgressTestFailed();
|
||||
}
|
||||
+107
-40
@@ -18,6 +18,7 @@ package com.intellij.execution.testframework.sm.runner;
|
||||
import com.intellij.execution.process.ProcessOutputTypes;
|
||||
import com.intellij.execution.testframework.AbstractTestProxy;
|
||||
import com.intellij.execution.testframework.sm.SMRunnerUtil;
|
||||
import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -43,10 +44,13 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
private final TestSuiteStack mySuitesStack = new TestSuiteStack();
|
||||
private final List<SMTRunnerEventsListener> myEventsListeners = new ArrayList<SMTRunnerEventsListener>();
|
||||
private final SMTestProxy myTestsRootNode;
|
||||
private final String myTestFrameworkName;
|
||||
private boolean myIsTestingFinished;
|
||||
|
||||
public GeneralToSMTRunnerEventsConvertor(@NotNull final SMTestProxy testsRootNode) {
|
||||
public GeneralToSMTRunnerEventsConvertor(@NotNull final SMTestProxy testsRootNode,
|
||||
@NotNull final String testFrameworkName) {
|
||||
myTestsRootNode = testsRootNode;
|
||||
myTestFrameworkName = testFrameworkName;
|
||||
}
|
||||
|
||||
public void addEventsListener(final SMTRunnerEventsListener listener) {
|
||||
@@ -92,15 +96,21 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onTestStarted(final String testName, final String locationUrl) {
|
||||
public void onTestStarted(@NotNull final String testName,
|
||||
@Nullable final String locationUrl) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final String fullName = getFullTestName(testName);
|
||||
|
||||
if (myRunningTestsFullNameToProxy.containsKey(fullName)) {
|
||||
//Dublicated event
|
||||
LOG.warn("Test [" + fullName + "] has been already started");
|
||||
return;
|
||||
//Duplicated event
|
||||
final boolean inDebugMode = SMTestRunnerConnectionUtil.isInDebugMode();
|
||||
logProblem("Test [" + fullName + "] has been already started",
|
||||
inDebugMode);
|
||||
|
||||
if (inDebugMode) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final SMTestProxy parentSuite = getCurrentSuite();
|
||||
@@ -120,7 +130,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onSuiteStarted(final String suiteName, @Nullable final String locationUrl) {
|
||||
public void onSuiteStarted(@NotNull final String suiteName, @Nullable final String locationUrl) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final SMTestProxy parentSuite = getCurrentSuite();
|
||||
@@ -139,14 +149,16 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onTestFinished(final String testName, final int duration) {
|
||||
public void onTestFinished(@NotNull final String testName,
|
||||
@Nullable final int duration) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final String fullTestName = getFullTestName(testName);
|
||||
final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
|
||||
|
||||
if (testProxy == null) {
|
||||
LOG.warn("Test wasn't started! TestFinished event: name = {" + testName + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName));
|
||||
logProblem("Test wasn't started! TestFinished event: name = {" + testName + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,7 +172,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onSuiteFinished(final String suiteName) {
|
||||
public void onSuiteFinished(@NotNull final String suiteName) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final SMTestProxy mySuite = mySuitesStack.popSuite(suiteName);
|
||||
@@ -174,7 +186,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onUncapturedOutput(final String text, final Key outputType) {
|
||||
public void onUncapturedOutput(@NotNull final String text, final Key outputType) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final SMTestProxy currentProxy = findCurrentTestOrSuite();
|
||||
@@ -225,27 +237,50 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onTestFailure(final String testName,
|
||||
final String localizedMessage, final String stackTrace,
|
||||
public void onTestFailure(@NotNull final String testName,
|
||||
@NotNull final String localizedMessage,
|
||||
@Nullable final String stackTrace,
|
||||
final boolean isTestError) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final boolean inDebugMode = SMTestRunnerConnectionUtil.isInDebugMode();
|
||||
|
||||
final String fullTestName = getFullTestName(testName);
|
||||
final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
|
||||
SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
|
||||
if (testProxy == null) {
|
||||
LOG.warn("Test wasn't started! TestFailure event: name = {" + testName + "}" +
|
||||
", message = {" + localizedMessage + "}" +
|
||||
", stackTrace = {" + stackTrace + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName));
|
||||
return;
|
||||
}
|
||||
// if wasn't processed
|
||||
if (myFailedTestsSet.contains(testProxy)) {
|
||||
// dublicate message
|
||||
LOG.warn("Duplicate failure for test [" + fullTestName + "]: msg = " + localizedMessage + ", stacktrace = " + stackTrace);
|
||||
return;
|
||||
logProblem("Test wasn't started! TestFailure event: name = {" + testName + "}" +
|
||||
", message = {" + localizedMessage + "}" +
|
||||
", stackTrace = {" + stackTrace + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName),
|
||||
inDebugMode);
|
||||
if (inDebugMode) {
|
||||
return;
|
||||
} else {
|
||||
// try to fix the problem:
|
||||
if (!myFailedTestsSet.contains(testProxy)) {
|
||||
// if hasn't been already reported
|
||||
// 1. report
|
||||
onTestStarted(testName, null);
|
||||
// 2. add failure
|
||||
testProxy = getProxyByFullTestName(fullTestName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if has been already processed
|
||||
if (myFailedTestsSet.contains(testProxy)) {
|
||||
// duplicate message
|
||||
logProblem("Duplicate failure for test [" + fullTestName + "]: msg = " + localizedMessage + ", stacktrace = " + stackTrace,
|
||||
inDebugMode);
|
||||
|
||||
if (inDebugMode) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (testProxy == null) {
|
||||
return;
|
||||
}
|
||||
testProxy.setTestFailed(localizedMessage, stackTrace, isTestError);
|
||||
|
||||
|
||||
@@ -257,20 +292,35 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onTestIgnored(final String testName, final String ignoreComment,
|
||||
public void onTestIgnored(@NotNull final String testName,
|
||||
@NotNull final String ignoreComment,
|
||||
@Nullable final String stackTrace) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final String fullTestName = getFullTestName(testName);
|
||||
final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
|
||||
SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
|
||||
if (testProxy == null) {
|
||||
final boolean debugMode = SMTestRunnerConnectionUtil.isInDebugMode();
|
||||
logProblem("Test wasn't started! " +
|
||||
"TestIgnored event: name = {" + testName + "}, " +
|
||||
"message = {" + ignoreComment + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName),
|
||||
debugMode);
|
||||
if (debugMode) {
|
||||
return;
|
||||
} else {
|
||||
// try to fix
|
||||
// 1. report test opened
|
||||
onTestStarted(testName, null);
|
||||
|
||||
// 2. report failure
|
||||
testProxy = getProxyByFullTestName(fullTestName);
|
||||
}
|
||||
|
||||
}
|
||||
if (testProxy == null) {
|
||||
LOG.warn("Test wasn't started! " +
|
||||
"TestIgnored event: name = {" + testName + "}, " +
|
||||
"message = {" + ignoreComment + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName));
|
||||
return;
|
||||
}
|
||||
|
||||
testProxy.setTestIgnored(ignoreComment, stackTrace);
|
||||
|
||||
// fire event
|
||||
@@ -279,17 +329,17 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
});
|
||||
}
|
||||
|
||||
public void onTestOutput(final String testName,
|
||||
final String text, final boolean stdOut) {
|
||||
public void onTestOutput(@NotNull final String testName,
|
||||
@NotNull final String text, final boolean stdOut) {
|
||||
SMRunnerUtil.addToInvokeLater(new Runnable() {
|
||||
public void run() {
|
||||
final String fullTestName = getFullTestName(testName);
|
||||
final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
|
||||
if (testProxy == null) {
|
||||
LOG.warn("Test wasn't started! TestOutput event: name = {" + testName + "}, " +
|
||||
"isStdOut = " + stdOut + ", " +
|
||||
"text = {" + text + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName));
|
||||
logProblem("Test wasn't started! TestOutput event: name = {" + testName + "}, " +
|
||||
"isStdOut = " + stdOut + ", " +
|
||||
"text = {" + text + "}. " +
|
||||
cannotFindFullTestNameMsg(fullTestName));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,7 +370,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
|
||||
// current suite shouldn't be null otherwise test runner isn't correct
|
||||
// or may be we are in debug mode
|
||||
LOG.warn("Current suite is undefined. Root suite will be used.");
|
||||
logProblem("Current suite is undefined. Root suite will be used.");
|
||||
return myTestsRootNode;
|
||||
|
||||
}
|
||||
@@ -449,7 +499,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
if (!myRunningTestsFullNameToProxy.isEmpty()) {
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
|
||||
LOG.warn("Not all events were processed! " + dumpRunningTestsNames());
|
||||
logProblem("Not all events were processed! " + dumpRunningTestsNames());
|
||||
}
|
||||
}
|
||||
myRunningTestsFullNameToProxy.clear();
|
||||
@@ -473,4 +523,21 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
|
||||
}
|
||||
return currentProxy;
|
||||
}
|
||||
|
||||
public static String getTFrameworkPrefix(final String testFrameworkName) {
|
||||
return "[" + testFrameworkName + "]: ";
|
||||
}
|
||||
|
||||
private void logProblem(final String msg) {
|
||||
logProblem(msg, SMTestRunnerConnectionUtil.isInDebugMode());
|
||||
}
|
||||
private void logProblem(final String msg, boolean throwError) {
|
||||
final String text = getTFrameworkPrefix(myTestFrameworkName) + msg;
|
||||
if (throwError) {
|
||||
LOG.error(text);
|
||||
}
|
||||
else {
|
||||
LOG.warn(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-7
@@ -29,6 +29,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerEventsConvertor.getTFrameworkPrefix;
|
||||
|
||||
/**
|
||||
* @author Roman Chernyatchik
|
||||
*
|
||||
@@ -42,6 +44,7 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
|
||||
private GeneralTestEventsProcessor myProcessor;
|
||||
private final MyServiceMessageVisitor myServiceMessageVisitor;
|
||||
private final String myTestFrameworkName;
|
||||
|
||||
private static class OutputChunk {
|
||||
private final Key myKey;
|
||||
@@ -67,7 +70,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
|
||||
private final List<OutputChunk> myOutputChunks;
|
||||
|
||||
public OutputToGeneralTestEventsConverter() {
|
||||
public OutputToGeneralTestEventsConverter(@NotNull final String testFrameworkName) {
|
||||
myTestFrameworkName = testFrameworkName;
|
||||
myServiceMessageVisitor = new MyServiceMessageVisitor();
|
||||
myOutputChunks = new ArrayList<OutputChunk>();
|
||||
}
|
||||
@@ -165,11 +169,14 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
}
|
||||
catch (ParseException e) {
|
||||
LOG.error(e);
|
||||
|
||||
LOG.error(getTFrameworkPrefix(myTestFrameworkName) + "Parsing error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireOnTestStarted(final String testName, @Nullable final String locationUrl) {
|
||||
assertNotNull(testName);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
@@ -179,6 +186,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
|
||||
private void fireOnTestFailure(final String testName, final String localizedMessage, final String stackTrace,
|
||||
final boolean isTestError) {
|
||||
assertNotNull(testName);
|
||||
assertNotNull(localizedMessage);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
@@ -189,6 +198,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
|
||||
private void fireOnTestIgnored(final String testName, final String ignoreComment,
|
||||
@Nullable final String details) {
|
||||
assertNotNull(testName);
|
||||
assertNotNull(ignoreComment);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
@@ -198,6 +209,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
private void fireOnTestFinished(final String testName, final int duration) {
|
||||
assertNotNull(testName);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
@@ -205,7 +218,10 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
}
|
||||
|
||||
private void fireOnCustomProgressTestsCategory(@NotNull final String categoryName, int testsCount) {
|
||||
private void fireOnCustomProgressTestsCategory(final String categoryName,
|
||||
int testsCount) {
|
||||
assertNotNull(categoryName);
|
||||
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
final boolean disableCustomMode = StringUtil.isEmpty(categoryName);
|
||||
@@ -229,6 +245,9 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
private void fireOnTestOutput(final String testName, final String text, final boolean stdOut) {
|
||||
assertNotNull(testName);
|
||||
assertNotNull(text);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
@@ -237,6 +256,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
private void fireOnUncapturedOutput(final String text, final Key outputType) {
|
||||
assertNotNull(text);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
@@ -253,6 +274,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
private void fireOnSuiteStarted(final String suiteName, @Nullable final String locationUrl) {
|
||||
assertNotNull(suiteName);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
@@ -261,6 +284,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
private void fireOnSuiteFinished(final String suiteName) {
|
||||
assertNotNull(suiteName);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
if (processor != null) {
|
||||
@@ -268,8 +293,9 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
}
|
||||
|
||||
private void fireOnErrorMsg(@NotNull final String localizedMessage,
|
||||
private void fireOnErrorMsg(final String localizedMessage,
|
||||
@Nullable final String stackTrace) {
|
||||
assertNotNull(localizedMessage);
|
||||
|
||||
// local variable is used to prevent concurrent modification
|
||||
final GeneralTestEventsProcessor processor = myProcessor;
|
||||
@@ -278,6 +304,12 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNotNull(final String s) {
|
||||
if (s == null) {
|
||||
LOG.error(getTFrameworkPrefix(myTestFrameworkName) + " @NotNull value is expected.");
|
||||
}
|
||||
}
|
||||
|
||||
private class MyServiceMessageVisitor extends DefaultServiceMessageVisitor {
|
||||
@NonNls public static final String KEY_TESTS_COUNT = "testCount";
|
||||
@NonNls private static final String ATTR_KEY_TEST_ERROR = "error";
|
||||
@@ -306,14 +338,15 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String fetchTestLocation(TestSuiteStarted suiteStarted) {
|
||||
private String fetchTestLocation(final TestSuiteStarted suiteStarted) {
|
||||
final Map<String, String> attrs = suiteStarted.getAttributes();
|
||||
final String location = attrs.get(ATTR_KEY_LOCATION_URL);
|
||||
if (location == null) {
|
||||
// try old API
|
||||
final String oldLocation = attrs.get(ATTR_KEY_LOCATION_URL_OLD);
|
||||
if (oldLocation != null) {
|
||||
LOG.error("Test Runner API was changed for TeamCity 5.0 compatibility. Please use 'locationHint' attribute instead of 'location'.");
|
||||
LOG.error(getTFrameworkPrefix(myTestFrameworkName)
|
||||
+ "Test Runner API was changed for TeamCity 5.0 compatibility. Please use 'locationHint' attribute instead of 'location'.");
|
||||
return oldLocation;
|
||||
}
|
||||
return null;
|
||||
@@ -441,7 +474,7 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
try {
|
||||
count = Integer.parseInt(countStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
LOG.error(ex);
|
||||
LOG.error(getTFrameworkPrefix(myTestFrameworkName) + "Parse integer error.", ex);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
+7
-4
@@ -21,19 +21,22 @@ import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.util.config.Storage;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XDebuggerManager;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author: Roman Chernyatchik
|
||||
*/
|
||||
public class SMTRunnerConsoleProperties extends TestConsoleProperties {
|
||||
@NonNls private static final String PREFIX = "RubyTestUnitSupport.";
|
||||
private final RuntimeConfiguration myConfiguration;
|
||||
|
||||
public SMTRunnerConsoleProperties(final RuntimeConfiguration config)
|
||||
/**
|
||||
* @param config
|
||||
* @param testFrameworkName Prefix for storage which keeps runner settings. E.g. "RubyTestUnit"
|
||||
*/
|
||||
public SMTRunnerConsoleProperties(final RuntimeConfiguration config,
|
||||
final String testFrameworkName)
|
||||
{
|
||||
super(new Storage.PropertiesComponentStorage(PREFIX, PropertiesComponent.getInstance()), config.getProject());
|
||||
super(new Storage.PropertiesComponentStorage(testFrameworkName + "Support.", PropertiesComponent.getInstance()), config.getProject());
|
||||
myConfiguration = config;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -282,7 +282,8 @@ public class SMTestProxy extends CompositePrintable implements PrintableTestProx
|
||||
}
|
||||
|
||||
public void setTestFailed(@NotNull final String localizedMessage,
|
||||
@NotNull final String stackTrace, final boolean testError) {
|
||||
@Nullable final String stackTrace,
|
||||
final boolean testError) {
|
||||
myState = testError
|
||||
? new TestErrorState(localizedMessage, stackTrace)
|
||||
: new TestFailedState(localizedMessage, stackTrace);
|
||||
|
||||
+4
-1
@@ -15,11 +15,14 @@
|
||||
*/
|
||||
package com.intellij.execution.testframework.sm.runner.states;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Roman Chernyatchik
|
||||
*/
|
||||
public class TestErrorState extends TestFailedState {
|
||||
public TestErrorState(final String localizedMessage, final String stackTrace) {
|
||||
public TestErrorState(@Nullable final String localizedMessage,
|
||||
@Nullable final String stackTrace) {
|
||||
super(localizedMessage, stackTrace);
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -43,6 +43,13 @@ public class SMTRunnerConsoleView extends BaseTestsOutputConsoleView {
|
||||
this(consoleProperties, runnerSettings, configurationPerRunnerSettings, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param consoleProperties
|
||||
* @param runnerSettings
|
||||
* @param configurationPerRunnerSettings
|
||||
* @param splitterProperty Key to store(project level) latest value of testTree/consoleTab splitter. E.g. "RSpec.Splitter.Proportion"
|
||||
*/
|
||||
public SMTRunnerConsoleView(final TestConsoleProperties consoleProperties, final RunnerSettings runnerSettings,
|
||||
final ConfigurationPerRunnerSettings configurationPerRunnerSettings,
|
||||
@Nullable final String splitterProperty) {
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ public abstract class BaseSMTRunnerTestCase extends LightPlatformTestCase {
|
||||
protected TestConsoleProperties createConsoleProperties() {
|
||||
final RuntimeConfiguration runConfiguration = createRunConfiguration();
|
||||
|
||||
final TestConsoleProperties consoleProperties = new SMTRunnerConsoleProperties(runConfiguration);
|
||||
final TestConsoleProperties consoleProperties = new SMTRunnerConsoleProperties(runConfiguration, "SMRunnerTests");
|
||||
TestConsoleProperties.HIDE_PASSED_TESTS.set(consoleProperties, false);
|
||||
|
||||
return consoleProperties;
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
|
||||
myConsole = new SMTRunnerConsoleView(consoleProperties, environment.getRunnerSettings(), environment.getConfigurationSettings());
|
||||
myConsole.initUI();
|
||||
myResultsViewer = myConsole.getResultsViewer();
|
||||
myEventsProcessor = new GeneralToSMTRunnerEventsConvertor(myResultsViewer.getTestsRootNode());
|
||||
myEventsProcessor = new GeneralToSMTRunnerEventsConvertor(myResultsViewer.getTestsRootNode(), "SMTestFramework");
|
||||
myEventsProcessor.addEventsListener(myResultsViewer);
|
||||
myTreeModel = myResultsViewer.getTreeView().getModel();
|
||||
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
|
||||
myConsole.initUI();
|
||||
myResultsViewer = myConsole.getResultsViewer();
|
||||
myRootSuite = myResultsViewer.getTestsRootNode();
|
||||
myEventsProcessor = new GeneralToSMTRunnerEventsConvertor(myResultsViewer.getTestsRootNode());
|
||||
myEventsProcessor = new GeneralToSMTRunnerEventsConvertor(myResultsViewer.getTestsRootNode(), "SMTestFramework");
|
||||
|
||||
myEventsProcessor.onStartTesting();
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public class SMTestRunnerResultsFormTest extends BaseSMTRunnerTestCase {
|
||||
myConsole.initUI();
|
||||
myResultsViewer = myConsole.getResultsViewer();
|
||||
myTestsRootNode = myResultsViewer.getTestsRootNode();
|
||||
myEventsProcessor = new GeneralToSMTRunnerEventsConvertor(myResultsViewer.getTestsRootNode());
|
||||
myEventsProcessor = new GeneralToSMTRunnerEventsConvertor(myResultsViewer.getTestsRootNode(), "SMTestFramework");
|
||||
myEventsProcessor.addEventsListener(myResultsViewer);
|
||||
myTreeModel = myResultsViewer.getTreeView().getModel();
|
||||
}
|
||||
|
||||
+1
-1
@@ -638,7 +638,7 @@ public class TestsPresentationUtilTest extends BaseSMTRunnerTestCase {
|
||||
|
||||
public MyRenderer(final boolean isPaused,
|
||||
final UITestUtil.FragmentsContainer fragmentsContainer) {
|
||||
super(new SMTRunnerConsoleProperties(createRunConfiguration()) {
|
||||
super(new SMTRunnerConsoleProperties(createRunConfiguration(), "SMRunnerTests") {
|
||||
@Override
|
||||
public boolean isPaused() {
|
||||
return isPaused;
|
||||
|
||||
Reference in New Issue
Block a user