diff --git a/plugins/autotest/autotestconstants.h b/plugins/autotest/autotestconstants.h index df3e26be11b..3cfc770d45d 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 ded6fab9f1e..eae9d4c58a7 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 // REMOVE +#include +#include + #include #include #include #include +#include + +#include +#include #include namespace Autotest { namespace Internal { -static TestRunner* m_instance = 0; +static TestRunner *m_instance = 0; +static QProcess *m_runner = 0; +static QFutureInterface *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 &selected) @@ -67,80 +72,6 @@ void TestRunner::setSelectedTests(const QList &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(""))) { 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("") || line == 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); description = QString(); } else if (line == QLatin1String("") && !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("") && !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("]]>"))) { @@ -268,20 +200,180 @@ void TestRunner::processOutput() } else if (xmlStartsWith(line, QLatin1String(""), 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)) { 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) +static QString which(const QString &path, const QString &cmd) { - qDebug("runnerFinished"); + if (path.isEmpty() || cmd.isEmpty()) + return QString(); + + QStringList paths; +#ifdef Q_OS_WIN + paths = path.split(QLatin1Char(';')); +#else + paths = path.split(QLatin1Char(':')); +#endif + + foreach (const QString p, paths) { + const QString fName = p + QDir::separator() + cmd; + QFileInfo fi(fName); + if (fi.exists() && fi.isExecutable()) + return fName; +#ifdef Q_OS_WIN + fi = QFileInfo(fName + QLatin1String(".exe")); + if (fi.exists()) + return fi.absoluteFilePath(); + fi = QFileInfo(fName + QLatin1String(".bat")); + if (fi.exists()) + return fi.absoluteFilePath(); + fi = QFileInfo(fName + QLatin1String(".cmd")); + if (fi.exists()) + return fi.absoluteFilePath(); +#endif + } + return QString(); +} + +bool performExec(const QString &cmd, const QStringList &args, const QString &workingDir, + const Utils::Environment &env, int timeout = 60000) +{ + QString runCmd; + if (!QDir::toNativeSeparators(cmd).contains(QDir::separator())) { + if (env.hasKey(QLatin1String("PATH"))) + runCmd = which(env.value(QLatin1String("PATH")), cmd); + } else if (QFileInfo(cmd).exists()) { + runCmd = cmd; + } + + if (runCmd.isEmpty()) { + 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()); + QTime executionTimer; + + if (args.count()) { + m_runner->start(runCmd, args); + } else { + m_runner->start(runCmd); + } + + bool ok = m_runner->waitForStarted(); + executionTimer.start(); + if (ok) { + 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; + } else { + return false; + } +} + +void performTestRun(QFutureInterface &future, const QList 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 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) @@ -306,80 +398,10 @@ void TestRunner::buildFinished(bool success) m_buildSucceeded = success; } -static QString which(const QString &path, const QString &cmd) +void TestRunner::stopTestRun() { - if (path.isEmpty() || cmd.isEmpty()) - return QString(); - - QStringList paths; -#ifdef Q_OS_WIN - paths = path.split(QLatin1Char(';')); -#else - paths = path.split(QLatin1Char(':')); -#endif - - foreach (const QString p, paths) { - QString fName = p + QDir::separator() + cmd; - QFileInfo fi(fName); - if (fi.exists() && fi.isExecutable()) - return fName; -#ifdef Q_OS_WIN - fi = QFileInfo(fName + QLatin1String(".exe")); - if (fi.exists()) - return fi.absoluteFilePath(); - fi = QFileInfo(fName + QLatin1String(".bat")); - if (fi.exists()) - return fi.absoluteFilePath(); - fi = QFileInfo(fName + QLatin1String(".cmd")); - if (fi.exists()) - return fi.absoluteFilePath(); -#endif - } - return QString(); -} - -bool TestRunner::exec(const QString &cmd, const QStringList &args, const QString &workingDir, - const Utils::Environment &env, int timeout) -{ - 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"))) - runCmd = which(env.value(QLatin1String("PATH")), cmd); - } else if (QFileInfo(cmd).exists()) { - runCmd = cmd; - } - - if (runCmd.isEmpty()) { - qDebug("Could not find cmd..."); - return false; - } - - m_runner.setWorkingDirectory(workingDir); - m_runner.setProcessEnvironment(env.toProcessEnvironment()); - QTime executionTimer; - - if (args.count()) { - m_runner.start(runCmd, args); - } else { - m_runner.start(runCmd); - } - - bool ok = m_runner.waitForStarted(); - executionTimer.start(); - if (ok) { - while (m_runner.state() == QProcess::Running && executionTimer.elapsed() < timeout) { - qApp->processEvents(); - } - } - if (ok && executionTimer.elapsed() < timeout) { - return m_runner.exitCode() == 0; - } else { - return false; - } + if (m_runner && m_runner->state() != QProcess::NotRunning && m_currentFuture) + m_currentFuture->cancel(); } } // namespace Internal diff --git a/plugins/autotest/testrunner.h b/plugins/autotest/testrunner.h index 6ed530d05cb..1a725eada77 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 m_selectedTests; bool m_building; bool m_buildSucceeded; - QStringList m_xmlLog; // holds complete xml log of the last test run - }; } // namespace Internal