Commit d5913658 authored by Christian Stenger's avatar Christian Stenger Committed by Christian Stenger
Browse files

Perform test execution in separate thread and add progress bar

parent 431d15bd
......@@ -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
......
......@@ -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
......@@ -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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment