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 }