File indexing completed on 2024-06-16 04:23:16
0001 /* 0002 SPDX-FileCopyrightText: 2011-2013 Milian Wolff <mail@milianw.de> 0003 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> 0004 SPDX-FileCopyrightText: 2007-2009 David Nolden <david.nolden.kdevelop@art-master.de> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "test_duchain.h" 0010 0011 #include <QTest> 0012 #include <QElapsedTimer> 0013 0014 #include <tests/autotestshell.h> 0015 #include <tests/testcore.h> 0016 0017 #include <language/duchain/duchain.h> 0018 #include <language/duchain/duchainlock.h> 0019 #include <language/duchain/persistentsymboltable.h> 0020 #include <language/duchain/codemodel.h> 0021 #include <language/duchain/types/typesystemdata.h> 0022 #include <language/duchain/types/integraltype.h> 0023 #include <language/duchain/types/typeregister.h> 0024 #include <language/duchain/declarationdata.h> 0025 #include <language/duchain/duchainregister.h> 0026 #include <language/duchain/problem.h> 0027 #include <language/duchain/parsingenvironment.h> 0028 0029 #include <language/codegen/coderepresentation.h> 0030 0031 #include <language/util/setrepository.h> 0032 #include <language/util/basicsetrepository.h> 0033 0034 // #include <typeinfo> 0035 #include <set> 0036 #include <algorithm> 0037 #include <iterator> // needed for std::insert_iterator on windows 0038 #include <type_traits> 0039 #include <QThread> 0040 0041 //Extremely slow 0042 // #define TEST_NORMAL_IMPORTS 0043 0044 QTEST_MAIN(TestDUChain) 0045 0046 using namespace KDevelop; 0047 using namespace Utils; 0048 0049 static_assert(std::is_nothrow_move_assignable<TypePtr<AbstractType>>(), "Why would a move assignment operator throw?"); 0050 static_assert(std::is_nothrow_move_constructible<TypePtr<AbstractType>>(), "Why would a move constructor throw?"); 0051 0052 static_assert(std::is_nothrow_move_assignable<DUChainPointer<DUContext>>(), "Why would a move assignment operator throw?"); 0053 static_assert(std::is_nothrow_move_constructible<DUChainPointer<DUContext>>(), "Why would a move constructor throw?"); 0054 0055 using Index = BasicSetRepository::Index; 0056 0057 struct Timer 0058 { 0059 Timer() 0060 { 0061 m_timer.start(); 0062 } 0063 qint64 elapsed() 0064 { 0065 return m_timer.nsecsElapsed(); 0066 } 0067 QElapsedTimer m_timer; 0068 }; 0069 0070 void TestDUChain::initTestCase() 0071 { 0072 AutoTestShell::init(); 0073 TestCore::initialize(Core::NoUi); 0074 0075 DUChain::self()->disablePersistentStorage(); 0076 CodeRepresentation::setDiskChangesForbidden(true); 0077 } 0078 0079 void TestDUChain::cleanupTestCase() 0080 { 0081 TestCore::shutdown(); 0082 } 0083 0084 #ifndef Q_OS_WIN 0085 void TestDUChain::testStringSets() 0086 { 0087 const unsigned int setCount = 8; 0088 const unsigned int choiceCount = 40; 0089 const unsigned int itemCount = 120; 0090 0091 QRecursiveMutex mutex; 0092 BasicSetRepository rep(QStringLiteral("test repository"), &mutex); 0093 0094 // qDebug() << "Start repository-layout: \n" << rep.dumpDotGraph(); 0095 0096 qint64 repositoryTime = 0; //Time spent on repository-operations 0097 qint64 genericTime = 0; //Time spend on equivalent operations with generic sets 0098 0099 qint64 repositoryIntersectionTime = 0; //Time spent on repository-operations 0100 qint64 genericIntersectionTime = 0; //Time spend on equivalent operations with generic sets 0101 qint64 qsetIntersectionTime = 0; //Time spend on equivalent operations with generic sets 0102 0103 qint64 repositoryUnionTime = 0; //Time spent on repository-operations 0104 qint64 genericUnionTime = 0; //Time spend on equivalent operations with generic sets 0105 0106 qint64 repositoryDifferenceTime = 0; //Time spent on repository-operations 0107 qint64 genericDifferenceTime = 0; //Time spend on equivalent operations with generic sets 0108 0109 Set sets[setCount]; 0110 std::set<Index> realSets[setCount]; 0111 for (unsigned int a = 0; a < setCount; a++) { 0112 std::set<Index> chosenIndices; 0113 unsigned int thisCount = rand() % choiceCount; 0114 if (thisCount == 0) 0115 thisCount = 1; 0116 0117 for (unsigned int b = 0; b < thisCount; b++) { 0118 Index choose = (rand() % itemCount) + 1; 0119 while (chosenIndices.find(choose) != chosenIndices.end()) { 0120 choose = (rand() % itemCount) + 1; 0121 } 0122 0123 Timer t; 0124 chosenIndices.insert(chosenIndices.end(), choose); 0125 genericTime += t.elapsed(); 0126 } 0127 0128 { 0129 Timer t; 0130 sets[a] = rep.createSet(chosenIndices); 0131 repositoryTime += t.elapsed(); 0132 } 0133 0134 realSets[a] = chosenIndices; 0135 0136 const std::set<Index> tempSet = sets[a].stdSet(); 0137 0138 if (tempSet != realSets[a]) { 0139 QString dbg = QStringLiteral("created set: "); 0140 for (unsigned int i : qAsConst(realSets[a])) 0141 dbg += QStringLiteral("%1 ").arg(i); 0142 0143 qDebug() << dbg; 0144 0145 dbg = QStringLiteral("repo. set: "); 0146 for (unsigned int i : tempSet) 0147 dbg += QStringLiteral("%1 ").arg(i); 0148 0149 qDebug() << dbg; 0150 0151 qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; 0152 QFAIL("sets are not the same!"); 0153 } 0154 } 0155 0156 for (int cycle = 0; cycle < 100; ++cycle) { 0157 if (cycle % 10 == 0) 0158 qDebug() << "cycle" << cycle; 0159 0160 for (unsigned int a = 0; a < setCount; a++) { 0161 for (unsigned int b = 0; b < setCount; b++) { 0162 /// ----- SUBTRACTION/DIFFERENCE 0163 std::set<Index> _realDifference; 0164 { 0165 Timer t; 0166 std::set_difference(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), 0167 realSets[b].end(), 0168 std::insert_iterator<std::set<Index>>(_realDifference, 0169 _realDifference.begin())); 0170 genericDifferenceTime += t.elapsed(); 0171 } 0172 0173 Set _difference; 0174 { 0175 Timer t; 0176 _difference = sets[a] - sets[b]; 0177 repositoryDifferenceTime += t.elapsed(); 0178 } 0179 0180 if (_difference.stdSet() != _realDifference) { 0181 { 0182 qDebug() << "SET a:"; 0183 QString dbg; 0184 for (unsigned int i : qAsConst(realSets[a])) 0185 dbg += QStringLiteral("%1 ").arg(i); 0186 0187 qDebug() << dbg; 0188 0189 qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; 0190 } 0191 { 0192 qDebug() << "SET b:"; 0193 QString dbg; 0194 for (unsigned int i : qAsConst(realSets[b])) 0195 dbg += QStringLiteral("%1 ").arg(i); 0196 0197 qDebug() << dbg; 0198 0199 qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; 0200 } 0201 0202 { 0203 const std::set<Index> tempSet = _difference.stdSet(); 0204 0205 qDebug() << "SET difference:"; 0206 QString dbg = QStringLiteral("real set: "); 0207 for (unsigned int i : qAsConst(_realDifference)) 0208 dbg += QStringLiteral("%1 ").arg(i); 0209 0210 qDebug() << dbg; 0211 0212 dbg = QStringLiteral("repo. set: "); 0213 for (unsigned int i : tempSet) 0214 dbg += QStringLiteral("%1 ").arg(i); 0215 0216 qDebug() << dbg; 0217 0218 qDebug() << "DOT-Graph:\n\n" << _difference.dumpDotGraph() << "\n\n"; 0219 } 0220 QFAIL("difference sets are not the same!"); 0221 } 0222 0223 /// ------ UNION 0224 0225 std::set<Index> _realUnion; 0226 { 0227 Timer t; 0228 std::set_union(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), 0229 realSets[b].end(), 0230 std::insert_iterator<std::set<Index>>(_realUnion, _realUnion.begin())); 0231 genericUnionTime += t.elapsed(); 0232 } 0233 0234 Set _union; 0235 { 0236 Timer t; 0237 _union = sets[a] + sets[b]; 0238 repositoryUnionTime += t.elapsed(); 0239 } 0240 0241 if (_union.stdSet() != _realUnion) { 0242 { 0243 qDebug() << "SET a:"; 0244 QString dbg; 0245 for (unsigned int i : qAsConst(realSets[a])) 0246 dbg += QStringLiteral("%1 ").arg(i); 0247 0248 qDebug() << dbg; 0249 0250 qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; 0251 } 0252 { 0253 qDebug() << "SET b:"; 0254 QString dbg; 0255 for (unsigned int i : qAsConst(realSets[b])) 0256 dbg += QStringLiteral("%1 ").arg(i); 0257 0258 qDebug() << dbg; 0259 0260 qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; 0261 } 0262 0263 { 0264 const std::set<Index> tempSet = _union.stdSet(); 0265 0266 qDebug() << "SET union:"; 0267 QString dbg = QStringLiteral("real set: "); 0268 for (unsigned int i : qAsConst(_realUnion)) 0269 dbg += QStringLiteral("%1 ").arg(i); 0270 0271 qDebug() << dbg; 0272 0273 dbg = QStringLiteral("repo. set: "); 0274 for (unsigned int i : tempSet) 0275 dbg += QStringLiteral("%1 ").arg(i); 0276 0277 qDebug() << dbg; 0278 0279 qDebug() << "DOT-Graph:\n\n" << _union.dumpDotGraph() << "\n\n"; 0280 } 0281 0282 QFAIL("union sets are not the same"); 0283 } 0284 0285 std::set<Index> _realIntersection; 0286 0287 /// -------- INTERSECTION 0288 { 0289 Timer t; 0290 std::set_intersection(realSets[a].begin(), realSets[a].end(), realSets[b].begin(), 0291 realSets[b].end(), 0292 std::insert_iterator<std::set<Index>>(_realIntersection, 0293 _realIntersection.begin())); 0294 genericIntersectionTime += t.elapsed(); 0295 } 0296 0297 //Just for fun: Test how fast QSet intersections are 0298 QSet<Index> first, second; 0299 for (unsigned int i : qAsConst(realSets[a])) { 0300 first.insert(i); 0301 } 0302 0303 for (unsigned int i : qAsConst(realSets[b])) { 0304 second.insert(i); 0305 } 0306 0307 { 0308 Timer t; 0309 QSet<Index> i = first.intersect(second); // clazy:exclude=unused-non-trivial-variable 0310 qsetIntersectionTime += t.elapsed(); 0311 } 0312 0313 Set _intersection; 0314 { 0315 Timer t; 0316 _intersection = sets[a] & sets[b]; 0317 repositoryIntersectionTime += t.elapsed(); 0318 } 0319 0320 if (_intersection.stdSet() != _realIntersection) { 0321 { 0322 qDebug() << "SET a:"; 0323 QString dbg; 0324 for (unsigned int i : qAsConst(realSets[a])) 0325 dbg += QStringLiteral("%1 ").arg(i); 0326 0327 qDebug() << dbg; 0328 0329 qDebug() << "DOT-Graph:\n\n" << sets[a].dumpDotGraph() << "\n\n"; 0330 } 0331 { 0332 qDebug() << "SET b:"; 0333 QString dbg; 0334 for (unsigned int i : qAsConst(realSets[b])) 0335 dbg += QStringLiteral("%1 ").arg(i); 0336 0337 qDebug() << dbg; 0338 0339 qDebug() << "DOT-Graph:\n\n" << sets[b].dumpDotGraph() << "\n\n"; 0340 } 0341 0342 { 0343 const std::set<Index> tempSet = _intersection.stdSet(); 0344 0345 qDebug() << "SET intersection:"; 0346 QString dbg = QStringLiteral("real set: "); 0347 for (unsigned int i : qAsConst(_realIntersection)) 0348 dbg += QStringLiteral("%1 ").arg(i); 0349 0350 qDebug() << dbg; 0351 0352 dbg = QStringLiteral("repo. set: "); 0353 for (unsigned int i : tempSet) 0354 dbg += QStringLiteral("%1 ").arg(i); 0355 0356 qDebug() << dbg; 0357 0358 qDebug() << "DOT-Graph:\n\n" << _intersection.dumpDotGraph() << "\n\n"; 0359 } 0360 QFAIL("intersection sets are not the same"); 0361 } 0362 } 0363 } 0364 0365 qDebug() << "cycle " << cycle; 0366 qDebug() << "ns needed for set-building: repository-set: " << float( repositoryTime ) 0367 << " generic-set: " << float( genericTime ); 0368 qDebug() << "ns needed for intersection: repository-sets: " << float( repositoryIntersectionTime ) 0369 << " generic-set: " << float( genericIntersectionTime ) << " QSet: " << float( qsetIntersectionTime ); 0370 qDebug() << "ns needed for union: repository-sets: " << float( repositoryUnionTime ) 0371 << " generic-set: " << float( genericUnionTime ); 0372 qDebug() << "ns needed for difference: repository-sets: " << float( repositoryDifferenceTime ) 0373 << " generic-set: " << float( genericDifferenceTime ); 0374 } 0375 } 0376 #endif 0377 0378 void TestDUChain::testSymbolTableValid() 0379 { 0380 DUChainReadLocker lock(DUChain::lock()); 0381 PersistentSymbolTable::self().dump(QTextStream(stdout)); 0382 } 0383 0384 void TestDUChain::testIndexedStrings() 0385 { 0386 int testCount = 600000; 0387 0388 QHash<QString, IndexedString> knownIndices; 0389 int a = 0; 0390 for (a = 0; a < testCount; ++a) { 0391 QString testString; 0392 int length = rand() % 10; 0393 for (int b = 0; b < length; ++b) 0394 testString.append(( char )(rand() % 6) + 'a'); 0395 0396 QByteArray array = testString.toUtf8(); 0397 //qDebug() << "checking with" << testString; 0398 //qDebug() << "checking" << a; 0399 IndexedString indexed(array.constData(), array.size(), IndexedString::hashString(array.constData(), 0400 array.size())); 0401 0402 QCOMPARE(indexed.str(), testString); 0403 if (knownIndices.contains(testString)) { 0404 QCOMPARE(indexed.index(), knownIndices[testString].index()); 0405 } else { 0406 knownIndices[testString] = indexed; 0407 } 0408 0409 if (a % (testCount / 10) == 0) 0410 qDebug() << a << "of" << testCount; 0411 } 0412 0413 qDebug() << a << "successful tests"; 0414 } 0415 0416 struct TestContext 0417 { 0418 TestContext() 0419 { 0420 static int number = 0; 0421 ++number; 0422 DUChainWriteLocker lock(DUChain::lock()); 0423 m_context = new TopDUContext(IndexedString(QStringLiteral("/test1/%1").arg(number)), RangeInRevision()); 0424 m_normalContext = new DUContext(RangeInRevision(), m_context); 0425 DUChain::self()->addDocumentChain(m_context); 0426 Q_ASSERT(IndexedDUContext(m_context).context() == m_context); 0427 } 0428 0429 ~TestContext() 0430 { 0431 const auto currentImporters = importers; 0432 for (TestContext* importer : currentImporters) { 0433 importer->unImport(QList<TestContext*>() << this); 0434 } 0435 0436 unImport(imports); 0437 0438 DUChainWriteLocker lock(DUChain::lock()); 0439 TopDUContextPointer tp(m_context); 0440 DUChain::self()->removeDocumentChain(static_cast<TopDUContext*>(m_context)); 0441 Q_ASSERT(!tp); 0442 } 0443 0444 void verify(QList<TestContext*> allContexts) 0445 { 0446 { 0447 DUChainReadLocker lock(DUChain::lock()); 0448 QCOMPARE(m_context->importedParentContexts().count(), imports.count()); 0449 } 0450 //Compute a closure of all children, and verify that they are imported. 0451 QSet<TestContext*> collected; 0452 collectImports(collected); 0453 collected.remove(this); 0454 0455 DUChainReadLocker lock(DUChain::lock()); 0456 for (TestContext* context : qAsConst(collected)) { 0457 QVERIFY(m_context->imports(context->m_context, CursorInRevision::invalid())); 0458 #ifdef TEST_NORMAL_IMPORTS 0459 QVERIFY(m_normalContext->imports(context->m_normalContext)); 0460 #endif 0461 } 0462 0463 //Verify that no other contexts are imported 0464 0465 for (TestContext* context : qAsConst(allContexts)) { 0466 if (context != this) { 0467 QVERIFY(collected.contains(context) || 0468 !m_context->imports(context->m_context, CursorInRevision::invalid())); 0469 #ifdef TEST_NORMAL_IMPORTS 0470 QVERIFY(collected.contains(context) || 0471 !m_normalContext->imports(context->m_normalContext, CursorInRevision::invalid())); 0472 #endif 0473 } 0474 } 0475 } 0476 0477 void collectImports(QSet<TestContext*>& collected) 0478 { 0479 if (collected.contains(this)) 0480 return; 0481 collected.insert(this); 0482 for (TestContext* context : qAsConst(imports)) { 0483 context->collectImports(collected); 0484 } 0485 } 0486 void import(TestContext* ctx) 0487 { 0488 if (imports.contains(ctx) || ctx == this) 0489 return; 0490 imports << ctx; 0491 ctx->importers << this; 0492 DUChainWriteLocker lock(DUChain::lock()); 0493 m_context->addImportedParentContext(ctx->m_context); 0494 #ifdef TEST_NORMAL_IMPORTS 0495 m_normalContext->addImportedParentContext(ctx->m_normalContext); 0496 #endif 0497 } 0498 0499 void unImport(QList<TestContext*> ctxList) 0500 { 0501 QList<TopDUContext*> list; 0502 QList<DUContext*> normalList; 0503 0504 for (TestContext* ctx : qAsConst(ctxList)) { 0505 if (!imports.contains(ctx)) 0506 continue; 0507 list << ctx->m_context; 0508 normalList << ctx->m_normalContext; 0509 0510 imports.removeAll(ctx); 0511 ctx->importers.removeAll(this); 0512 } 0513 0514 DUChainWriteLocker lock(DUChain::lock()); 0515 m_context->removeImportedParentContexts(list); 0516 0517 #ifdef TEST_NORMAL_IMPORTS 0518 for (DUContext* ctx : qAsConst(normalList)) { 0519 m_normalContext->removeImportedParentContext(ctx); 0520 } 0521 0522 #endif 0523 } 0524 0525 void clearImports() 0526 { 0527 { 0528 DUChainWriteLocker lock(DUChain::lock()); 0529 0530 m_context->clearImportedParentContexts(); 0531 m_normalContext->clearImportedParentContexts(); 0532 } 0533 const auto currentImports = imports; 0534 for (TestContext* ctx : currentImports) { 0535 imports.removeAll(ctx); 0536 ctx->importers.removeAll(this); 0537 } 0538 } 0539 0540 QList<TestContext*> imports; 0541 0542 private: 0543 0544 TopDUContext* m_context; 0545 DUContext* m_normalContext; 0546 QList<TestContext*> importers; 0547 }; 0548 0549 void collectReachableNodes(QSet<uint>& reachableNodes, uint currentNode) 0550 { 0551 if (!currentNode) 0552 return; 0553 reachableNodes.insert(currentNode); 0554 const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); 0555 Q_ASSERT(node); 0556 collectReachableNodes(reachableNodes, node->leftNode()); 0557 collectReachableNodes(reachableNodes, node->rightNode()); 0558 } 0559 0560 uint collectNaiveNodeCount(uint currentNode) 0561 { 0562 if (!currentNode) 0563 return 0; 0564 uint ret = 1; 0565 const Utils::SetNodeData* node = KDevelop::RecursiveImportRepository::repository()->nodeFromIndex(currentNode); 0566 Q_ASSERT(node); 0567 ret += collectNaiveNodeCount(node->leftNode()); 0568 ret += collectNaiveNodeCount(node->rightNode()); 0569 return ret; 0570 } 0571 0572 void TestDUChain::testImportStructure() 0573 { 0574 Timer total; 0575 qDebug() << "before: " << KDevelop::RecursiveImportRepository::repository()->statistics().print(); 0576 0577 ///Maintains a naive import-structure along with a real top-context import structure, and allows comparing both. 0578 int cycles = 5; 0579 //int cycles = 100; 0580 //srand(time(NULL)); 0581 for (int t = 0; t < cycles; ++t) { 0582 QList<TestContext*> allContexts; 0583 //Create a random structure 0584 int contextCount = 50; 0585 int verifyOnceIn = contextCount /*((contextCount*contextCount)/20)+1*/; //Verify once in every chances(not in all cases, because else the import-structure isn't built on-demand!) 0586 int clearOnceIn = contextCount; 0587 for (int a = 0; a < contextCount; a++) 0588 allContexts << new TestContext(); 0589 0590 for (int c = 0; c < cycles; ++c) { 0591 //qDebug() << "main-cycle" << t << "sub-cycle" << c; 0592 //Add random imports and compare 0593 for (int a = 0; a < contextCount; a++) { 0594 //Import up to 5 random other contexts into each context 0595 int importCount = rand() % 5; 0596 //qDebug() << "cnt> " << importCount; 0597 for (int i = 0; i < importCount; ++i) { 0598 //int importNr = rand() % contextCount; 0599 //qDebug() << "nmr > " << importNr; 0600 //allContexts[a]->import(allContexts[importNr]); 0601 allContexts[a]->import(allContexts[rand() % contextCount]); 0602 } 0603 0604 for (int b = 0; b < contextCount; b++) 0605 if (rand() % verifyOnceIn == 0) 0606 allContexts[b]->verify(allContexts); 0607 } 0608 0609 //Remove random imports and compare 0610 for (int a = 0; a < contextCount; a++) { 0611 //Import up to 5 random other contexts into each context 0612 int removeCount = rand() % 3; 0613 QSet<TestContext*> removeImports; 0614 for (int i = 0; i < removeCount; ++i) 0615 if (!allContexts[a]->imports.isEmpty()) 0616 removeImports.insert(allContexts[a]->imports[rand() % allContexts[a]->imports.count()]); 0617 0618 allContexts[a]->unImport(removeImports.values()); 0619 0620 for (int b = 0; b < contextCount; b++) 0621 if (rand() % verifyOnceIn == 0) 0622 allContexts[b]->verify(allContexts); 0623 } 0624 0625 for (int a = 0; a < contextCount; a++) { 0626 if (rand() % clearOnceIn == 0) { 0627 allContexts[a]->clearImports(); 0628 allContexts[a]->verify(allContexts); 0629 } 0630 } 0631 } 0632 0633 qDebug() << "after: " << 0634 KDevelop::RecursiveImportRepository::repository()->statistics().print(); 0635 0636 for (int a = 0; a < contextCount; ++a) 0637 delete allContexts[a]; 0638 0639 allContexts.clear(); 0640 qDebug() << "after cleanup: " << 0641 KDevelop::RecursiveImportRepository::repository()->statistics().print(); 0642 } 0643 0644 qDebug() << "total ns needed for import-structure test:" << float( total.elapsed()); 0645 } 0646 0647 class TestWorker 0648 : public QObject 0649 { 0650 Q_OBJECT 0651 0652 public Q_SLOTS: 0653 void lockForWrite() 0654 { 0655 for (int i = 0; i < 10000; ++i) { 0656 DUChainWriteLocker lock; 0657 } 0658 } 0659 void lockForRead() 0660 { 0661 for (int i = 0; i < 10000; ++i) { 0662 DUChainReadLocker lock; 0663 } 0664 } 0665 void lockForReadWrite() 0666 { 0667 for (int i = 0; i < 10000; ++i) { 0668 { 0669 DUChainReadLocker lock; 0670 } 0671 { 0672 DUChainWriteLocker lock; 0673 } 0674 } 0675 } 0676 static QSharedPointer<QThread> createWorkerThread(const char* workerSlot) 0677 { 0678 auto* thread = new QThread; 0679 auto* worker = new TestWorker; 0680 connect(thread, SIGNAL(started()), worker, workerSlot); 0681 connect(thread, &QThread::finished, worker, &TestWorker::deleteLater); 0682 worker->moveToThread(thread); 0683 return QSharedPointer<QThread>(thread); 0684 } 0685 }; 0686 0687 class ThreadList 0688 : public QVector<QSharedPointer<QThread>> 0689 { 0690 public: 0691 bool join(int timeout) 0692 { 0693 for (const QSharedPointer<QThread>& thread : qAsConst(*this)) { 0694 // quit event loop 0695 Q_ASSERT(thread->isRunning()); 0696 thread->quit(); 0697 // wait for finish 0698 if (!thread->wait(timeout)) { 0699 return false; 0700 } 0701 Q_ASSERT(thread->isFinished()); 0702 } 0703 0704 return true; 0705 } 0706 void start() 0707 { 0708 for (const QSharedPointer<QThread>& thread : qAsConst(*this)) { 0709 thread->start(); 0710 } 0711 } 0712 }; 0713 0714 void TestDUChain::testLockForWrite() 0715 { 0716 ThreadList threads; 0717 for (int i = 0; i < 10; ++i) { 0718 threads << TestWorker::createWorkerThread(SLOT(lockForWrite())); 0719 } 0720 0721 threads.start(); 0722 QBENCHMARK { 0723 { 0724 DUChainWriteLocker lock; 0725 } 0726 { 0727 DUChainReadLocker lock; 0728 } 0729 } 0730 QVERIFY(threads.join(1000)); 0731 } 0732 0733 void TestDUChain::testLockForRead() 0734 { 0735 ThreadList threads; 0736 for (int i = 0; i < 10; ++i) { 0737 threads << TestWorker::createWorkerThread(SLOT(lockForRead())); 0738 } 0739 0740 threads.start(); 0741 QBENCHMARK { 0742 DUChainReadLocker lock; 0743 } 0744 QVERIFY(threads.join(1000)); 0745 } 0746 0747 void TestDUChain::testLockForReadWrite() 0748 { 0749 ThreadList threads; 0750 for (int i = 0; i < 10; ++i) { 0751 threads << TestWorker::createWorkerThread(SLOT(lockForReadWrite())); 0752 } 0753 0754 threads.start(); 0755 QBENCHMARK { 0756 DUChainWriteLocker lock; 0757 } 0758 QVERIFY(threads.join(1000)); 0759 } 0760 0761 void TestDUChain::testProblemSerialization() 0762 { 0763 DUChain::self()->disablePersistentStorage(false); 0764 0765 auto parent = ProblemPointer{new Problem}; 0766 parent->setDescription(QStringLiteral("parent")); 0767 0768 auto child = ProblemPointer{new Problem}; 0769 child->setDescription(QStringLiteral("child")); 0770 parent->addDiagnostic(child); 0771 0772 const IndexedString url("/my/test/file"); 0773 0774 TopDUContextPointer smartTop; 0775 0776 { // serialize 0777 DUChainWriteLocker lock; 0778 auto file = new ParsingEnvironmentFile(url); 0779 auto top = new TopDUContext(url, {}, file); 0780 0781 top->addProblem(parent); 0782 QCOMPARE(top->problems().size(), 1); 0783 auto p = top->problems().at(0); 0784 QCOMPARE(p->description(), QStringLiteral("parent")); 0785 QCOMPARE(p->diagnostics().size(), 1); 0786 auto c = p->diagnostics().first(); 0787 QCOMPARE(c->description(), QStringLiteral("child")); 0788 0789 DUChain::self()->addDocumentChain(top); 0790 QVERIFY(DUChain::self()->chainForDocument(url)); 0791 smartTop = top; 0792 } 0793 0794 DUChain::self()->storeToDisk(); 0795 0796 ProblemPointer parent_deserialized; 0797 IProblem::Ptr child_deserialized; 0798 0799 { // deserialize 0800 DUChainWriteLocker lock; 0801 QVERIFY(!smartTop); 0802 auto top = DUChain::self()->chainForDocument(url); 0803 QVERIFY(top); 0804 smartTop = top; 0805 QCOMPARE(top->problems().size(), 1); 0806 parent_deserialized = top->problems().at(0); 0807 QCOMPARE(parent_deserialized->diagnostics().size(), 1); 0808 child_deserialized = parent_deserialized->diagnostics().first(); 0809 0810 QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); 0811 QCOMPARE(child_deserialized->description(), QStringLiteral("child")); 0812 0813 top->clearProblems(); 0814 QVERIFY(top->problems().isEmpty()); 0815 0816 QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); 0817 QCOMPARE(child_deserialized->description(), QStringLiteral("child")); 0818 0819 DUChain::self()->removeDocumentChain(top); 0820 0821 QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); 0822 QCOMPARE(child_deserialized->description(), QStringLiteral("child")); 0823 0824 QVERIFY(!smartTop); 0825 } 0826 0827 DUChain::self()->disablePersistentStorage(true); 0828 0829 QCOMPARE(parent->description(), QStringLiteral("parent")); 0830 QCOMPARE(child->description(), QStringLiteral("child")); 0831 QCOMPARE(parent_deserialized->description(), QStringLiteral("parent")); 0832 QCOMPARE(child_deserialized->description(), QStringLiteral("child")); 0833 0834 parent->clearDiagnostics(); 0835 QVERIFY(parent->diagnostics().isEmpty()); 0836 } 0837 0838 void TestDUChain::testIdentifiers() 0839 { 0840 QualifiedIdentifier aj(QStringLiteral("::Area::jump")); 0841 QCOMPARE(aj.count(), 2); 0842 QCOMPARE(aj.explicitlyGlobal(), true); 0843 QCOMPARE(aj.at(0), Identifier(QStringLiteral("Area"))); 0844 QCOMPARE(aj.at(1), Identifier(QStringLiteral("jump"))); 0845 0846 QualifiedIdentifier aj2 = QualifiedIdentifier(QStringLiteral("Area::jump")); 0847 QCOMPARE(aj2.count(), 2); 0848 QCOMPARE(aj2.explicitlyGlobal(), false); 0849 QCOMPARE(aj2.at(0), Identifier(QStringLiteral("Area"))); 0850 QCOMPARE(aj2.at(1), Identifier(QStringLiteral("jump"))); 0851 QVERIFY(aj != aj2); 0852 0853 QVERIFY(QualifiedIdentifier(QString()) == QualifiedIdentifier()); 0854 QVERIFY(QualifiedIdentifier(QString()).index() == QualifiedIdentifier().index()); 0855 0856 QualifiedIdentifier ajt(QStringLiteral("Area::jump::test")); 0857 QualifiedIdentifier jt(QStringLiteral("jump::test")); 0858 QualifiedIdentifier ajt2(QStringLiteral("Area::jump::tes")); 0859 0860 QualifiedIdentifier t(QStringLiteral(" Area<A,B>::jump <F> ::tes<C>")); 0861 QCOMPARE(t.count(), 3); 0862 QCOMPARE(t.at(0).templateIdentifiersCount(), 2u); 0863 QCOMPARE(t.at(1).templateIdentifiersCount(), 1u); 0864 QCOMPARE(t.at(2).templateIdentifiersCount(), 1u); 0865 QCOMPARE(t.at(0).identifier().str(), QStringLiteral("Area")); 0866 QCOMPARE(t.at(1).identifier().str(), QStringLiteral("jump")); 0867 QCOMPARE(t.at(2).identifier().str(), QStringLiteral("tes")); 0868 0869 QualifiedIdentifier op1(QStringLiteral("operator<")); 0870 QualifiedIdentifier op2(QStringLiteral("operator<=")); 0871 QualifiedIdentifier op3(QStringLiteral("operator>")); 0872 QualifiedIdentifier op4(QStringLiteral("operator>=")); 0873 QualifiedIdentifier op5(QStringLiteral("operator()")); 0874 QualifiedIdentifier op6(QStringLiteral("operator( )")); 0875 QCOMPARE(op1.count(), 1); 0876 QCOMPARE(op2.count(), 1); 0877 QCOMPARE(op3.count(), 1); 0878 QCOMPARE(op4.count(), 1); 0879 QCOMPARE(op5.count(), 1); 0880 QCOMPARE(op6.count(), 1); 0881 QCOMPARE(op4.toString(), QStringLiteral("operator>=")); 0882 QCOMPARE(op3.toString(), QStringLiteral("operator>")); 0883 QCOMPARE(op1.toString(), QStringLiteral("operator<")); 0884 QCOMPARE(op2.toString(), QStringLiteral("operator<=")); 0885 QCOMPARE(op5.toString(), QStringLiteral("operator()")); 0886 QCOMPARE(op6.toString(), QStringLiteral("operator( )")); 0887 QCOMPARE(QualifiedIdentifier(QStringLiteral("Area<A,B>::jump <F> ::tes<C>")).index(), t.index()); 0888 QCOMPARE(op4.index(), QualifiedIdentifier(QStringLiteral("operator>=")).index()); 0889 0890 QualifiedIdentifier pushTest(QStringLiteral("foo")); 0891 QCOMPARE(pushTest.count(), 1); 0892 QCOMPARE(pushTest.toString(), QStringLiteral("foo")); 0893 pushTest.push(Identifier(QStringLiteral("bar"))); 0894 QCOMPARE(pushTest.count(), 2); 0895 QCOMPARE(pushTest.toString(), QStringLiteral("foo::bar")); 0896 pushTest.push(QualifiedIdentifier(QStringLiteral("baz::asdf"))); 0897 QCOMPARE(pushTest.count(), 4); 0898 QCOMPARE(pushTest.toString(), QStringLiteral("foo::bar::baz::asdf")); 0899 QualifiedIdentifier mergeTest = pushTest.merge(QualifiedIdentifier(QStringLiteral("meh::muh"))); 0900 QCOMPARE(mergeTest.count(), 6); 0901 QCOMPARE(mergeTest.toString(), QStringLiteral("meh::muh::foo::bar::baz::asdf")); 0902 QualifiedIdentifier plusTest = QualifiedIdentifier(QStringLiteral("la::lu")) + 0903 QualifiedIdentifier(QStringLiteral("ba::bu")); 0904 QCOMPARE(plusTest.count(), 4); 0905 QCOMPARE(plusTest.toString(), QStringLiteral("la::lu::ba::bu")); 0906 ///@todo create a big randomized test for the identifier repository(check that indices are the same) 0907 } 0908 0909 void TestDUChain::testTypePtr() 0910 { 0911 AbstractType::Ptr abstractT; 0912 QVERIFY(!abstractT); 0913 0914 IntegralType::Ptr integralT(new IntegralType(IntegralType::TypeDouble)); 0915 0916 abstractT = integralT; 0917 QCOMPARE(abstractT, integralT); 0918 0919 DelayedType::Ptr delayedT(new DelayedType); 0920 QVERIFY(abstractT != delayedT); 0921 0922 auto abstractT2 = abstractT; 0923 QCOMPARE(abstractT2, integralT); 0924 0925 auto abstractT3 = AbstractType::Ptr(integralT); 0926 QCOMPARE(abstractT2, integralT); 0927 } 0928 0929 #if 0 0930 0931 ///NOTE: the "unit tests" below are not automated, they - so far - require 0932 /// human interpretation which is not useful for a unit test! 0933 /// someone should investigate what the expected output should be 0934 /// and add proper QCOMPARE/QVERIFY checks accordingly 0935 0936 ///FIXME: this needs to be rewritten in order to remove dependencies on formerly run unit tests 0937 void TestDUChain::testImportCache() 0938 { 0939 KDevelop::globalItemRepositoryRegistry().printAllStatistics(); 0940 0941 KDevelop::RecursiveImportRepository::repository()->printStatistics(); 0942 0943 //Analyze the whole existing import-cache 0944 //This is very expensive, since it involves loading all existing top-contexts 0945 uint topContextCount = DUChain::self()->newTopContextIndex(); 0946 0947 uint analyzedCount = 0; 0948 uint totalImportCount = 0; 0949 uint naiveNodeCount = 0; 0950 QSet<uint> reachableNodes; 0951 0952 DUChainReadLocker lock(DUChain::lock()); 0953 for (uint a = 0; a < topContextCount; ++a) { 0954 if (a % qMax(1u, topContextCount / 100) == 0) { 0955 qDebug() << "progress:" << (a * 100) / topContextCount; 0956 } 0957 TopDUContext* context = DUChain::self()->chainForIndex(a); 0958 if (context) { 0959 TopDUContext::IndexedRecursiveImports imports = context->recursiveImportIndices(); 0960 ++analyzedCount; 0961 totalImportCount += imports.set().count(); 0962 collectReachableNodes(reachableNodes, imports.setIndex()); 0963 naiveNodeCount += collectNaiveNodeCount(imports.setIndex()); 0964 } 0965 } 0966 0967 QVERIFY(analyzedCount); 0968 qDebug() << "average total count of imports:" << totalImportCount / analyzedCount; 0969 qDebug() << "count of reachable nodes:" << reachableNodes.size(); 0970 qDebug() << "naive node-count:" << naiveNodeCount << "sharing compression factor:" << 0971 (( float )reachableNodes.size()) / (( float )naiveNodeCount); 0972 } 0973 0974 #endif 0975 0976 void TestDUChain::benchCodeModel() 0977 { 0978 const IndexedString file("testFile"); 0979 0980 QVERIFY(!QTypeInfo<KDevelop::CodeModelItem>::isStatic); 0981 0982 int i = 0; 0983 QBENCHMARK { 0984 CodeModel::self().addItem(file, QualifiedIdentifier("testQID" + QString::number(i++)), 0985 KDevelop::CodeModelItem::Class); 0986 } 0987 } 0988 0989 void TestDUChain::benchTypeRegistry() 0990 { 0991 IntegralTypeData data; 0992 data.m_dataType = IntegralType::TypeInt; 0993 data.typeClassId = IntegralType::Identity; 0994 data.inRepository = true; 0995 data.m_modifiers = 42; 0996 data.m_dynamic = false; 0997 data.refCount = 1; 0998 0999 IntegralTypeData to; 1000 1001 QFETCH(int, func); 1002 1003 QBENCHMARK { 1004 switch (func) { 1005 case 0: 1006 TypeSystem::self().dataClassSize(data); 1007 break; 1008 case 1: 1009 TypeSystem::self().dynamicSize(data); 1010 break; 1011 case 2: { 1012 AbstractType::Ptr t(TypeSystem::self().create(&data)); 1013 break; 1014 } 1015 case 3: 1016 TypeSystem::self().isFactoryLoaded(data); 1017 break; 1018 case 4: 1019 TypeSystem::self().copy(data, to, !data.m_dynamic); 1020 break; 1021 case 5: 1022 TypeSystem::self().copy(data, to, data.m_dynamic); 1023 break; 1024 case 6: 1025 TypeSystem::self().callDestructor(&data); 1026 break; 1027 } 1028 } 1029 } 1030 1031 void TestDUChain::benchTypeRegistry_data() 1032 { 1033 QTest::addColumn<int>("func"); 1034 QTest::newRow("dataClassSize") << 0; 1035 QTest::newRow("dynamicSize") << 1; 1036 QTest::newRow("create") << 2; 1037 QTest::newRow("isFactoryLoaded") << 3; 1038 QTest::newRow("copy") << 4; 1039 QTest::newRow("copyNonDynamic") << 5; 1040 QTest::newRow("callDestructor") << 6; 1041 } 1042 1043 void TestDUChain::benchDuchainReadLocker() 1044 { 1045 QBENCHMARK { 1046 DUChainReadLocker lock; 1047 } 1048 } 1049 1050 void TestDUChain::benchDuchainWriteLocker() 1051 { 1052 QBENCHMARK { 1053 DUChainWriteLocker lock; 1054 } 1055 } 1056 1057 void TestDUChain::benchDUChainItemFactory_copy() 1058 { 1059 DUChainItemFactory<Declaration, DeclarationData> factory; 1060 DeclarationData from, to; 1061 from.classId = Declaration::Identity; 1062 1063 QFETCH(int, constant); 1064 1065 bool c = constant; 1066 1067 QBENCHMARK { 1068 factory.copy(from, to, c); 1069 if (constant == 2) { 1070 c = !c; 1071 } 1072 } 1073 } 1074 1075 void TestDUChain::benchDUChainItemFactory_copy_data() 1076 { 1077 QTest::addColumn<int>("constant"); 1078 QTest::newRow("non-const") << 0; 1079 QTest::newRow("const") << 1; 1080 QTest::newRow("flip") << 2; 1081 } 1082 1083 void TestDUChain::benchDeclarationQualifiedIdentifier() 1084 { 1085 QVector<DUContext*> contexts; 1086 contexts.reserve(10); 1087 DUChainWriteLocker lock; 1088 auto topDUContext = new TopDUContext(IndexedString("/tmp/something"), {0, 0, INT_MAX, INT_MAX}); 1089 DUChain::self()->addDocumentChain(topDUContext); 1090 contexts << topDUContext; 1091 for (int i = 1; i < contexts.capacity(); ++i) { 1092 contexts << new DUContext({0, 0, INT_MAX, INT_MAX}, contexts.at(i - 1)); 1093 contexts.last()->setLocalScopeIdentifier(QualifiedIdentifier(QString::number(i))); 1094 } 1095 1096 auto dec = new Declaration({0, 0, 0, 1}, contexts.last()); 1097 dec->setIdentifier(Identifier(QStringLiteral("myDecl"))); 1098 1099 qDebug() << "start benchmark!"; 1100 qint64 count = 0; 1101 QBENCHMARK { 1102 count += dec->qualifiedIdentifier().count(); 1103 } 1104 QVERIFY(count > 0); 1105 1106 // manually delete as QScopedPointer does not work well with QBENCHMARK 1107 delete dec; 1108 DUChain::self()->removeDocumentChain(topDUContext); 1109 } 1110 1111 #include "test_duchain.moc" 1112 #include "moc_test_duchain.cpp"