AutoTest: Move test framework related code...

...into files and respective sub folders.

Change-Id: Ic80d693bd73993a6e74b6d422349e47276e8cb6e
Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
This commit is contained in:
Christian Stenger
2016-05-11 13:02:42 +02:00
parent 6c18ed50d2
commit 2f8b4b3fbf
52 changed files with 3643 additions and 2427 deletions

View File

@@ -26,30 +26,25 @@
#include "autotestconstants.h"
#include "autotest_utils.h"
#include "testcodeparser.h"
#include "testvisitor.h"
// FIXME
#include "qtest/qttestparser.h"
#include "quick/quicktestparser.h"
#include "gtest/googletestparser.h"
// end of FIXME
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <cplusplus/LookupContext.h>
#include <cplusplus/TypeOfExpression.h>
#include <cpptools/cpptoolsconstants.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cppworkingcopy.h>
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/qmljsdialect.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/textfileformat.h>
#include <QDirIterator>
#include <QFuture>
@@ -160,468 +155,6 @@ void TestCodeParser::updateTestTree()
scanForTests();
}
/****** scan for QTest related stuff helpers ******/
static QByteArray getFileContent(QString filePath)
{
QByteArray fileContent;
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CppTools::WorkingCopy wc = cppMM->workingCopy();
if (wc.contains(filePath)) {
fileContent = wc.source(filePath);
} else {
QString error;
const QTextCodec *codec = Core::EditorManager::defaultTextCodec();
if (Utils::TextFileFormat::readFileUTF8(filePath, codec, &fileContent, &error)
!= Utils::TextFileFormat::ReadSuccess) {
qDebug() << "Failed to read file" << filePath << ":" << error;
}
}
return fileContent;
}
static bool includesQtTest(const CPlusPlus::Document::Ptr &doc, const CPlusPlus::Snapshot &snapshot)
{
static QStringList expectedHeaderPrefixes
= Utils::HostOsInfo::isMacHost()
? QStringList({ QLatin1String("QtTest.framework/Headers"), QLatin1String("QtTest") })
: QStringList({ QLatin1String("QtTest") });
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
foreach (const CPlusPlus::Document::Include &inc, includes) {
// TODO this short cut works only for #include <QtTest>
// bad, as there could be much more different approaches
if (inc.unresolvedFileName() == QLatin1String("QtTest")) {
foreach (const QString &prefix, expectedHeaderPrefixes) {
if (inc.resolvedFileName().endsWith(QString::fromLatin1("%1/QtTest").arg(prefix)))
return true;
}
}
}
const QSet<QString> allIncludes = snapshot.allIncludesForDocument(doc->fileName());
foreach (const QString &include, allIncludes) {
foreach (const QString &prefix, expectedHeaderPrefixes) {
if (include.endsWith(QString::fromLatin1("%1/qtest.h").arg(prefix)))
return true;
}
}
return false;
}
static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot)
{
static QStringList expectedHeaderPrefixes
= Utils::HostOsInfo::isMacHost()
? QStringList({ QLatin1String("QtQuickTest.framework/Headers"),
QLatin1String("QtQuickTest") })
: QStringList({ QLatin1String("QtQuickTest") });
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
foreach (const CPlusPlus::Document::Include &inc, includes) {
if (inc.unresolvedFileName() == QLatin1String("QtQuickTest/quicktest.h")) {
foreach (const QString &prefix, expectedHeaderPrefixes) {
if (inc.resolvedFileName().endsWith(
QString::fromLatin1("%1/quicktest.h").arg(prefix))) {
return true;
}
}
}
}
foreach (const QString &include, snapshot.allIncludesForDocument(doc->fileName())) {
foreach (const QString &prefix, expectedHeaderPrefixes) {
if (include.endsWith(QString::fromLatin1("%1/quicktest.h").arg(prefix)))
return true;
}
}
return false;
}
static bool includesGTest(const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot)
{
const QString gtestH = QLatin1String("gtest/gtest.h");
foreach (const CPlusPlus::Document::Include &inc, doc->resolvedIncludes()) {
if (inc.resolvedFileName().endsWith(gtestH))
return true;
}
foreach (const QString &include, snapshot.allIncludesForDocument(doc->fileName())) {
if (include.endsWith(gtestH))
return true;
}
return false;
}
static bool qtTestLibDefined(const QString &fileName)
{
const QList<CppTools::ProjectPart::Ptr> parts =
CppTools::CppModelManager::instance()->projectPart(fileName);
if (parts.size() > 0)
return parts.at(0)->projectDefines.contains("#define QT_TESTLIB_LIB");
return false;
}
static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM,
const QString &fileName)
{
static const QByteArray qtsd(" QUICK_TEST_SOURCE_DIR ");
const QList<CppTools::ProjectPart::Ptr> parts = cppMM->projectPart(fileName);
if (parts.size() > 0) {
QByteArray projDefines(parts.at(0)->projectDefines);
foreach (const QByteArray &line, projDefines.split('\n')) {
if (line.contains(qtsd)) {
QByteArray result = line.mid(line.indexOf(qtsd) + qtsd.length());
if (result.startsWith('"'))
result.remove(result.length() - 1, 1).remove(0, 1);
if (result.startsWith("\\\""))
result.remove(result.length() - 2, 2).remove(0, 2);
return QLatin1String(result);
}
}
}
return QString();
}
static QString testClass(const CppTools::CppModelManager *modelManager,
const CPlusPlus::Snapshot &snapshot, const QString &fileName)
{
const QByteArray &fileContent = getFileContent(fileName);
CPlusPlus::Document::Ptr document = modelManager->document(fileName);
if (document.isNull())
return QString();
const QList<CPlusPlus::Document::MacroUse> macros = document->macroUses();
foreach (const CPlusPlus::Document::MacroUse &macro, macros) {
if (!macro.isFunctionLike())
continue;
const QByteArray name = macro.macro().name();
if (TestUtils::isQTestMacro(name)) {
const CPlusPlus::Document::Block arg = macro.arguments().at(0);
return QLatin1String(fileContent.mid(arg.bytesBegin(),
arg.bytesEnd() - arg.bytesBegin()));
}
}
// check if one has used a self-defined macro or QTest::qExec() directly
document = snapshot.preprocessedDocument(fileContent, fileName);
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
TestAstVisitor astVisitor(document);
astVisitor.accept(ast);
return astVisitor.className();
}
static QString quickTestName(const CPlusPlus::Document::Ptr &doc)
{
const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses();
foreach (const CPlusPlus::Document::MacroUse &macro, macros) {
if (!macro.isFunctionLike())
continue;
const QByteArray name = macro.macro().name();
if (TestUtils::isQuickTestMacro(name)) {
CPlusPlus::Document::Block arg = macro.arguments().at(0);
return QLatin1String(getFileContent(doc->fileName())
.mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
}
}
return QString();
}
static bool hasGTestNames(const CPlusPlus::Document::Ptr &document)
{
foreach (const CPlusPlus::Document::MacroUse &macro, document->macroUses()) {
if (!macro.isFunctionLike())
continue;
if (TestUtils::isGTestMacro(QLatin1String(macro.macro().name()))) {
const QVector<CPlusPlus::Document::Block> args = macro.arguments();
if (args.size() != 2)
continue;
return true;
}
}
return false;
}
static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QString &srcDir)
{
QStringList dirs(srcDir);
QmlJS::ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance();
// make sure even files not listed in pro file are available inside the snapshot
QFutureInterface<void> future;
QmlJS::PathsAndLanguages paths;
paths.maybeInsert(Utils::FileName::fromString(srcDir), QmlJS::Dialect::Qml);
const bool emitDocumentChanges = false;
const bool onlyTheLib = false;
QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), paths, qmlJsMM,
emitDocumentChanges, onlyTheLib);
const QmlJS::Snapshot snapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot();
QDirIterator it(srcDir, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QFileInfo fi(it.fileInfo().canonicalFilePath());
dirs << fi.filePath();
}
QList<QmlJS::Document::Ptr> foundDocs;
foreach (const QString &path, dirs) {
const QList<QmlJS::Document::Ptr> docs = snapshot.documentsInDirectory(path);
foreach (const QmlJS::Document::Ptr &doc, docs) {
const QString fileName(QFileInfo(doc->fileName()).fileName());
if (fileName.startsWith(QLatin1String("tst_")) && fileName.endsWith(QLatin1String(".qml")))
foundDocs << doc;
}
}
return foundDocs;
}
static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc,
const CPlusPlus::Snapshot &snapshot,
const QString &testCaseName,
unsigned *line, unsigned *column)
{
CPlusPlus::Document::Ptr declaringDoc = doc;
CPlusPlus::TypeOfExpression typeOfExpr;
typeOfExpr.init(doc, snapshot);
QList<CPlusPlus::LookupItem> lookupItems = typeOfExpr(testCaseName.toUtf8(),
doc->globalNamespace());
if (lookupItems.size()) {
if (CPlusPlus::Symbol *symbol = lookupItems.first().declaration()) {
if (CPlusPlus::Class *toeClass = symbol->asClass()) {
const QString declFileName = QLatin1String(toeClass->fileId()->chars(),
toeClass->fileId()->size());
declaringDoc = snapshot.document(declFileName);
*line = toeClass->line();
*column = toeClass->column() - 1;
}
}
}
return declaringDoc;
}
static QSet<QString> filesWithDataFunctionDefinitions(
const QMap<QString, TestCodeLocationAndType> &testFunctions)
{
QSet<QString> result;
QMap<QString, TestCodeLocationAndType>::ConstIterator it = testFunctions.begin();
const QMap<QString, TestCodeLocationAndType>::ConstIterator end = testFunctions.end();
for ( ; it != end; ++it) {
const QString &key = it.key();
if (key.endsWith(QLatin1String("_data")) && testFunctions.contains(key.left(key.size() - 5)))
result.insert(it.value().m_name);
}
return result;
}
static QMap<QString, TestCodeLocationList> checkForDataTags(const QString &fileName,
const CPlusPlus::Snapshot &snapshot)
{
const QByteArray fileContent = getFileContent(fileName);
CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, fileName);
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
TestDataFunctionVisitor visitor(document);
visitor.accept(ast);
return visitor.dataTags();
}
/****** end of helpers ******/
static bool checkQmlDocumentForQuickTestCode(QFutureInterface<TestParseResultPtr> futureInterface,
const QmlJS::Document::Ptr &qmlJSDoc,
const QString &proFile = QString())
{
if (qmlJSDoc.isNull())
return false;
QmlJS::AST::Node *ast = qmlJSDoc->ast();
QTC_ASSERT(ast, return false);
TestQmlVisitor qmlVisitor(qmlJSDoc);
QmlJS::AST::Node::accept(ast, &qmlVisitor);
const QString testCaseName = qmlVisitor.testCaseName();
const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation();
const QMap<QString, TestCodeLocationAndType> &testFunctions = qmlVisitor.testFunctions();
QuickTestParseResult *parseResult = new QuickTestParseResult;
parseResult->proFile = proFile;
parseResult->itemType = TestTreeItem::TestCase;
QMap<QString, TestCodeLocationAndType>::ConstIterator it = testFunctions.begin();
const QMap<QString, TestCodeLocationAndType>::ConstIterator end = testFunctions.end();
for ( ; it != end; ++it) {
const TestCodeLocationAndType &loc = it.value();
QuickTestParseResult *funcResult = new QuickTestParseResult;
funcResult->name = it.key();
funcResult->displayName = it.key();
funcResult->itemType = loc.m_type;
funcResult->fileName = loc.m_name;
funcResult->line = loc.m_line;
funcResult->column = loc.m_column;
funcResult->proFile = proFile;
parseResult->children.append(funcResult);
}
if (!testCaseName.isEmpty()) {
parseResult->fileName = tcLocationAndType.m_name;
parseResult->name = testCaseName;
parseResult->line = tcLocationAndType.m_line;
parseResult->column = tcLocationAndType.m_column;
}
futureInterface.reportResult(TestParseResultPtr(parseResult));
return true;
}
static bool handleQtTest(QFutureInterface<TestParseResultPtr> futureInterface,
CPlusPlus::Document::Ptr document,
const CPlusPlus::Snapshot &snapshot,
const QString &oldTestCaseName)
{
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QString &fileName = document->fileName();
QString testCaseName(testClass(modelManager, snapshot, fileName));
// we might be in a reparse without the original entry point with the QTest::qExec()
if (testCaseName.isEmpty())
testCaseName = oldTestCaseName;
if (!testCaseName.isEmpty()) {
unsigned line = 0;
unsigned column = 0;
CPlusPlus::Document::Ptr declaringDoc = declaringDocument(document, snapshot, testCaseName,
&line, &column);
if (declaringDoc.isNull())
return false;
TestVisitor visitor(testCaseName);
visitor.accept(declaringDoc->globalNamespace());
if (!visitor.resultValid())
return false;
const QMap<QString, TestCodeLocationAndType> &testFunctions = visitor.privateSlots();
const QSet<QString> &files = filesWithDataFunctionDefinitions(testFunctions);
// TODO: change to QHash<>
QMap<QString, TestCodeLocationList> dataTags;
foreach (const QString &file, files)
dataTags.unite(checkForDataTags(file, snapshot));
QtTestParseResult *parseResult = new QtTestParseResult;
parseResult->itemType = TestTreeItem::TestCase;
parseResult->fileName = declaringDoc->fileName();
parseResult->name = testCaseName;
parseResult->displayName = testCaseName;
parseResult->line = line;
parseResult->column = column;
parseResult->proFile = modelManager->projectPart(fileName).first()->projectFile;
QMap<QString, TestCodeLocationAndType>::ConstIterator it = testFunctions.begin();
const QMap<QString, TestCodeLocationAndType>::ConstIterator end = testFunctions.end();
for ( ; it != end; ++it) {
const TestCodeLocationAndType &location = it.value();
QtTestParseResult *func = new QtTestParseResult;
func->itemType = location.m_type;
func->name = testCaseName + QLatin1String("::") + it.key();
func->displayName = it.key();
func->fileName = location.m_name;
func->line = location.m_line;
func->column = location.m_column;
foreach (const TestCodeLocationAndType &tag, dataTags.value(func->name)) {
QtTestParseResult *dataTag = new QtTestParseResult;
dataTag->itemType = tag.m_type;
dataTag->name = tag.m_name;
dataTag->displayName = tag.m_name;
dataTag->fileName = testFunctions.value(it.key() + QLatin1String("_data")).m_name;
dataTag->line = tag.m_line;
dataTag->column = tag.m_column;
func->children.append(dataTag);
}
parseResult->children.append(func);
}
futureInterface.reportResult(TestParseResultPtr(parseResult));
return true;
}
return false;
}
static bool handleQtQuickTest(QFutureInterface<TestParseResultPtr> futureInterface,
CPlusPlus::Document::Ptr document)
{
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
if (quickTestName(document).isEmpty())
return false;
const QString cppFileName = document->fileName();
QList<CppTools::ProjectPart::Ptr> ppList = modelManager->projectPart(cppFileName);
QTC_ASSERT(!ppList.isEmpty(), return false);
const QString &proFile = ppList.at(0)->projectFile;
const QString srcDir = quickTestSrcDir(modelManager, cppFileName);
if (srcDir.isEmpty())
return false;
const QList<QmlJS::Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir);
bool result = false;
foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs)
result |= checkQmlDocumentForQuickTestCode(futureInterface, qmlJSDoc, proFile);
return result;
}
static bool handleGTest(QFutureInterface<TestParseResultPtr> futureInterface,
const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot)
{
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QString &filePath = doc->fileName();
const QByteArray &fileContent = getFileContent(filePath);
CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, filePath);
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
GTestVisitor visitor(document);
visitor.accept(ast);
QMap<GTestCaseSpec, GTestCodeLocationList> result = visitor.gtestFunctions();
QString proFile;
const QList<CppTools::ProjectPart::Ptr> &ppList = modelManager->projectPart(filePath);
if (ppList.size())
proFile = ppList.first()->projectFile;
foreach (const GTestCaseSpec &testSpec, result.keys()) {
GoogleTestParseResult *parseResult = new GoogleTestParseResult;
parseResult->itemType = TestTreeItem::TestCase;
parseResult->fileName = filePath;
parseResult->name = testSpec.testCaseName;
parseResult->parameterized = testSpec.parameterized;
parseResult->typed = testSpec.typed;
parseResult->disabled = testSpec.disabled;
parseResult->proFile = proFile;
foreach (const GTestCodeLocationAndType &location, result.value(testSpec)) {
GoogleTestParseResult *testSet = new GoogleTestParseResult;
testSet->name = location.m_name;
testSet->fileName = filePath;
testSet->line = location.m_line;
testSet->column = location.m_column;
testSet->disabled = location.m_state & GoogleTestTreeItem::Disabled;
testSet->itemType = location.m_type;
testSet->proFile = proFile;
parseResult->children.append(testSet);
}
futureInterface.reportResult(TestParseResultPtr(parseResult));
}
return !result.keys().isEmpty();
}
// used internally to indicate a parse that failed due to having triggered a parse for a file that
// is not (yet) part of the CppModelManager's snapshot
static bool parsingHasFailed;
@@ -882,90 +415,5 @@ void TestCodeParser::onPartialParsingFinished()
}
}
TestTreeItem *QtTestParseResult::createTestTreeItem() const
{
return itemType == TestTreeItem::Root ? 0 : AutoTestTreeItem::createTestItem(this);
}
TestTreeItem *QuickTestParseResult::createTestTreeItem() const
{
if (itemType == TestTreeItem::Root || itemType == TestTreeItem::TestDataTag)
return 0;
return QuickTestTreeItem::createTestItem(this);
}
TestTreeItem *GoogleTestParseResult::createTestTreeItem() const
{
if (itemType == TestTreeItem::TestCase || itemType == TestTreeItem::TestFunctionOrSet)
return GoogleTestTreeItem::createTestItem(this);
return 0;
}
void CppParser::init(const QStringList &filesToParse)
{
Q_UNUSED(filesToParse);
m_cppSnapshot = CppTools::CppModelManager::instance()->snapshot();
}
bool CppParser::selectedForBuilding(const QString &fileName)
{
QList<CppTools::ProjectPart::Ptr> projParts =
CppTools::CppModelManager::instance()->projectPart(fileName);
return projParts.size() && projParts.at(0)->selectedForBuilding;
}
void QtTestParser::init(const QStringList &filesToParse)
{
m_testCaseNames = TestTreeModel::instance()->testCaseNamesForFiles(filesToParse);
CppParser::init(filesToParse);
}
bool QtTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
const QString &fileName)
{
if (!m_cppSnapshot.contains(fileName) || !selectedForBuilding(fileName))
return false;
CPlusPlus::Document::Ptr doc = m_cppSnapshot.find(fileName).value();
const QString &oldName = m_testCaseNames.value(fileName);
if ((!includesQtTest(doc, m_cppSnapshot) || !qtTestLibDefined(fileName)) && oldName.isEmpty())
return false;
return handleQtTest(futureInterface, doc, m_cppSnapshot, oldName);
}
void QuickTestParser::init(const QStringList &filesToParse)
{
m_qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot();
CppParser::init(filesToParse);
}
bool QuickTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
const QString &fileName)
{
if (fileName.endsWith(".qml")) {
QmlJS::Document::Ptr qmlJSDoc = m_qmlSnapshot.document(fileName);
return checkQmlDocumentForQuickTestCode(futureInterface, qmlJSDoc);
}
if (!m_cppSnapshot.contains(fileName) || !selectedForBuilding(fileName))
return false;
CPlusPlus::Document::Ptr document = m_cppSnapshot.find(fileName).value();
if (!includesQtQuickTest(document, m_cppSnapshot))
return false;
return handleQtQuickTest(futureInterface, document);
}
bool GoogleTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
const QString &fileName)
{
if (!m_cppSnapshot.contains(fileName) || !selectedForBuilding(fileName))
return false;
CPlusPlus::Document::Ptr document = m_cppSnapshot.find(fileName).value();
if (!includesGTest(document, m_cppSnapshot) || !hasGTestNames(document))
return false;
return handleGTest(futureInterface, document, m_cppSnapshot);
}
} // namespace Internal
} // namespace Autotest