File indexing completed on 2024-05-12 04:39:28

0001 /*
0002     SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ctestsuite.h"
0008 #include "ctestrunjob.h"
0009 #include <debug_testing.h>
0010 
0011 #include <interfaces/itestcontroller.h>
0012 #include <interfaces/iproject.h>
0013 #include <language/duchain/duchain.h>
0014 #include <language/duchain/duchainlock.h>
0015 #include <language/duchain/duchainutils.h>
0016 #include <language/duchain/declaration.h>
0017 #include <language/duchain/indexeddeclaration.h>
0018 #include <language/duchain/classdeclaration.h>
0019 #include <language/duchain/classfunctiondeclaration.h>
0020 #include <language/duchain/functiondeclaration.h>
0021 #include <language/duchain/functiondefinition.h>
0022 #include <language/duchain/types/functiontype.h>
0023 #include <language/duchain/types/pointertype.h>
0024 #include <language/duchain/types/referencetype.h>
0025 #include <language/duchain/types/structuretype.h>
0026 
0027 using namespace KDevelop;
0028 
0029 CTestSuite::CTestSuite(const QString& name, const KDevelop::Path &executable, const QList<KDevelop::Path>& files, IProject* project, const QStringList& args, const QHash<QString, QString>& properties):
0030 m_executable(executable),
0031 m_name(name),
0032 m_args(args),
0033 m_files(files),
0034 m_project(project),
0035 m_properties(properties)
0036 {
0037     Q_ASSERT(project);
0038     qCDebug(CMAKE_TESTING) << m_name << m_executable << m_project->name();
0039 }
0040 
0041 CTestSuite::~CTestSuite()
0042 {
0043 
0044 }
0045 
0046 bool CTestSuite::findCaseDeclarations(const QVector<Declaration*> &classDeclarations)
0047 {
0048     for (Declaration* decl : classDeclarations) {
0049         qCDebug(CMAKE_TESTING) << "Found declaration" << decl->toString()
0050                                << decl->identifier().identifier().byteArray();
0051 
0052         const auto* const function = dynamic_cast<ClassFunctionDeclaration*>(decl);
0053         if (!function || !(function->accessPolicy() == Declaration::Private && function->isSlot())) {
0054             continue;
0055         }
0056         QString name = function->qualifiedIdentifier().last().toString();
0057         qCDebug(CMAKE_TESTING) << "Found private slot in test" << name;
0058 
0059         if (name.endsWith(QLatin1String("_data"))) {
0060             continue;
0061         }
0062 
0063         const auto functionType = function->type<FunctionType>();
0064         if (!functionType || functionType->indexedArgumentsSize() > 0) {
0065             // function declarations with arguments are not valid test functions
0066             continue;
0067         }
0068         qCDebug(CMAKE_TESTING) << "Found test case function declaration" << function->identifier().toString();
0069 
0070         if (name != QLatin1String("initTestCase") && name != QLatin1String("cleanupTestCase") &&
0071             name != QLatin1String("init") && name != QLatin1String("cleanup"))
0072         {
0073             m_cases << name;
0074         } else {
0075             continue;
0076         }
0077 
0078         const auto* def = FunctionDefinition::definition(decl);
0079         m_declarations[name] = def ? IndexedDeclaration(def) : IndexedDeclaration(function);
0080     }
0081     return !m_declarations.isEmpty();
0082 }
0083 
0084 void CTestSuite::loadDeclarations(const IndexedString& document, const KDevelop::ReferencedTopDUContext& ref)
0085 {
0086     DUChainReadLocker locker(DUChain::lock());
0087     TopDUContext* topContext = DUChainUtils::contentContextFromProxyContext(ref.data());
0088     if (!topContext) {
0089         qCDebug(CMAKE_TESTING) << "No top context in" << document.str();
0090         return;
0091     }
0092 
0093     Declaration* testClass = nullptr;
0094     const auto mainId = Identifier(QStringLiteral("main"));
0095     const auto mainDeclarations = topContext->findLocalDeclarations(mainId);
0096     DUContext* tmpInternalContext;
0097     for (Declaration* declaration : mainDeclarations) {
0098         if (!declaration->isDefinition() || !(tmpInternalContext = declaration->internalContext())) {
0099             continue;
0100         }
0101         RangeInRevision contextRange = tmpInternalContext->range();
0102         qCDebug(CMAKE_TESTING) << "Found a definition for a function 'main()' at" << contextRange;
0103 
0104         /*
0105             * This function tries to deduce the test class from the main function definition of
0106             * the test source file. To do so, the cursor is set before the end of the internal
0107             * context. Going backwards from there, the first variable declaration of a class
0108             * type with private slots is assumed to be the test class.
0109             * This method finds test classes passed to QTEST_MAIN or QTEST_GUILESS_MAIN, but
0110             * also some classes which are used in manual implementations with a similar structure
0111             * as in the Qt macros.
0112             * If no such class is found, the main function definition is linked to the test suite
0113             * and no test cases are added to the test suite.
0114         */
0115 
0116         --contextRange.end.column; // set cursor before the end of the definition
0117         const auto innerContext = topContext->findContextAt(contextRange.end, true);
0118         if (!innerContext) {
0119             continue;
0120         }
0121         const auto mainDeclarations = innerContext->localDeclarations(topContext);
0122         for (auto it = mainDeclarations.rbegin(); it != mainDeclarations.rend(); ++it) {
0123             qCDebug(CMAKE_TESTING) << "Main declaration" << (*it)->toString();
0124 
0125             auto type = (*it)->abstractType();
0126             // Strip pointer and reference types to finally get to the structure type of the test class
0127             while (type && (type->whichType() == AbstractType::TypePointer || type->whichType() == AbstractType::TypeReference)) {
0128                 if (const auto ptype = type.dynamicCast<PointerType>()) {
0129                     type = ptype->baseType();
0130                 } else if (const auto rtype = type.dynamicCast<ReferenceType>()) {
0131                     type = rtype->baseType();
0132                 } else {
0133                     type = nullptr;
0134                 }
0135             }
0136             const auto structureType = type.dynamicCast<StructureType>();
0137             if (!structureType) {
0138                 continue;
0139             }
0140 
0141             testClass = structureType->declaration(topContext);
0142             if (!testClass || !(tmpInternalContext = testClass->internalContext())) {
0143                 continue;
0144             }
0145 
0146             if (findCaseDeclarations(tmpInternalContext->localDeclarations(topContext))) {
0147                 break;
0148             }
0149         }
0150         testClass = declaration;
0151     }
0152 
0153     if (testClass && testClass->internalContext()) {
0154         m_suiteDeclaration = IndexedDeclaration(testClass);
0155     } else {
0156         qCDebug(CMAKE_TESTING) << "No test class found or internal context missing in " << document.str();
0157     }
0158 }
0159 
0160 KJob* CTestSuite::launchCase(const QString& testCase, TestJobVerbosity verbosity)
0161 {
0162     return launchCases(QStringList() << testCase, verbosity);
0163 }
0164 
0165 KJob* CTestSuite::launchCases(const QStringList& testCases, ITestSuite::TestJobVerbosity verbosity)
0166 {
0167     qCDebug(CMAKE_TESTING) << "Launching test run" << m_name << "with cases" << testCases;
0168 
0169     OutputJob::OutputJobVerbosity outputVerbosity = (verbosity == Verbose) ? OutputJob::Verbose : OutputJob::Silent;
0170     return new CTestRunJob(this, testCases, outputVerbosity);
0171 }
0172 
0173 KJob* CTestSuite::launchAllCases(TestJobVerbosity verbosity)
0174 {
0175     return launchCases(cases(), verbosity);
0176 }
0177 
0178 KDevelop::Path CTestSuite::executable() const
0179 {
0180     return m_executable;
0181 }
0182 
0183 QStringList CTestSuite::cases() const
0184 {
0185     return m_cases;
0186 }
0187 
0188 QString CTestSuite::name() const
0189 {
0190     return m_name;
0191 }
0192 
0193 KDevelop::IProject* CTestSuite::project() const
0194 {
0195     return m_project;
0196 }
0197 
0198 QStringList CTestSuite::arguments() const
0199 {
0200     return m_args;
0201 }
0202 
0203 IndexedDeclaration CTestSuite::declaration() const
0204 {
0205     return m_suiteDeclaration;
0206 }
0207 
0208 IndexedDeclaration CTestSuite::caseDeclaration(const QString& testCase) const
0209 {
0210     return m_declarations.value(testCase, IndexedDeclaration(nullptr));
0211 }
0212 
0213 void CTestSuite::setTestCases(const QStringList& cases)
0214 {
0215     m_cases = cases;
0216 }
0217 
0218 QList<KDevelop::Path> CTestSuite::sourceFiles() const
0219 {
0220     return m_files;
0221 }
0222 
0223 QHash<QString, QString> CTestSuite::properties() const
0224 {
0225     return m_properties;
0226 }