forked from qt-creator/qt-creator
Use QXmlStreamReader to parse test run output
Using the QXmlStreamReader will be easier to extend current functionality and should be more robust than parsing on our own. Change-Id: I9e1df7083a1af7681987f3971550e19a35b29df9 Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
This commit is contained in:
committed by
David Schulz
parent
17fbe92100
commit
23bdcf77b6
@@ -23,18 +23,20 @@ namespace Autotest {
|
|||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
FaultyTestResult::FaultyTestResult(Result::Type result, const QString &description)
|
FaultyTestResult::FaultyTestResult(Result::Type result, const QString &description)
|
||||||
: TestResult(QString(), QString(), QString(), result, description)
|
{
|
||||||
|
setResult(result);
|
||||||
|
setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestResult::TestResult()
|
||||||
|
: TestResult(QString())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult::TestResult(const QString &className, const QString &testCase, const QString &dataTag,
|
TestResult::TestResult(const QString &className)
|
||||||
Result::Type result, const QString &description)
|
: m_class(className)
|
||||||
: m_class(className),
|
, m_result(Result::INVALID)
|
||||||
m_case(testCase),
|
, m_line(0)
|
||||||
m_dataTag(dataTag),
|
|
||||||
m_result(result),
|
|
||||||
m_description(description),
|
|
||||||
m_line(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -57,10 +57,8 @@ enum Type {
|
|||||||
class TestResult
|
class TestResult
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
TestResult();
|
||||||
TestResult(const QString &className = QString(), const QString &testCase = QString(),
|
TestResult(const QString &className);
|
||||||
const QString &dataTag = QString(),
|
|
||||||
Result::Type result = Result::INVALID, const QString &description = QString());
|
|
||||||
|
|
||||||
QString className() const { return m_class; }
|
QString className() const { return m_class; }
|
||||||
QString testCase() const { return m_case; }
|
QString testCase() const { return m_case; }
|
||||||
@@ -74,6 +72,8 @@ public:
|
|||||||
void setFileName(const QString &fileName) { m_file = fileName; }
|
void setFileName(const QString &fileName) { m_file = fileName; }
|
||||||
void setLine(int line) { m_line = line; }
|
void setLine(int line) { m_line = line; }
|
||||||
void setResult(Result::Type type) { m_result = type; }
|
void setResult(Result::Type type) { m_result = type; }
|
||||||
|
void setTestCase(const QString &testCase) { m_case = testCase; }
|
||||||
|
void setDataTag(const QString &dataTag) { m_dataTag = dataTag; }
|
||||||
|
|
||||||
static Result::Type resultFromString(const QString &resultString);
|
static Result::Type resultFromString(const QString &resultString);
|
||||||
static Result::Type toResultType(int rt);
|
static Result::Type toResultType(int rt);
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
#include "testresult.h"
|
#include "testresult.h"
|
||||||
|
|
||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
@@ -60,44 +61,6 @@ static QString constructSourceFilePath(const QString &path, const QString &fileP
|
|||||||
return QFileInfo(path, filePath).canonicalFilePath();
|
return QFileInfo(path, filePath).canonicalFilePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool xmlStartsWith(const QString &code, const QString &start, QString &result)
|
|
||||||
{
|
|
||||||
if (code.startsWith(start)) {
|
|
||||||
result = code.mid(start.length());
|
|
||||||
result = result.left(result.indexOf(QLatin1Char('"')));
|
|
||||||
result = result.left(result.indexOf(QLatin1String("</")));
|
|
||||||
return !result.isEmpty();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool xmlCData(const QString &code, const QString &start, QString &result)
|
|
||||||
{
|
|
||||||
if (code.startsWith(start)) {
|
|
||||||
int index = code.indexOf(QLatin1String("<![CDATA[")) + 9;
|
|
||||||
result = code.mid(index, code.indexOf(QLatin1String("]]>"), index) - index);
|
|
||||||
return !result.isEmpty();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool xmlExtractTypeFileLine(const QString &code, const QString &tagStart,
|
|
||||||
Result::Type &result, QString &file, int &line)
|
|
||||||
{
|
|
||||||
if (code.startsWith(tagStart)) {
|
|
||||||
int start = code.indexOf(QLatin1String(" type=\"")) + 7;
|
|
||||||
result = TestResult::resultFromString(
|
|
||||||
code.mid(start, code.indexOf(QLatin1Char('"'), start) - start));
|
|
||||||
start = code.indexOf(QLatin1String(" file=\"")) + 7;
|
|
||||||
file = decode(code.mid(start, code.indexOf(QLatin1Char('"'), start) - start));
|
|
||||||
start = code.indexOf(QLatin1String(" line=\"")) + 7;
|
|
||||||
line = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toInt();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// adapted from qplaintestlogger.cpp
|
// adapted from qplaintestlogger.cpp
|
||||||
static QString formatResult(double value)
|
static QString formatResult(double value)
|
||||||
{
|
{
|
||||||
@@ -146,35 +109,24 @@ static QString formatResult(double value)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool xmlExtractBenchmarkInformation(const QString &code, const QString &tagStart,
|
static QString constructBenchmarkInformation(const QString &metric, double value, int iterations)
|
||||||
QString &description)
|
|
||||||
{
|
{
|
||||||
if (code.startsWith(tagStart)) {
|
QString metricsText;
|
||||||
int start = code.indexOf(QLatin1String(" metric=\"")) + 9;
|
if (metric == QLatin1String("WalltimeMilliseconds")) // default
|
||||||
const QString metric = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start);
|
metricsText = QLatin1String("msecs");
|
||||||
start = code.indexOf(QLatin1String(" value=\"")) + 8;
|
else if (metric == QLatin1String("CPUTicks")) // -tickcounter
|
||||||
const double value = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toDouble();
|
metricsText = QLatin1String("CPU ticks");
|
||||||
start = code.indexOf(QLatin1String(" iterations=\"")) + 13;
|
else if (metric == QLatin1String("Events")) // -eventcounter
|
||||||
const int iterations = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toInt();
|
metricsText = QLatin1String("events");
|
||||||
QString metricsText;
|
else if (metric == QLatin1String("InstructionReads")) // -callgrind
|
||||||
if (metric == QLatin1String("WalltimeMilliseconds")) // default
|
metricsText = QLatin1String("instruction reads");
|
||||||
metricsText = QLatin1String("msecs");
|
else if (metric == QLatin1String("CPUCycles")) // -perf
|
||||||
else if (metric == QLatin1String("CPUTicks")) // -tickcounter
|
metricsText = QLatin1String("CPU cycles");
|
||||||
metricsText = QLatin1String("CPU ticks");
|
return QObject::tr("%1 %2 per iteration (total: %3, iterations: %4)")
|
||||||
else if (metric == QLatin1String("Events")) // -eventcounter
|
.arg(formatResult(value))
|
||||||
metricsText = QLatin1String("events");
|
.arg(metricsText)
|
||||||
else if (metric == QLatin1String("InstructionReads")) // -callgrind
|
.arg(formatResult(value * (double)iterations))
|
||||||
metricsText = QLatin1String("instruction reads");
|
.arg(iterations);
|
||||||
else if (metric == QLatin1String("CPUCycles")) // -perf
|
|
||||||
metricsText = QLatin1String("CPU cycles");
|
|
||||||
description = QObject::tr("%1 %2 per iteration (total: %3, iterations: %4)")
|
|
||||||
.arg(formatResult(value))
|
|
||||||
.arg(metricsText)
|
|
||||||
.arg(formatResult(value * (double)iterations))
|
|
||||||
.arg(iterations);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication)
|
TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication)
|
||||||
@@ -184,14 +136,24 @@ TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication)
|
|||||||
this, &TestXmlOutputReader::processOutput);
|
this, &TestXmlOutputReader::processOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
TestXmlOutputReader::~TestXmlOutputReader()
|
enum CDATAMode {
|
||||||
{
|
None,
|
||||||
}
|
DataTag,
|
||||||
|
Description,
|
||||||
|
QtVersion,
|
||||||
|
QTestVersion
|
||||||
|
};
|
||||||
|
|
||||||
void TestXmlOutputReader::processOutput()
|
void TestXmlOutputReader::processOutput()
|
||||||
{
|
{
|
||||||
if (!m_testApplication || m_testApplication->state() != QProcess::Running)
|
if (!m_testApplication || m_testApplication->state() != QProcess::Running)
|
||||||
return;
|
return;
|
||||||
|
static QStringList validEndTags = { QStringLiteral("Incident"),
|
||||||
|
QStringLiteral("Message"),
|
||||||
|
QStringLiteral("BenchmarkResult"),
|
||||||
|
QStringLiteral("QtVersion"),
|
||||||
|
QStringLiteral("QTestVersion") };
|
||||||
|
static CDATAMode cdataMode = None;
|
||||||
static QString className;
|
static QString className;
|
||||||
static QString testCase;
|
static QString testCase;
|
||||||
static QString dataTag;
|
static QString dataTag;
|
||||||
@@ -200,98 +162,136 @@ void TestXmlOutputReader::processOutput()
|
|||||||
static QString file;
|
static QString file;
|
||||||
static int lineNumber = 0;
|
static int lineNumber = 0;
|
||||||
static QString duration;
|
static QString duration;
|
||||||
static bool readingDescription = false;
|
static QXmlStreamReader xmlReader;
|
||||||
static QString qtVersion;
|
|
||||||
static QString qtestVersion;
|
|
||||||
static QString benchmarkDescription;
|
|
||||||
|
|
||||||
while (m_testApplication->canReadLine()) {
|
while (m_testApplication->canReadLine()) {
|
||||||
// TODO Qt5 uses UTF-8 - while Qt4 uses ISO-8859-1 - could this be a problem?
|
xmlReader.addData(m_testApplication->readLine());
|
||||||
const QString line = QString::fromUtf8(m_testApplication->readLine()).trimmed();
|
while (!xmlReader.atEnd()) {
|
||||||
if (line.isEmpty() || xmlStartsWith(line, QLatin1String("<TestCase name=\""), className)) {
|
QXmlStreamReader::TokenType token = xmlReader.readNext();
|
||||||
testResultCreated(new TestResult(className, QString(), QString(),
|
switch (token) {
|
||||||
Result::MESSAGE_TEST_CASE_START,
|
case QXmlStreamReader::StartDocument:
|
||||||
QObject::tr("Executing test case %1").arg(className)));
|
className.clear();
|
||||||
continue;
|
break;
|
||||||
}
|
case QXmlStreamReader::EndDocument:
|
||||||
if (line.startsWith(QLatin1String("<?xml version"))) {
|
xmlReader.clear();
|
||||||
className = QString();
|
return;
|
||||||
continue;
|
case QXmlStreamReader::StartElement: {
|
||||||
}
|
const QString currentTag = xmlReader.name().toString();
|
||||||
if (xmlStartsWith(line, QLatin1String("<TestFunction name=\""), testCase)) {
|
if (currentTag == QStringLiteral("TestCase")) {
|
||||||
dataTag = QString();
|
className = xmlReader.attributes().value(QStringLiteral("name")).toString();
|
||||||
description = QString();
|
QTC_ASSERT(!className.isEmpty(), continue);
|
||||||
duration = QString();
|
auto testResult = new TestResult(className);
|
||||||
file = QString();
|
testResult->setResult(Result::MESSAGE_TEST_CASE_START);
|
||||||
result = Result::INVALID;
|
testResult->setDescription(tr("Executing test case %1").arg(className));
|
||||||
lineNumber = 0;
|
testResultCreated(testResult);
|
||||||
readingDescription = false;
|
} else if (currentTag == QStringLiteral("TestFunction")) {
|
||||||
testResultCreated(new TestResult(QString(), QString(), QString(), Result::MESSAGE_CURRENT_TEST,
|
testCase = xmlReader.attributes().value(QStringLiteral("name")).toString();
|
||||||
QObject::tr("Entering test function %1::%2").arg(className).arg(testCase)));
|
QTC_ASSERT(!testCase.isEmpty(), continue);
|
||||||
continue;
|
auto testResult = new TestResult();
|
||||||
}
|
testResult->setResult(Result::MESSAGE_CURRENT_TEST);
|
||||||
if (xmlStartsWith(line, QLatin1String("<Duration msecs=\""), duration)) {
|
testResult->setDescription(tr("Entering test function %1::%2").arg(className,
|
||||||
continue;
|
testCase));
|
||||||
}
|
testResultCreated(testResult);
|
||||||
if (xmlExtractTypeFileLine(line, QLatin1String("<Message"), result, file, lineNumber))
|
} else if (currentTag == QStringLiteral("Duration")) {
|
||||||
continue;
|
duration = xmlReader.attributes().value(QStringLiteral("msecs")).toString();
|
||||||
if (xmlCData(line, QLatin1String("<DataTag>"), dataTag))
|
QTC_ASSERT(!duration.isEmpty(), continue);
|
||||||
continue;
|
} else if (currentTag == QStringLiteral("Message")
|
||||||
if (xmlCData(line, QLatin1String("<Description>"), description)) {
|
|| currentTag == QStringLiteral("Incident")) {
|
||||||
if (!line.endsWith(QLatin1String("</Description>")))
|
dataTag.clear();
|
||||||
readingDescription = true;
|
description.clear();
|
||||||
continue;
|
duration.clear();
|
||||||
}
|
file.clear();
|
||||||
if (xmlExtractTypeFileLine(line, QLatin1String("<Incident"), result, file, lineNumber)) {
|
result = Result::INVALID;
|
||||||
if (line.endsWith(QLatin1String("/>"))) {
|
lineNumber = 0;
|
||||||
TestResult *testResult = new TestResult(className, testCase, dataTag, result, description);
|
const QXmlStreamAttributes &attributes = xmlReader.attributes();
|
||||||
if (!file.isEmpty())
|
result = TestResult::resultFromString(
|
||||||
file = constructSourceFilePath(m_testApplication->workingDirectory(), file,
|
attributes.value(QStringLiteral("type")).toString());
|
||||||
m_testApplication->program());
|
file = decode(attributes.value(QStringLiteral("file")).toString());
|
||||||
testResult->setFileName(file);
|
if (!file.isEmpty())
|
||||||
testResult->setLine(lineNumber);
|
file = constructSourceFilePath(m_testApplication->workingDirectory(), file,
|
||||||
testResultCreated(testResult);
|
m_testApplication->program());
|
||||||
|
lineNumber = attributes.value(QStringLiteral("line")).toInt();
|
||||||
|
} else if (currentTag == QStringLiteral("BenchmarkResult")) {
|
||||||
|
const QXmlStreamAttributes &attributes = xmlReader.attributes();
|
||||||
|
const QString metric = attributes.value(QStringLiteral("metrics")).toString();
|
||||||
|
const double value = attributes.value(QStringLiteral("value")).toDouble();
|
||||||
|
const int iterations = attributes.value(QStringLiteral("iterations")).toInt();
|
||||||
|
description = constructBenchmarkInformation(metric, value, iterations);
|
||||||
|
result = Result::BENCHMARK;
|
||||||
|
} else if (currentTag == QStringLiteral("DataTag")) {
|
||||||
|
cdataMode = DataTag;
|
||||||
|
} else if (currentTag == QStringLiteral("Description")) {
|
||||||
|
cdataMode = Description;
|
||||||
|
} else if (currentTag == QStringLiteral("QtVersion")) {
|
||||||
|
result = Result::MESSAGE_INTERNAL;
|
||||||
|
cdataMode = QtVersion;
|
||||||
|
} else if (currentTag == QStringLiteral("QTestVersion")) {
|
||||||
|
result = Result::MESSAGE_INTERNAL;
|
||||||
|
cdataMode = QTestVersion;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
case QXmlStreamReader::Characters: {
|
||||||
}
|
QStringRef text = xmlReader.text().trimmed();
|
||||||
if (xmlExtractBenchmarkInformation(line, QLatin1String("<BenchmarkResult"), benchmarkDescription)) {
|
if (text.isEmpty())
|
||||||
testResultCreated(new TestResult(className, testCase, dataTag, Result::BENCHMARK,
|
break;
|
||||||
benchmarkDescription));
|
|
||||||
continue;
|
switch (cdataMode) {
|
||||||
}
|
case DataTag:
|
||||||
if (line == QLatin1String("</Message>") || line == QLatin1String("</Incident>")) {
|
dataTag = text.toString();
|
||||||
TestResult *testResult = new TestResult(className, testCase, dataTag, result, description);
|
break;
|
||||||
if (!file.isEmpty())
|
case Description:
|
||||||
file = constructSourceFilePath(m_testApplication->workingDirectory(), file,
|
if (!description.isEmpty())
|
||||||
m_testApplication->program());
|
description.append(QLatin1Char('\n'));
|
||||||
testResult->setFileName(file);
|
description.append(text);
|
||||||
testResult->setLine(lineNumber);
|
break;
|
||||||
testResultCreated(testResult);
|
case QtVersion:
|
||||||
description = QString();
|
description = tr("Qt version: %1").arg(text.toString());
|
||||||
} else if (line == QLatin1String("</TestFunction>") && !duration.isEmpty()) {
|
break;
|
||||||
testResultCreated(new TestResult(className, testCase, QString(), Result::MESSAGE_INTERNAL,
|
case QTestVersion:
|
||||||
QObject::tr("Execution took %1 ms.").arg(duration)));
|
description = tr("QTest version: %1").arg(text.toString());
|
||||||
emit increaseProgress();
|
break;
|
||||||
} else if (line == QLatin1String("</TestCase>") && !duration.isEmpty()) {
|
default:
|
||||||
testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_TEST_CASE_END,
|
QString message = QString::fromLatin1("unexpected cdatamode %1 for text \"%2\"")
|
||||||
QObject::tr("Test execution took %1 ms.").arg(duration)));
|
.arg(cdataMode)
|
||||||
} else if (readingDescription) {
|
.arg(text.toString());
|
||||||
if (line.endsWith(QLatin1String("]]></Description>"))) {
|
QTC_ASSERT(false, qWarning() << message);
|
||||||
description.append(QLatin1Char('\n'));
|
break;
|
||||||
description.append(line.left(line.indexOf(QLatin1String("]]></Description>"))));
|
}
|
||||||
readingDescription = false;
|
break;
|
||||||
} else {
|
}
|
||||||
description.append(QLatin1Char('\n'));
|
case QXmlStreamReader::EndElement: {
|
||||||
description.append(line);
|
cdataMode = None;
|
||||||
|
const QStringRef currentTag = xmlReader.name();
|
||||||
|
if (currentTag == QStringLiteral("TestFunction")) {
|
||||||
|
if (!duration.isEmpty()) {
|
||||||
|
auto testResult = new TestResult(className);
|
||||||
|
testResult->setTestCase(testCase);
|
||||||
|
testResult->setResult(Result::MESSAGE_INTERNAL);
|
||||||
|
testResult->setDescription(tr("Execution took %1 ms.").arg(duration));
|
||||||
|
testResultCreated(testResult);
|
||||||
|
}
|
||||||
|
emit increaseProgress();
|
||||||
|
} else if (currentTag == QStringLiteral("TestCase") && !duration.isEmpty()) {
|
||||||
|
auto testResult = new TestResult(className);
|
||||||
|
testResult->setResult(Result::MESSAGE_TEST_CASE_END);
|
||||||
|
testResult->setDescription(tr("Test execution took %1 ms.").arg(duration));
|
||||||
|
testResultCreated(testResult);
|
||||||
|
} else if (validEndTags.contains(currentTag.toString())) {
|
||||||
|
auto testResult = new TestResult(className);
|
||||||
|
testResult->setTestCase(testCase);
|
||||||
|
testResult->setDataTag(dataTag);
|
||||||
|
testResult->setResult(result);
|
||||||
|
testResult->setFileName(file);
|
||||||
|
testResult->setLine(lineNumber);
|
||||||
|
testResult->setDescription(description);
|
||||||
|
testResultCreated(testResult);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (xmlStartsWith(line, QLatin1String("<QtVersion>"), qtVersion)) {
|
|
||||||
testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL,
|
|
||||||
QObject::tr("Qt version: %1").arg(qtVersion)));
|
|
||||||
} else if (xmlStartsWith(line, QLatin1String("<QTestVersion>"), qtestVersion)) {
|
|
||||||
testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL,
|
|
||||||
QObject::tr("QTest version: %1").arg(qtestVersion)));
|
|
||||||
} else {
|
|
||||||
// qDebug() << "Unhandled line:" << line; // TODO remove
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,10 +24,9 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QXmlStreamReader;
|
|
||||||
class QIODevice;
|
|
||||||
class QProcess;
|
class QProcess;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
@@ -37,18 +36,18 @@ namespace Internal {
|
|||||||
class TestXmlOutputReader : public QObject
|
class TestXmlOutputReader : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TestXmlOutputReader(QProcess *testApplication);
|
TestXmlOutputReader(QProcess *testApplication);
|
||||||
~TestXmlOutputReader();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void processOutput();
|
void processOutput();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void testResultCreated(TestResult *testResult);
|
void testResultCreated(TestResult *testResult);
|
||||||
void increaseProgress();
|
void increaseProgress();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QProcess *m_testApplication;
|
QProcess *m_testApplication; // not owned
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
Reference in New Issue
Block a user