File indexing completed on 2024-04-21 04:34:49
0001 /* 0002 SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "phpunitprovider.h" 0008 0009 #include "phpunittestsuite.h" 0010 #include "testproviderdebug.h" 0011 0012 #include <interfaces/icore.h> 0013 #include <interfaces/iprojectcontroller.h> 0014 #include <interfaces/iproject.h> 0015 #include <interfaces/ilanguagecontroller.h> 0016 #include <interfaces/itestcontroller.h> 0017 #include <project/projectmodel.h> 0018 0019 #include <language/duchain/duchainlock.h> 0020 #include <language/duchain/duchain.h> 0021 #include <language/duchain/declaration.h> 0022 #include <language/duchain/classdeclaration.h> 0023 #include <language/duchain/duchainutils.h> 0024 0025 #include <KPluginFactory> 0026 #include <KAboutData> 0027 #include <KLocalizedString> 0028 #include <QVariant> 0029 #include <QTimer> 0030 #include <QStandardPaths> 0031 0032 using namespace KDevelop; 0033 0034 K_PLUGIN_FACTORY_WITH_JSON(PhpUnitProviderFactory, "kdevphpunitprovider.json", 0035 registerPlugin<PhpUnitProvider>(); ) 0036 0037 PhpUnitProvider::PhpUnitProvider(QObject* parent, const QList< QVariant >& args) 0038 : IPlugin(QStringLiteral("kdevphpunitprovider"), parent) 0039 { 0040 Q_UNUSED(args); 0041 0042 QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpunitdeclarations.php")); 0043 m_phpUnitDeclarationsFile = IndexedString(file); 0044 DUChain::self()->updateContextForUrl(m_phpUnitDeclarationsFile, KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this, -10); 0045 0046 connect(DUChain::self(), &DUChain::updateReady, 0047 this, &PhpUnitProvider::updateReady); 0048 } 0049 0050 void PhpUnitProvider::updateReady(const IndexedString& document, const ReferencedTopDUContext& context) 0051 { 0052 Q_UNUSED(document); 0053 0054 DUChainReadLocker lock; 0055 if (!context) { 0056 qCDebug(TESTPROVIDER) << "Received null context for file: " << document; 0057 return; 0058 } 0059 0060 if (document == m_phpUnitDeclarationsFile) { 0061 QVector<Declaration*> declarations = context.data()->localDeclarations(); 0062 if (declarations.isEmpty()) { 0063 qCDebug(TESTPROVIDER) << "Update of the internal test file found no suitable declarations"; 0064 return; 0065 } 0066 m_testCaseDeclaration = IndexedDeclaration(declarations.first()); 0067 0068 qCDebug(TESTPROVIDER) << "Found declaration" << declarations.first()->toString(); 0069 0070 foreach (const ReferencedTopDUContext& context, m_pendingContexts) { 0071 processContext(context); 0072 } 0073 } else { 0074 if (!m_testCaseDeclaration.isValid()) { 0075 m_pendingContexts << context; 0076 } else { 0077 processContext(context); 0078 } 0079 } 0080 } 0081 0082 0083 void PhpUnitProvider::processContext(ReferencedTopDUContext referencedContext) 0084 { 0085 TopDUContext* context = referencedContext.data(); 0086 0087 if (!context) { 0088 qCDebug(TESTPROVIDER) << "context went away"; 0089 return; 0090 } 0091 0092 Declaration* testCase = m_testCaseDeclaration.data(); 0093 if (!testCase) { 0094 qCDebug(TESTPROVIDER) << "test case declaration went away"; 0095 return; 0096 } 0097 0098 qCDebug(TESTPROVIDER) << "Number of declarations" << context->localDeclarations().size(); 0099 0100 foreach (Declaration* declaration, context->localDeclarations()) 0101 { 0102 ClassDeclaration* classDeclaration = dynamic_cast<ClassDeclaration*>(declaration); 0103 if (!classDeclaration || classDeclaration->classModifier() & ClassDeclarationData::Abstract || !classDeclaration->internalContext()) 0104 { 0105 continue; 0106 } 0107 0108 if (classDeclaration->isPublicBaseClass(static_cast<ClassDeclaration*>(m_testCaseDeclaration.data()), context)) { 0109 processTestCaseDeclaration(declaration); 0110 } 0111 } 0112 } 0113 0114 void PhpUnitProvider::processTestCaseDeclaration(Declaration* d) 0115 { 0116 QString name = d->identifier().toString(); 0117 QUrl url = d->url().toUrl(); 0118 IProject* project = ICore::self()->projectController()->findProjectForUrl(url); 0119 qCDebug(TESTPROVIDER) << name << url << (project ? project->name() : QStringLiteral("No project")); 0120 if (!project) 0121 { 0122 return; 0123 } 0124 QStringList testCases; 0125 QHash<QString, IndexedDeclaration> testCaseDeclarations; 0126 ClassDeclaration* classDeclaration = dynamic_cast<ClassDeclaration*>(d); 0127 0128 if (!classDeclaration) 0129 { 0130 return; 0131 } 0132 0133 if (!(classDeclaration->classModifier() & ClassDeclarationData::Abstract)) 0134 { 0135 foreach (Declaration* member, classDeclaration->internalContext()->localDeclarations()) 0136 { 0137 qCDebug(TESTPROVIDER) << "Trying test case declaration" << member; 0138 if (member->isFunctionDeclaration() && member->identifier().toString().startsWith(QLatin1String("test"))) 0139 { 0140 const QString caseName = member->identifier().toString(); 0141 testCases << caseName; 0142 testCaseDeclarations.insert(caseName, IndexedDeclaration(member)); 0143 } 0144 } 0145 0146 if (!testCaseDeclarations.isEmpty()) 0147 { 0148 // NOTE: No declarations usually means the class in abstract 0149 // This should be resolved by the classDeclaration->isAbstract() check 0150 // But that always returns false. 0151 ICore::self()->testController()->addTestSuite(new PhpUnitTestSuite(name, url, IndexedDeclaration(classDeclaration), testCases, testCaseDeclarations, project)); 0152 return; 0153 } 0154 } 0155 0156 uint steps = 100; 0157 foreach (Declaration* inheriter, DUChainUtils::inheriters(d, steps)) 0158 { 0159 processTestCaseDeclaration(inheriter); 0160 } 0161 } 0162 0163 #include "phpunitprovider.moc" 0164 0165 #include "moc_phpunitprovider.cpp"