kde-extraapps/kdevplatform/language/backgroundparser/tests/test_backgroundparser.cpp
2015-07-26 14:23:17 +03:00

295 lines
9.3 KiB
C++

/*
* This file is part of KDevelop
*
* Copyright 2012 by Sven Brauch <svenbrauch@googlemail.com>
* Copyright 2012 by Milian Wolff <mail@milianw.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "test_backgroundparser.h"
#include <QTest>
#include <QElapsedTimer>
#include <QApplication>
#include <qtest_kde.h>
#include <KTextEditor/EditorChooser>
#include <KTextEditor/View>
#include <tests/autotestshell.h>
#include <tests/testcore.h>
#include <tests/testlanguagecontroller.h>
#include <language/duchain/duchain.h>
#include <language/backgroundparser/backgroundparser.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/iplugincontroller.h>
#include "testlanguagesupport.h"
#include "testparsejob.h"
QTEST_KDEMAIN(TestBackgroundparser, GUI)
#define QVERIFY_RETURN(statement, retval) \
do { if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) return retval; } while (0)
using namespace KDevelop;
JobPlan::JobPlan()
{
}
void JobPlan::addJob(const JobPrototype& job)
{
m_jobs << job;
}
void JobPlan::clear()
{
m_jobs.clear();
m_finishedJobs.clear();
m_createdJobs.clear();
}
void JobPlan::parseJobCreated(ParseJob* job)
{
// e.g. for the benchmark
if (m_jobs.isEmpty()) {
return;
}
TestParseJob* testJob = dynamic_cast<TestParseJob*>(job);
Q_ASSERT(testJob);
kDebug() << "assigning propierties for created job" << testJob->document().toUrl();
testJob->duration_ms = jobForUrl(testJob->document()).m_duration;
m_createdJobs.append(testJob->document());
}
bool JobPlan::runJobs(int timeoutMS)
{
// add parse jobs
foreach(const JobPrototype& job, m_jobs) {
ICore::self()->languageController()->backgroundParser()->addDocument(
job.m_url, TopDUContext::Empty, job.m_priority, this, job.m_flags
);
}
ICore::self()->languageController()->backgroundParser()->parseDocuments();
QElapsedTimer t;
t.start();
while ( !t.hasExpired(timeoutMS) && m_jobs.size() != m_finishedJobs.size() ) {
QTest::qWait(50);
}
QVERIFY_RETURN(m_jobs.size() == m_createdJobs.size(), false);
QVERIFY_RETURN(m_finishedJobs.size() == m_jobs.size(), false);
// verify they're started in the right order
int currentBestPriority = BackgroundParser::BestPriority;
foreach ( const IndexedString& url, m_createdJobs ) {
const JobPrototype p = jobForUrl(url);
QVERIFY_RETURN(p.m_priority >= currentBestPriority, false);
currentBestPriority = p.m_priority;
}
return true;
}
JobPrototype JobPlan::jobForUrl(const IndexedString& url)
{
foreach(const JobPrototype& job, m_jobs) {
if (job.m_url == url) {
return job;
}
}
return JobPrototype();
}
void JobPlan::updateReady(const IndexedString& url, const ReferencedTopDUContext& /*context*/)
{
kDebug() << "update ready on " << url.toUrl();
const JobPrototype job = jobForUrl(url);
QVERIFY(job.m_url.toUrl().isValid());
if (job.m_flags & ParseJob::RequiresSequentialProcessing) {
// ensure that all jobs that respect sequential processing
// with lower priority have been run
foreach(const JobPrototype& otherJob, m_jobs) {
if (otherJob.m_url == job.m_url) {
continue;
}
if (otherJob.m_flags & ParseJob::RespectsSequentialProcessing &&
otherJob.m_priority < job.m_priority)
{
QVERIFY(m_finishedJobs.contains(otherJob.m_url));
}
}
}
QVERIFY(!m_finishedJobs.contains(job.m_url));
m_finishedJobs << job.m_url;
}
void TestBackgroundparser::initTestCase()
{
AutoTestShell::init();
TestCore* core = TestCore::initialize(Core::NoUi);
DUChain::self()->disablePersistentStorage();
TestLanguageController* langController = new TestLanguageController(core);
core->setLanguageController(langController);
langController->backgroundParser()->setThreadCount(4);
TestLanguageSupport* testLang = new TestLanguageSupport();
connect(testLang, SIGNAL(parseJobCreated(KDevelop::ParseJob*)),
&m_jobPlan, SLOT(parseJobCreated(KDevelop::ParseJob*)));
langController->addTestLanguage(testLang, QStringList() << "text/plain");
}
void TestBackgroundparser::cleanupTestCase()
{
TestCore::shutdown();
}
void TestBackgroundparser::init()
{
m_jobPlan.clear();
}
void TestBackgroundparser::testParseOrdering_foregroundThread()
{
m_jobPlan.clear();
// prove that background parsing happens with sequential flags although there is a high-priority
// foreground thread (active document being edited, ...) running all the time.
// the long-running high-prio job
m_jobPlan.addJob(JobPrototype(KUrl("test_fgt_hp.txt"), -500, ParseJob::IgnoresSequentialProcessing, 630));
// several small background jobs
for ( int i = 0; i < 10; i++ ) {
m_jobPlan.addJob(JobPrototype(KUrl("test_fgt_lp__" + QString::number(i) + ".txt"), i, ParseJob::FullSequentialProcessing, 40));
}
// not enough time if the small jobs run after the large one
QVERIFY(m_jobPlan.runJobs(700));
}
void TestBackgroundparser::testParseOrdering_noSequentialProcessing()
{
m_jobPlan.clear();
for ( int i = 0; i < 20; i++ ) {
// create jobs with no sequential processing, and different priorities
m_jobPlan.addJob(JobPrototype(KUrl("test_nsp1__" + QString::number(i) + ".txt"), i, ParseJob::IgnoresSequentialProcessing, i));
}
for ( int i = 0; i < 8; i++ ) {
// create a few more jobs with the same priority
m_jobPlan.addJob(JobPrototype(KUrl("test_nsp2__" + QString::number(i) + ".txt"), 10, ParseJob::IgnoresSequentialProcessing, i));
}
QVERIFY(m_jobPlan.runJobs(1000));
}
void TestBackgroundparser::testParseOrdering_lockup()
{
m_jobPlan.clear();
for ( int i = 3; i > 0; i-- ) {
// add 3 jobs which do not care about sequential processing, at 4 threads it should take no more than 1s to process them
m_jobPlan.addJob(JobPrototype(KUrl("test" + QString::number(i) + ".txt"), i, ParseJob::IgnoresSequentialProcessing, 200));
}
// add one job which requires sequential processing with high priority
m_jobPlan.addJob(JobPrototype(KUrl("test_hp.txt"), -200, ParseJob::FullSequentialProcessing, 200));
// verify that the low-priority nonsequential jobs are run simultaneously with the other one.
QVERIFY(m_jobPlan.runJobs(700));
}
void TestBackgroundparser::testParseOrdering_simple()
{
m_jobPlan.clear();
for ( int i = 20; i > 0; i-- ) {
// the job with priority i should be at place i in the finished list
// (lower priority value -> should be parsed first)
ParseJob::SequentialProcessingFlags flags = ParseJob::FullSequentialProcessing;
m_jobPlan.addJob(JobPrototype(KUrl("test" + QString::number(i) + ".txt"),
i, flags));
}
// also add a few jobs which ignore the processing
for ( int i = 0; i < 5; ++i ) {
m_jobPlan.addJob(JobPrototype(KUrl("test2-" + QString::number(i) + ".txt"),
BackgroundParser::NormalPriority,
ParseJob::IgnoresSequentialProcessing));
}
QVERIFY(m_jobPlan.runJobs(1000));
}
void TestBackgroundparser::benchmark()
{
const int jobs = 10000;
QVector<IndexedString> jobUrls;
jobUrls.reserve(jobs);
for ( int i = 0; i < jobs; ++i ) {
jobUrls << IndexedString("test" + QString::number(i) + ".txt");
}
QBENCHMARK {
foreach ( const IndexedString& url, jobUrls ) {
ICore::self()->languageController()->backgroundParser()->addDocument(url);
}
ICore::self()->languageController()->backgroundParser()->parseDocuments();
while ( ICore::self()->languageController()->backgroundParser()->queuedCount() ) {
QTest::qWait(50);
}
}
}
void TestBackgroundparser::benchmarkDocumentChanges()
{
KTextEditor::Editor* editor = KTextEditor::EditorChooser::editor();
QVERIFY(editor);
KTextEditor::Document* doc = editor->createDocument(this);
QVERIFY(doc);
doc->saveAs(KUrl::fromPath(QDir::tempPath() + "/__kdevbackgroundparsertest_benchmark.txt"));
DocumentChangeTracker tracker(doc);
doc->setText("hello world");
// required for proper benchmark results
doc->createView(0);
QBENCHMARK {
for ( int i = 0; i < 5000; i++ ) {
doc->startEditing();
doc->insertText(KTextEditor::Cursor(0, 0), "This is a test line.\n");
doc->endEditing();
QApplication::processEvents();
}
}
doc->clear();
doc->save();
}
#include "moc_test_backgroundparser.cpp"