diff options
author | Christian Stenger <christian.stenger@digia.com> | 2014-11-04 12:35:00 +0100 |
---|---|---|
committer | Christian Stenger <christian.stenger@theqtcompany.com> | 2014-12-04 13:52:15 +0100 |
commit | d5913658b2d0f2bd04a5522e32bc8496414cb0ed (patch) | |
tree | 8af3a13d88a2d3d7aea83e4bb2369ab06f5a4c4f /plugins/autotest | |
parent | 431d15bd0caa70588c7118ef8c0b32850d08ab82 (diff) | |
download | qt-creator-d5913658b2d0f2bd04a5522e32bc8496414cb0ed.tar.gz |
Perform test execution in separate thread and add progress bar
Diffstat (limited to 'plugins/autotest')
-rw-r--r-- | plugins/autotest/autotestconstants.h | 1 | ||||
-rw-r--r-- | plugins/autotest/testrunner.cpp | 292 | ||||
-rw-r--r-- | plugins/autotest/testrunner.h | 7 |
3 files changed, 158 insertions, 142 deletions
diff --git a/plugins/autotest/autotestconstants.h b/plugins/autotest/autotestconstants.h index df3e26be11..3cfc770d45 100644 --- a/plugins/autotest/autotestconstants.h +++ b/plugins/autotest/autotestconstants.h @@ -26,6 +26,7 @@ const char ACTION_ID[] = "AutoTest.Action"; const char MENU_ID[] = "AutoTest.Menu"; const char AUTOTEST_ID[] = "AutoTest.ATP"; const char AUTOTEST_CONTEXT[] = "Auto Tests"; +const char TASK_INDEX[] = "AutoTest.Task.Index"; } // namespace Autotest } // namespace Constants diff --git a/plugins/autotest/testrunner.cpp b/plugins/autotest/testrunner.cpp index ded6fab9f1..eae9d4c58a 100644 --- a/plugins/autotest/testrunner.cpp +++ b/plugins/autotest/testrunner.cpp @@ -16,22 +16,32 @@ ** ****************************************************************************/ +#include "autotestconstants.h" #include "testresultspane.h" #include "testrunner.h" #include <QDebug> // REMOVE +#include <coreplugin/progressmanager/futureprogress.h> +#include <coreplugin/progressmanager/progressmanager.h> + #include <projectexplorer/buildmanager.h> #include <projectexplorer/project.h> #include <projectexplorer/projectexplorer.h> #include <projectexplorer/projectexplorersettings.h> +#include <utils/multitask.h> + +#include <QFuture> +#include <QFutureInterface> #include <QTime> namespace Autotest { namespace Internal { -static TestRunner* m_instance = 0; +static TestRunner *m_instance = 0; +static QProcess *m_runner = 0; +static QFutureInterface<void> *m_currentFuture = 0; TestRunner *TestRunner::instance() { @@ -44,13 +54,6 @@ TestRunner::TestRunner(QObject *parent) : QObject(parent), m_building(false) { - m_runner.setReadChannelMode(QProcess::MergedChannels); - m_runner.setReadChannel(QProcess::StandardOutput); - - connect(&m_runner, &QProcess::readyReadStandardOutput, - this, &TestRunner::processOutput, Qt::DirectConnection); - connect(&m_runner, SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(onRunnerFinished(int,QProcess::ExitStatus)), Qt::DirectConnection); } TestRunner::~TestRunner() @@ -58,6 +61,8 @@ TestRunner::~TestRunner() qDeleteAll(m_selectedTests); m_selectedTests.clear(); m_instance = 0; + if (m_runner) + delete m_runner; } void TestRunner::setSelectedTests(const QList<TestConfiguration *> &selected) @@ -67,80 +72,6 @@ void TestRunner::setSelectedTests(const QList<TestConfiguration *> &selected) m_selectedTests = selected; } -void TestRunner::runTests() -{ - if (m_selectedTests.empty()) { - TestResultsPane::instance()->addTestResult( - TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, - tr("*** No tests selected - canceling Test Run ***"))); - return; - } - - ProjectExplorer::Project *project = m_selectedTests.at(0)->project(); - - if (!project) {// add a warning or info to output? possible at all? - return; - } - - ProjectExplorer::ProjectExplorerPlugin *pep = ProjectExplorer::ProjectExplorerPlugin::instance(); - ProjectExplorer::Internal::ProjectExplorerSettings pes = pep->projectExplorerSettings(); - if (pes.buildBeforeDeploy) { - if (!project->hasActiveBuildSettings()) { - TestResultsPane::instance()->addTestResult( - TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, - tr("*** Project is not configured - canceling Test Run ***"))); - return; - } - buildProject(project); - while (m_building) { - qApp->processEvents(); - } - - if (!m_buildSucceeded) { - TestResultsPane::instance()->addTestResult( - TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, - tr("*** Build failed - canceling Test Run ***"))); - return; - } - } - - int testCaseCount = 0; - foreach (const TestConfiguration *config, m_selectedTests) - testCaseCount += config->testCaseCount(); - - // clear old log and output pane - m_xmlLog.clear(); - TestResultsPane::instance()->clearContents(); - - emit testRunStarted(); - - foreach (TestConfiguration *tc, m_selectedTests) { - QString cmd = tc->targetFile(); - QString workDir = tc->workingDirectory(); - QStringList args; - Utils::Environment env = tc->environment(); - - args << QLatin1String("-xml"); - if (tc->testCases().count()) - args << tc->testCases(); - - exec(cmd, args, workDir, env); - } - qDebug("test run finished"); - emit testRunFinished(); -} - -void TestRunner::stopTestRun() -{ - if (m_runner.state() != QProcess::NotRunning) { - m_runner.kill(); - m_runner.waitForFinished(); - TestResultsPane::instance()->addTestResult( - TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, - tr("*** Test Run canceled by user ***"))); - } -} - /******************** XML line parser helper ********************/ static bool xmlStartsWith(const QString &code, const QString &start, QString &result) @@ -182,8 +113,10 @@ static bool xmlExtractTypeFileLine(const QString &code, const QString &tagStart, /****************** XML line parser helper end ******************/ -void TestRunner::processOutput() +void processOutput() { + if (!m_runner) + return; static QString className; static QString testCase; static QString dataTag; @@ -196,11 +129,9 @@ void TestRunner::processOutput() static QString qtVersion; static QString qtestVersion; - while (m_runner.canReadLine()) { + while (m_runner->canReadLine()) { // TODO Qt5 uses UTF-8 - while Qt4 uses ISO-8859-1 - could this be a problem? - QString line = QString::fromUtf8(m_runner.readLine()); - line = line.trimmed(); - m_xmlLog.append(line); + const QString line = QString::fromUtf8(m_runner->readLine()).trimmed(); if (line.isEmpty() || line.startsWith(QLatin1String("<?xml version"))) { className = QString(); continue; @@ -233,7 +164,7 @@ void TestRunner::processOutput() if (line.endsWith(QLatin1String("/>"))) { TestResult testResult(className, testCase, dataTag, result, description); if (!file.isEmpty()) - file = QFileInfo(m_runner.workingDirectory(), file).canonicalFilePath(); + file = QFileInfo(m_runner->workingDirectory(), file).canonicalFilePath(); testResult.setFileName(file); testResult.setLine(lineNumber); TestResultsPane::instance()->addTestResult(testResult); @@ -243,18 +174,19 @@ void TestRunner::processOutput() if (line == QLatin1String("</Message>") || line == QLatin1String("</Incident>")) { TestResult testResult(className, testCase, dataTag, result, description); if (!file.isEmpty()) - file = QFileInfo(m_runner.workingDirectory(), file).canonicalFilePath(); + file = QFileInfo(m_runner->workingDirectory(), file).canonicalFilePath(); testResult.setFileName(file); testResult.setLine(lineNumber); TestResultsPane::instance()->addTestResult(testResult); description = QString(); } else if (line == QLatin1String("</TestFunction>") && !duration.isEmpty()) { TestResult testResult(className, testCase, QString(), ResultType::MESSAGE_INTERNAL, - tr("execution took %1ms").arg(duration)); + QObject::tr("execution took %1ms").arg(duration)); TestResultsPane::instance()->addTestResult(testResult); + m_currentFuture->setProgressValue(m_currentFuture->progressValue() + 1); } else if (line == QLatin1String("</TestCase>") && !duration.isEmpty()) { TestResult testResult(className, QString(), QString(), ResultType::MESSAGE_INTERNAL, - tr("Test execution took %1ms").arg(duration)); + QObject::tr("Test execution took %1ms").arg(duration)); TestResultsPane::instance()->addTestResult(testResult); } else if (readingDescription) { if (line.endsWith(QLatin1String("]]></Description>"))) { @@ -268,44 +200,17 @@ void TestRunner::processOutput() } else if (xmlStartsWith(line, QLatin1String("<QtVersion>"), qtVersion)) { TestResultsPane::instance()->addTestResult( TestResult(QString(), QString(), QString(), ResultType::MESSAGE_INTERNAL, - tr("Qt Version: %1").arg(qtVersion))); + QObject::tr("Qt Version: %1").arg(qtVersion))); } else if (xmlStartsWith(line, QLatin1String("<QTestVersion>"), qtestVersion)) { TestResultsPane::instance()->addTestResult( TestResult(QString(), QString(), QString(), ResultType::MESSAGE_INTERNAL, - tr("QTest Version: %1").arg(qtestVersion))); + QObject::tr("QTest Version: %1").arg(qtestVersion))); } else { // qDebug() << "Unhandled line:" << line; // TODO remove } } } -void TestRunner::onRunnerFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - qDebug("runnerFinished"); -} - -void TestRunner::buildProject(ProjectExplorer::Project *project) -{ - m_building = true; - m_buildSucceeded = false; - ProjectExplorer::BuildManager *mgr = static_cast<ProjectExplorer::BuildManager *>( - ProjectExplorer::BuildManager::instance()); - ProjectExplorer::ProjectExplorerPlugin *pep = ProjectExplorer::ProjectExplorerPlugin::instance(); - pep->buildProject(project); - connect(mgr, &ProjectExplorer::BuildManager::buildQueueFinished, - this, &TestRunner::buildFinished); -} - -void TestRunner::buildFinished(bool success) -{ - ProjectExplorer::BuildManager *mgr = static_cast<ProjectExplorer::BuildManager *>( - ProjectExplorer::BuildManager::instance()); - disconnect(mgr, &ProjectExplorer::BuildManager::buildQueueFinished, - this, &TestRunner::buildFinished); - m_building = false; - m_buildSucceeded = success; -} - static QString which(const QString &path, const QString &cmd) { if (path.isEmpty() || cmd.isEmpty()) @@ -319,7 +224,7 @@ static QString which(const QString &path, const QString &cmd) #endif foreach (const QString p, paths) { - QString fName = p + QDir::separator() + cmd; + const QString fName = p + QDir::separator() + cmd; QFileInfo fi(fName); if (fi.exists() && fi.isExecutable()) return fName; @@ -338,13 +243,9 @@ static QString which(const QString &path, const QString &cmd) return QString(); } -bool TestRunner::exec(const QString &cmd, const QStringList &args, const QString &workingDir, - const Utils::Environment &env, int timeout) +bool performExec(const QString &cmd, const QStringList &args, const QString &workingDir, + const Utils::Environment &env, int timeout = 60000) { - if (m_runner.state() != QProcess::NotRunning) { // kill the runner if it's running already - m_runner.kill(); - m_runner.waitForFinished(); - } QString runCmd; if (!QDir::toNativeSeparators(cmd).contains(QDir::separator())) { if (env.hasKey(QLatin1String("PATH"))) @@ -354,33 +255,154 @@ bool TestRunner::exec(const QString &cmd, const QStringList &args, const QString } if (runCmd.isEmpty()) { - qDebug("Could not find cmd..."); + TestResultsPane::instance()->addTestResult( + TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, + QObject::tr("*** Could not find command '%1' ***").arg(cmd))); return false; } - m_runner.setWorkingDirectory(workingDir); - m_runner.setProcessEnvironment(env.toProcessEnvironment()); + m_runner->setWorkingDirectory(workingDir); + m_runner->setProcessEnvironment(env.toProcessEnvironment()); QTime executionTimer; if (args.count()) { - m_runner.start(runCmd, args); + m_runner->start(runCmd, args); } else { - m_runner.start(runCmd); + m_runner->start(runCmd); } - bool ok = m_runner.waitForStarted(); + bool ok = m_runner->waitForStarted(); executionTimer.start(); if (ok) { - while (m_runner.state() == QProcess::Running && executionTimer.elapsed() < timeout) { + while (m_runner->state() == QProcess::Running && executionTimer.elapsed() < timeout) { + if (m_currentFuture->isCanceled()) { + m_runner->kill(); + m_runner->waitForFinished(); + TestResultsPane::instance()->addTestResult( + TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, + QObject::tr("*** Test Run canceled by user ***"))); + } qApp->processEvents(); } } if (ok && executionTimer.elapsed() < timeout) { - return m_runner.exitCode() == 0; + return m_runner->exitCode() == 0; } else { return false; } } +void performTestRun(QFutureInterface<void> &future, const QList<TestConfiguration *> selectedTests) +{ + int testCaseCount = 0; + foreach (const TestConfiguration *config, selectedTests) + testCaseCount += config->testCaseCount(); + + m_currentFuture = &future; + m_runner = new QProcess; + m_runner->setReadChannelMode(QProcess::MergedChannels); + m_runner->setReadChannel(QProcess::StandardOutput); + + QObject::connect(m_runner, &QProcess::readyReadStandardOutput, &processOutput); + + future.setProgressRange(0, testCaseCount); + future.setProgressValue(0); + + foreach (const TestConfiguration *tc, selectedTests) { + if (future.isCanceled()) + break; + QString cmd = tc->targetFile(); + QString workDir = tc->workingDirectory(); + QStringList args; + Utils::Environment env = tc->environment(); + + args << QLatin1String("-xml"); + if (tc->testCases().count()) + args << tc->testCases(); + + performExec(cmd, args, workDir, env); + } + future.setProgressValue(testCaseCount); + + delete m_runner; + m_runner = 0; + m_currentFuture = 0; +} + +void TestRunner::runTests() +{ + if (m_selectedTests.empty()) { + TestResultsPane::instance()->addTestResult( + TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, + tr("*** No tests selected - canceling Test Run ***"))); + return; + } + + ProjectExplorer::Project *project = m_selectedTests.at(0)->project(); + + if (!project) // add a warning or info to output? possible at all? + return; + + ProjectExplorer::ProjectExplorerPlugin *pep = ProjectExplorer::ProjectExplorerPlugin::instance(); + ProjectExplorer::Internal::ProjectExplorerSettings pes = pep->projectExplorerSettings(); + if (pes.buildBeforeDeploy) { + if (!project->hasActiveBuildSettings()) { + TestResultsPane::instance()->addTestResult( + TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, + tr("*** Project is not configured - canceling Test Run ***"))); + return; + } + buildProject(project); + while (m_building) { + qApp->processEvents(); + } + + if (!m_buildSucceeded) { + TestResultsPane::instance()->addTestResult( + TestResult(QString(), QString(), QString(), ResultType::MESSAGE_FATAL, + tr("*** Build failed - canceling Test Run ***"))); + return; + } + } + + // clear old log and output pane + TestResultsPane::instance()->clearContents(); + + emit testRunStarted(); + QFuture<void> future = QtConcurrent::run(&performTestRun , m_selectedTests); + Core::FutureProgress *progress = Core::ProgressManager::addTask(future, tr("Running Tests"), + Autotest::Constants::TASK_INDEX); + connect(progress, &Core::FutureProgress::finished, + TestRunner::instance(), &TestRunner::testRunFinished); +} + +void TestRunner::buildProject(ProjectExplorer::Project *project) +{ + m_building = true; + m_buildSucceeded = false; + ProjectExplorer::BuildManager *mgr = static_cast<ProjectExplorer::BuildManager *>( + ProjectExplorer::BuildManager::instance()); + ProjectExplorer::ProjectExplorerPlugin *pep = ProjectExplorer::ProjectExplorerPlugin::instance(); + pep->buildProject(project); + connect(mgr, &ProjectExplorer::BuildManager::buildQueueFinished, + this, &TestRunner::buildFinished); +} + +void TestRunner::buildFinished(bool success) +{ + ProjectExplorer::BuildManager *mgr = static_cast<ProjectExplorer::BuildManager *>( + ProjectExplorer::BuildManager::instance()); + disconnect(mgr, &ProjectExplorer::BuildManager::buildQueueFinished, + this, &TestRunner::buildFinished); + m_building = false; + m_buildSucceeded = success; +} + +void TestRunner::stopTestRun() +{ + if (m_runner && m_runner->state() != QProcess::NotRunning && m_currentFuture) + m_currentFuture->cancel(); +} + } // namespace Internal } // namespace Autotest diff --git a/plugins/autotest/testrunner.h b/plugins/autotest/testrunner.h index 6ed530d05c..1a725eada7 100644 --- a/plugins/autotest/testrunner.h +++ b/plugins/autotest/testrunner.h @@ -51,23 +51,16 @@ public slots: void stopTestRun(); private slots: - void processOutput(); - void onRunnerFinished(int exitCode, QProcess::ExitStatus exitStatus); void buildProject(ProjectExplorer::Project *project); void buildFinished(bool success); private: explicit TestRunner(QObject *parent = 0); - bool exec(const QString &cmd, const QStringList &args, const QString &workingDir = QString(), - const Utils::Environment &env = Utils::Environment(), int timeout = 60000); - QProcess m_runner; QList<TestConfiguration *> m_selectedTests; bool m_building; bool m_buildSucceeded; - QStringList m_xmlLog; // holds complete xml log of the last test run - }; } // namespace Internal |