AutoTest: Re-implement catch parser

The AST handling is limited and fails for other macros
that will be added later on.
Replace the parser by using a lexer.

Change-Id: Ia11f0a05eec770c703180935a64615e5090b314c
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Stenger
2020-04-09 13:59:04 +02:00
parent d7b1adae78
commit d04597f2aa
5 changed files with 250 additions and 145 deletions

View File

@@ -24,12 +24,11 @@
#include "catchtestparser.h"
#include "catchcodeparser.h"
#include "catchtreeitem.h"
#include <cpptools/cppmodelmanager.h>
#include <cpptools/projectpart.h>
#include <cplusplus/LookupContext.h>
#include <cplusplus/Overview.h>
#include <utils/qtcassert.h>
#include <QRegularExpression>
@@ -37,12 +36,6 @@
namespace Autotest {
namespace Internal {
struct CatchTestCaseSpec
{
QString name;
QStringList tags;
};
static bool isCatchTestCaseMacro(const QString &macroName)
{
const QStringList validTestCaseMacros = {
@@ -59,123 +52,6 @@ static bool isCatchMacro(const QString &macroName)
return isCatchTestCaseMacro(macroName) || validSectionMacros.contains(macroName);
}
static inline QString stringLiteralToQString(const CPlusPlus::StringLiteral *literal)
{
return QString::fromLatin1(literal->chars(), int(literal->size()));
}
static QStringList parseTags(const QString &tagsString)
{
QStringList tagsList;
const QRegularExpression tagRegEx("\\[(.*?)\\]",QRegularExpression::CaseInsensitiveOption);
int pos = 0;
QRegularExpressionMatch it = tagRegEx.match(tagsString, pos);
while (it.hasMatch()) {
tagsList.append(it.captured(1));
pos += it.capturedLength();
it = tagRegEx.match(tagsString, pos);
}
return tagsList;
}
class CatchTestVisitor : public CPlusPlus::ASTVisitor
{
public:
explicit CatchTestVisitor(CPlusPlus::Document::Ptr doc)
: CPlusPlus::ASTVisitor(doc->translationUnit()) , m_document(doc) {}
bool visit(CPlusPlus::FunctionDefinitionAST *ast) override;
QMap<CatchTestCaseSpec, TestCodeLocationAndType> testFunctions() const { return m_testFunctions; }
private:
bool isValidTestCase(unsigned int macroTokenIndex, QString &testCaseName, QStringList &tags);
CPlusPlus::Document::Ptr m_document;
CPlusPlus::Overview m_overview;
QMap<CatchTestCaseSpec, TestCodeLocationAndType> m_testFunctions;
};
bool CatchTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast)
{
if (!ast || !ast->declarator || !ast->declarator->core_declarator)
return false;
CPlusPlus::DeclaratorIdAST *id = ast->declarator->core_declarator->asDeclaratorId();
if (!id || !id->name)
return false;
const QString prettyName = m_overview.prettyName(id->name->name);
if (!isCatchTestCaseMacro(prettyName))
return false;
QString testName;
QStringList tags;
if (!isValidTestCase(ast->firstToken(), testName, tags))
return false;
CatchTestCaseSpec spec;
spec.name = testName;
if (prettyName == "SCENARIO")
spec.name.prepend("Scenario: "); // TODO maybe better as a flag?
spec.tags = tags;
TestCodeLocationAndType location;
location.m_type = TestTreeItem::TestCase;
location.m_name = m_document->fileName();
getTokenStartPosition(ast->firstToken(), &location.m_line, &location.m_column);
m_testFunctions.insert(spec, location);
return true;
}
bool CatchTestVisitor::isValidTestCase(unsigned int macroTokenIndex, QString &testCaseName, QStringList &tags)
{
QTC_ASSERT(testCaseName.isEmpty(), return false);
QTC_ASSERT(tags.isEmpty(), return false);
unsigned int end = tokenCount();
++macroTokenIndex;
enum ParseMode { TestCaseMode, TagsMode, Ignored } mode = TestCaseMode;
QString captured;
while (macroTokenIndex < end) {
CPlusPlus::Token token = tokenAt(macroTokenIndex);
if (token.kind() == CPlusPlus::T_RPAREN) {
if (mode == TagsMode)
tags = parseTags(captured);
else
testCaseName = captured;
break;
} else if (token.kind() == CPlusPlus::T_COMMA) {
if (mode == TestCaseMode) {
mode = TagsMode;
testCaseName = captured;
captured = QString();
} else if (mode == TagsMode) {
mode = Ignored;
}
} else if (token.kind() == CPlusPlus::T_STRING_LITERAL) {
if (mode != Ignored)
captured += stringLiteralToQString(stringLiteral(macroTokenIndex));
}
++macroTokenIndex;
}
return !testCaseName.isEmpty();
}
inline bool operator<(const CatchTestCaseSpec &spec1, const CatchTestCaseSpec &spec2)
{
if (spec1.name != spec2.name)
return spec1.name < spec2.name;
if (spec1.tags != spec2.tags)
return spec1.tags < spec2.tags;
return false;
}
static bool includesCatchHeader(const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot)
{
@@ -217,37 +93,30 @@ static bool handleCatchDocument(QFutureInterface<TestParseResultPtr> futureInter
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QString &filePath = doc->fileName();
const QByteArray &fileContent = CppParser::getFileContent(filePath);
CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, filePath);
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
CatchTestVisitor visitor(document);
visitor.accept(ast);
QMap<CatchTestCaseSpec, TestCodeLocationAndType> result = visitor.testFunctions();
QString proFile;
const QList<CppTools::ProjectPart::Ptr> &ppList = modelManager->projectPart(filePath);
if (ppList.size())
proFile = ppList.first()->projectFile;
else
const QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(filePath);
if (projectParts.isEmpty()) // happens if shutting down while parsing
return false;
QString proFile;
const CppTools::ProjectPart::Ptr projectPart = projectParts.first();
proFile = projectPart->projectFile;
CatchCodeParser codeParser(fileContent, projectPart->languageFeatures, doc, snapshot);
const TestCodeLocationList foundTests = codeParser.findTests();
CatchParseResult *parseResult = new CatchParseResult(framework);
parseResult->itemType = TestTreeItem::TestCase;
parseResult->fileName = filePath;
parseResult->name = filePath;
parseResult->displayName = filePath;
QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(doc->fileName());
if (projectParts.isEmpty()) // happens if shutting down while parsing
return false;
parseResult->proFile = projectParts.first()->projectFile;
for (const auto &testSpec : result.keys()) {
const TestCodeLocationAndType &testLocation = result.value(testSpec);
for (const TestCodeLocationAndType & testLocation : foundTests) {
CatchParseResult *testCase = new CatchParseResult(framework);
testCase->fileName = filePath;
testCase->name = testSpec.name;
testCase->name = testLocation.m_name;
testCase->proFile = proFile;
testCase->itemType = TestTreeItem::TestFunction;
testCase->itemType = testLocation.m_type;
testCase->line = testLocation.m_line;
testCase->column = testLocation.m_column;
@@ -256,7 +125,7 @@ static bool handleCatchDocument(QFutureInterface<TestParseResultPtr> futureInter
futureInterface.reportResult(TestParseResultPtr(parseResult));
return !result.keys().isEmpty();
return !foundTests.isEmpty();
}
bool CatchTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface, const QString &fileName)