AutoTest: Take precompiled headers into account

Test frameworks might be added to the precompiled
headers. This in turn would make some pre-checks
whether a file has to be processed or not fail.

Fixes: QTCREATORBUG-25821
Change-Id: Iff69c1a83889cb6f79a3e3f9b2e59c5383989ccd
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Stenger
2021-06-04 14:28:03 +02:00
parent 684c5f7529
commit 9db6569c43
7 changed files with 143 additions and 12 deletions

View File

@@ -84,7 +84,9 @@ static bool includesBoostTest(const CPlusPlus::Document::Ptr &doc,
return true;
}
return false;
return CppParser::precompiledHeaderContains(snapshot,
Utils::FilePath::fromString(doc->fileName()),
boostTestHpp);
}
static bool hasBoostTestMacros(const CPlusPlus::Document::Ptr &doc)

View File

@@ -86,6 +86,12 @@ static bool includesCatchHeader(const CPlusPlus::Document::Ptr &doc,
}
}
for (const QString &catchHeader : catchHeaders) {
if (CppParser::precompiledHeaderContains(snapshot,
Utils::FilePath::fromString(doc->fileName()),
catchHeader))
return true;
}
return false;
}
@@ -109,13 +115,24 @@ bool CatchTestParser::processDocument(QFutureInterface<TestParseResultPtr> futur
const Utils::FilePath &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
if (doc.isNull() || !includesCatchHeader(doc, m_cppSnapshot) || !hasCatchNames(doc))
if (doc.isNull() || !includesCatchHeader(doc, m_cppSnapshot))
return false;
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QString &filePath = doc->fileName();
const QByteArray &fileContent = getFileContent(fileName);
if (!hasCatchNames(doc)) {
const QRegularExpression regex("\\b(SCENARIO|(TEMPLATE_(PRODUCT_)?)?TEST_CASE(_METHOD)?|"
"TEMPLATE_TEST_CASE(_METHOD)?_SIG|"
"TEMPLATE_PRODUCT_TEST_CASE(_METHOD)?_SIG|"
"TEMPLATE_LIST_TEST_CASE_METHOD|METHOD_AS_TEST_CASE|"
"REGISTER_TEST_CASE)");
if (!regex.match(QString::fromUtf8(fileContent)).hasMatch())
return false;
}
const QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(fileName);
if (projectParts.isEmpty()) // happens if shutting down while parsing
return false;

View File

@@ -32,6 +32,9 @@
#include <cpptools/cppmodelmanager.h>
#include <cpptools/projectpart.h>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
namespace Autotest {
namespace Internal {
@@ -69,7 +72,9 @@ static bool includesGTest(const CPlusPlus::Document::Ptr &doc,
return true;
}
return false;
return CppParser::precompiledHeaderContains(snapshot,
Utils::FilePath::fromString(doc->fileName()),
gtestH);
}
static bool hasGTestNames(const CPlusPlus::Document::Ptr &document)
@@ -91,12 +96,18 @@ bool GTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInt
const Utils::FilePath &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
if (doc.isNull() || !includesGTest(doc, m_cppSnapshot) || !hasGTestNames(doc))
if (doc.isNull() || !includesGTest(doc, m_cppSnapshot))
return false;
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QString &filePath = doc->fileName();
const QByteArray &fileContent = getFileContent(fileName);
if (!hasGTestNames(doc)) {
const QRegularExpression regex("\\b(TEST(_[FP])?|TYPED_TEST(_P)?|(GTEST_TEST))");
if (!regex.match(QString::fromUtf8(fileContent)).hasMatch())
return false;
}
const QString &filePath = doc->fileName();
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
CPlusPlus::Document::Ptr document = m_cppSnapshot.preprocessedDocument(fileContent, fileName);
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();

View File

@@ -28,6 +28,10 @@
#include <coreplugin/editormanager/editormanager.h>
#include <cpptools/cppmodelmanager.h>
#include <utils/textfileformat.h>
#include <utils/algorithm.h>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
namespace Autotest {
@@ -69,6 +73,39 @@ QByteArray CppParser::getFileContent(const Utils::FilePath &filePath) const
return fileContent;
}
bool precompiledHeaderContains(const CPlusPlus::Snapshot &snapshot,
const Utils::FilePath &filePath,
const std::function<bool(const QString &)> &checker)
{
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(filePath);
if (projectParts.isEmpty())
return false;
const QStringList precompiledHeaders = projectParts.first()->precompiledHeaders;
auto headerContains = [&](const QString &header){
return Utils::anyOf(snapshot.allIncludesForDocument(header), checker);
};
return Utils::anyOf(precompiledHeaders, headerContains);
}
bool CppParser::precompiledHeaderContains(const CPlusPlus::Snapshot &snapshot,
const Utils::FilePath &filePath,
const QString &headerFilePath)
{
return Autotest::precompiledHeaderContains(snapshot, filePath, [&](const QString &include) {
return include.endsWith(headerFilePath);
});
}
bool CppParser::precompiledHeaderContains(const CPlusPlus::Snapshot &snapshot,
const Utils::FilePath &filePath,
const QRegularExpression &headerFileRegex)
{
return Autotest::precompiledHeaderContains(snapshot, filePath, [&](const QString &include) {
return headerFileRegex.match(include).hasMatch();
});
}
void CppParser::release()
{
m_cppSnapshot = CPlusPlus::Snapshot();

View File

@@ -33,6 +33,10 @@
#include <QFutureInterface>
QT_BEGIN_NAMESPACE
class QRegularExpression;
QT_END_NAMESPACE
namespace Autotest {
class ITestFramework;
@@ -85,6 +89,12 @@ public:
CPlusPlus::Document::Ptr document(const Utils::FilePath &fileName);
static bool precompiledHeaderContains(const CPlusPlus::Snapshot &snapshot,
const Utils::FilePath &filePath,
const QString &headerFilePath);
static bool precompiledHeaderContains(const CPlusPlus::Snapshot &snapshot,
const Utils::FilePath &filePath,
const QRegularExpression &headerFileRegex);
protected:
CPlusPlus::Snapshot m_cppSnapshot;
CppTools::WorkingCopy m_workingCopy;

View File

@@ -32,6 +32,8 @@
#include <cplusplus/TypeOfExpression.h>
#include <utils/algorithm.h>
#include <QRegularExpressionMatchIterator>
namespace Autotest {
namespace Internal {
@@ -78,6 +80,14 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc, const CPlusPlus:
return true;
}
}
for (const QString &prefix : expectedHeaderPrefixes) {
if (CppParser::precompiledHeaderContains(snapshot,
Utils::FilePath::fromString(doc->fileName()),
QString("%1/qtest.h").arg(prefix))) {
return true;
}
}
return false;
}
@@ -120,7 +130,19 @@ TestCases QtTestParser::testCases(const CppTools::CppModelManager *modelManager,
CPlusPlus::AST *ast = document->translationUnit()->ast();
TestAstVisitor astVisitor(document, m_cppSnapshot);
astVisitor.accept(ast);
if (!astVisitor.testCases().isEmpty())
return astVisitor.testCases();
// check pch usage - might give false positives, but we can't do better without cost
TestCases result;
const QRegularExpression regex("\\bQTEST_(APPLESS_|GUILESS_)?MAIN"
"\\s*\\(\\s*([[:alnum:]]+)\\s*\\)");
QRegularExpressionMatchIterator it = regex.globalMatch(QString::fromUtf8(fileContent));
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
result.append({match.captured(2), false});
}
return result;
}
static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc,

View File

@@ -87,6 +87,14 @@ static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc,
return true;
}
}
for (const QString &prefix : expectedHeaderPrefixes) {
if (CppParser::precompiledHeaderContains(snapshot,
Utils::FilePath::fromString(doc->fileName()),
QString("%1/quicktest.h").arg(prefix))) {
return true;
}
}
return false;
}
@@ -115,6 +123,7 @@ static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM,
QString QuickTestParser::quickTestName(const CPlusPlus::Document::Ptr &doc) const
{
const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses();
const Utils::FilePath filePath = Utils::FilePath::fromString(doc->fileName());
for (const CPlusPlus::Document::MacroUse &macro : macros) {
if (!macro.isFunctionLike())
@@ -122,22 +131,45 @@ QString QuickTestParser::quickTestName(const CPlusPlus::Document::Ptr &doc) cons
const QByteArray name = macro.macro().name();
if (QuickTestUtils::isQuickTestMacro(name)) {
CPlusPlus::Document::Block arg = macro.arguments().at(0);
return QLatin1String(getFileContent(Utils::FilePath::fromString(doc->fileName()))
return QLatin1String(getFileContent(filePath)
.mid(int(arg.bytesBegin()), int(arg.bytesEnd() - arg.bytesBegin())));
}
}
const QByteArray &fileContent = getFileContent(filePath);
// check for using quick_test_main() directly
const QString fileName = doc->fileName();
const QByteArray &fileContent = getFileContent(Utils::FilePath::fromString(fileName));
CPlusPlus::Document::Ptr document = m_cppSnapshot.preprocessedDocument(fileContent, fileName);
CPlusPlus::Document::Ptr document = m_cppSnapshot.preprocessedDocument(fileContent, filePath);
if (document.isNull())
return QString();
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
QuickTestAstVisitor astVisitor(document, m_cppSnapshot);
astVisitor.accept(ast);
if (!astVisitor.testBaseName().isEmpty())
return astVisitor.testBaseName();
// check for precompiled headers
static QStringList expectedHeaderPrefixes
= Utils::HostOsInfo::isMacHost()
? QStringList({"QtQuickTest.framework/Headers", "QtQuickTest"})
: QStringList({"QtQuickTest"});
bool pchIncludes = false;
for (const QString &prefix : expectedHeaderPrefixes) {
if (CppParser::precompiledHeaderContains(m_cppSnapshot, filePath,
QString("%1/quicktest.h").arg(prefix))) {
pchIncludes = true;
break;
}
}
if (pchIncludes) {
const QRegularExpression regex("\\bQUICK_TEST_(MAIN|OPENGL_MAIN|MAIN_WITH_SETUP)");
const QRegularExpressionMatch match = regex.match(QString::fromUtf8(fileContent));
if (match.hasMatch())
return match.captured(); // we do not care for the name, just return something non-empty
}
return {};
}
QList<Document::Ptr> QuickTestParser::scanDirectoryForQuickTestQmlFiles(const QString &srcDir)