File indexing completed on 2024-04-21 15:24:20

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"