summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Stenger <christian.stenger@theqtcompany.com>2015-05-08 13:36:17 +0200
committerChristian Stenger <christian.stenger@theqtcompany.com>2015-06-09 07:43:42 +0300
commit9d4509540b7a67fcb4f4087708eb207164bdee5f (patch)
tree763e1b51ff65ab95bfb60f9411b2a6e973ea1ebe
parent8df59beb6a99fdc30d76cc0aa72b2d91441b3d77 (diff)
downloadqt-creator-9d4509540b7a67fcb4f4087708eb207164bdee5f.tar.gz
Provide some interaction with squish related tools
This patch provides a rudimentary way to interact with squishserver and squishrunner. It allows to a run single test case or several test cases of a single suite. The results will be actually stored already in their respective results.xml files. Change-Id: I7983584e40dc6b0ef4dacff2c72559b7408c6433 Reviewed-by: Riitta-Leena Miettinen <riitta-leena.miettinen@theqtcompany.com> Reviewed-by: Robert Loehning <robert.loehning@theqtcompany.com>
-rw-r--r--plugins/autotest/autotest.pro6
-rw-r--r--plugins/autotest/autotest.qbs4
-rw-r--r--plugins/autotest/testnavigationwidget.cpp22
-rw-r--r--plugins/autotest/testresultspane.cpp39
-rw-r--r--plugins/autotest/testresultspane.h5
-rw-r--r--plugins/autotest/testsquishfilehandler.cpp55
-rw-r--r--plugins/autotest/testsquishfilehandler.h8
-rw-r--r--plugins/autotest/testsquishtools.cpp678
-rw-r--r--plugins/autotest/testsquishtools.h121
-rw-r--r--plugins/autotest/testtreemodel.cpp21
-rw-r--r--plugins/autotest/testtreemodel.h2
11 files changed, 945 insertions, 16 deletions
diff --git a/plugins/autotest/autotest.pro b/plugins/autotest/autotest.pro
index b60a83f656..85ab704576 100644
--- a/plugins/autotest/autotest.pro
+++ b/plugins/autotest/autotest.pro
@@ -31,7 +31,8 @@ SOURCES += \
testxmloutputreader.cpp \
testsquishfilehandler.cpp \
opensquishsuitesdialog.cpp \
- testsquishutils.cpp
+ testsquishutils.cpp \
+ testsquishtools.cpp
HEADERS += \
squishsettings.h \
@@ -58,7 +59,8 @@ HEADERS += \
testxmloutputreader.h \
testsquishfilehandler.h \
opensquishsuitesdialog.h \
- testsquishutils.h
+ testsquishutils.h \
+ testsquishtools.h
RESOURCES += \
autotest.qrc
diff --git a/plugins/autotest/autotest.qbs b/plugins/autotest/autotest.qbs
index 09581cfdee..faf80f0484 100644
--- a/plugins/autotest/autotest.qbs
+++ b/plugins/autotest/autotest.qbs
@@ -77,7 +77,9 @@ QtcPlugin {
"opensquishsuitesdialog.h",
"opensquishsuitesdialog.ui",
"testsquishutils.cpp",
- "testsquishutils.h"
+ "testsquishutils.h",
+ "testsquishtools.cpp",
+ "testsquishtools.h"
]
Group {
diff --git a/plugins/autotest/testnavigationwidget.cpp b/plugins/autotest/testnavigationwidget.cpp
index 33c5f4342b..26c1bfd3d8 100644
--- a/plugins/autotest/testnavigationwidget.cpp
+++ b/plugins/autotest/testnavigationwidget.cpp
@@ -95,6 +95,11 @@ TestNavigationWidget::TestNavigationWidget(QWidget *parent) :
connect(m_view, &QTreeView::collapsed, this, &TestNavigationWidget::onCollapsed);
connect(m_model, &QAbstractItemModel::rowsInserted, this, &TestNavigationWidget::onRowsInserted);
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &TestNavigationWidget::onRowsRemoved);
+
+ connect(m_view, &TestTreeView::runTestCase,
+ TestSquishFileHandler::instance(), &TestSquishFileHandler::runTestCase);
+ connect(m_view, &TestTreeView::runTestSuite,
+ TestSquishFileHandler::instance(), &TestSquishFileHandler::runTestSuite);
}
TestNavigationWidget::~TestNavigationWidget()
@@ -111,12 +116,15 @@ bool TestNavigationWidget::handleSquishContextMenuEvent(QContextMenuEvent *event
const QModelIndexList list = m_view->selectionModel()->selectedIndexes();
// 3 columns - but we only need the data of the first column as the others contain only an icon
if (list.size() == 3) {
- QRect rect(m_view->visualRect(list.first()));
+ const QModelIndex &index = list.first();
+ QRect rect(m_view->visualRect(index));
rect.adjust(0, 0, defaultSectionSize * 2, 0);
if (rect.contains(event->pos())) {
- TestTreeItem::Type type = TestTreeItem::toTestType(list.first().data(TypeRole).toInt());
+ TestTreeItem::Type type = TestTreeItem::toTestType(index.data(TypeRole).toInt());
if (type == TestTreeItem::SQUISH_TESTCASE) {
+ const QString caseName = index.data().toString();
+ const QString suiteName = index.parent().data().toString();
isSquishMenu = true;
QAction *runThisTestCase = new QAction(tr("Run This Test Case"), &menu);
menu.addAction(runThisTestCase);
@@ -125,8 +133,12 @@ bool TestNavigationWidget::handleSquishContextMenuEvent(QContextMenuEvent *event
menu.addAction(deleteTestCase);
deleteTestCase->setEnabled(enabled);
menu.addSeparator();
+
+ connect(runThisTestCase, &QAction::triggered, [suiteName, caseName] () {
+ TestSquishFileHandler::instance()->runTestCase(suiteName, caseName);
+ });
} else if (type == TestTreeItem::SQUISH_SUITE) {
- const QString suiteName = list.first().data().toString();
+ const QString suiteName = index.data().toString();
isSquishMenu = true;
QAction *runThisTestSuite = new QAction(tr("Run This Test Suite"), &menu);
menu.addAction(runThisTestSuite);
@@ -143,6 +155,10 @@ bool TestNavigationWidget::handleSquishContextMenuEvent(QContextMenuEvent *event
deleteTestSuite->setEnabled(enabled);
menu.addSeparator();
+ connect(runThisTestSuite, &QAction::triggered,
+ [suiteName] () {
+ TestSquishFileHandler::instance()->runTestSuite(suiteName);
+ });
connect(closeTestSuite, &QAction::triggered,
[suiteName] () {
TestSquishFileHandler::instance()->closeTestSuite(suiteName);
diff --git a/plugins/autotest/testresultspane.cpp b/plugins/autotest/testresultspane.cpp
index e5d9bd6084..1378b6cfcd 100644
--- a/plugins/autotest/testresultspane.cpp
+++ b/plugins/autotest/testresultspane.cpp
@@ -37,6 +37,8 @@
#include <QDebug>
#include <QHBoxLayout>
#include <QMenu>
+#include <QPlainTextEdit>
+#include <QTabWidget>
#include <QToolButton>
#include <QVBoxLayout>
@@ -48,6 +50,9 @@ TestResultsPane::TestResultsPane(QObject *parent) :
m_context(new Core::IContext(this)),
m_wasVisibleBefore(false)
{
+ m_outputPane = new QTabWidget;
+ m_outputPane->setDocumentMode(true);
+
m_outputWidget = new QWidget;
QVBoxLayout *outputLayout = new QVBoxLayout;
outputLayout->setMargin(0);
@@ -86,6 +91,15 @@ TestResultsPane::TestResultsPane(QObject *parent) :
createToolButtons();
+ m_runnerServerLog = new QPlainTextEdit;
+ m_runnerServerLog->setMaximumBlockCount(10000);
+
+ m_outputPane->addTab(m_outputWidget, tr("Test Results"));
+ m_outputPane->addTab(m_runnerServerLog, tr("Runner/Server Log"));
+
+ connect(m_outputPane, &QTabWidget::currentChanged, [this] () {
+ navigateStateChanged();
+ });
connect(m_treeView, &Utils::TreeView::activated, this, &TestResultsPane::onItemActivated);
connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged,
trd, &TestResultDelegate::currentChanged);
@@ -151,14 +165,19 @@ void TestResultsPane::addTestResult(const TestResult &result)
navigateStateChanged();
}
+void TestResultsPane::addLogoutput(const QString &output)
+{
+ m_runnerServerLog->appendPlainText(output);
+}
+
QWidget *TestResultsPane::outputWidget(QWidget *parent)
{
- if (m_outputWidget) {
- m_outputWidget->setParent(parent);
+ if (m_outputPane) {
+ m_outputPane->setParent(parent);
} else {
qDebug() << "This should not happen...";
}
- return m_outputWidget;
+ return m_outputPane;
}
QList<QWidget *> TestResultsPane::toolBarWidgets() const
@@ -178,9 +197,13 @@ int TestResultsPane::priorityInStatusBar() const
void TestResultsPane::clearContents()
{
- m_filterModel->clearTestResults();
- navigateStateChanged();
- m_summaryWidget->setVisible(false);
+ if (m_outputPane->currentIndex() == 0) {
+ m_filterModel->clearTestResults();
+ navigateStateChanged();
+ m_summaryWidget->setVisible(false);
+ } else if (m_outputPane->currentIndex() == 1) {
+ m_runnerServerLog->clear();
+ }
}
void TestResultsPane::visibilityChanged(bool visible)
@@ -208,7 +231,7 @@ void TestResultsPane::setFocus()
bool TestResultsPane::hasFocus() const
{
- return m_treeView->hasFocus();
+ return m_treeView->hasFocus() || m_runnerServerLog->hasFocus();
}
bool TestResultsPane::canFocus() const
@@ -218,7 +241,7 @@ bool TestResultsPane::canFocus() const
bool TestResultsPane::canNavigate() const
{
- return true;
+ return m_outputPane->currentIndex() == 0; // only support navigation for test results
}
bool TestResultsPane::canNext() const
diff --git a/plugins/autotest/testresultspane.h b/plugins/autotest/testresultspane.h
index 0d76c56ff4..765b6ff9ff 100644
--- a/plugins/autotest/testresultspane.h
+++ b/plugins/autotest/testresultspane.h
@@ -28,6 +28,8 @@ class QFrame;
class QLabel;
class QModelIndex;
class QMenu;
+class QPlainTextEdit;
+class QTabWidget;
class QToolButton;
QT_END_NAMESPACE
@@ -73,6 +75,7 @@ signals:
public slots:
void addTestResult(const TestResult &result);
+ void addLogoutput(const QString &output);
private slots:
void onItemActivated(const QModelIndex &index);
@@ -90,7 +93,9 @@ private:
void onTestRunFinished();
void onTestTreeModelChanged();
+ QTabWidget *m_outputPane;
QWidget *m_outputWidget;
+ QPlainTextEdit *m_runnerServerLog;
QFrame *m_summaryWidget;
QLabel *m_summaryLabel;
Utils::TreeView *m_treeView;
diff --git a/plugins/autotest/testsquishfilehandler.cpp b/plugins/autotest/testsquishfilehandler.cpp
index 26a2f718b0..b69e5e2544 100644
--- a/plugins/autotest/testsquishfilehandler.cpp
+++ b/plugins/autotest/testsquishfilehandler.cpp
@@ -20,6 +20,11 @@
#include "opensquishsuitesdialog.h"
#include "testsquishfilehandler.h"
#include "testsquishutils.h"
+#include "testsquishtools.h"
+
+#include <coreplugin/icore.h>
+
+#include <utils/qtcassert.h>
#include <QDir>
#include <QFileInfo>
@@ -30,11 +35,18 @@ namespace Internal {
static TestSquishFileHandler *m_instance = 0;
-TestSquishFileHandler::TestSquishFileHandler(QObject *parent) : QObject(parent)
+TestSquishFileHandler::TestSquishFileHandler(QObject *parent)
+ : QObject(parent),
+ m_squishTools(new TestSquishTools)
{
m_instance = this;
}
+TestSquishFileHandler::~TestSquishFileHandler()
+{
+ delete m_squishTools;
+}
+
TestSquishFileHandler *TestSquishFileHandler::instance()
{
if (!m_instance)
@@ -64,7 +76,7 @@ void TestSquishFileHandler::openTestSuites()
const QStringList cases = TestSquishUtils::validTestCases(suite);
const QFileInfo suiteConf(suiteDir, QLatin1String("suite.conf"));
if (m_suites.contains(suiteDir.dirName())) {
- if (QMessageBox::question(0, tr("Suite Already Open"),
+ if (QMessageBox::question(Core::ICore::dialogParent(), tr("Suite Already Open"),
tr("A test suite with the name \"%1\" is already open. "
"The opened test suite will be closed and replaced "
"with the new one.").arg(suiteDir.dirName()),
@@ -110,5 +122,44 @@ void TestSquishFileHandler::closeAllTestSuites()
m_suites.clear();
}
+void TestSquishFileHandler::runTestCase(const QString &suiteName, const QString &testCaseName)
+{
+ QTC_ASSERT(!suiteName.isEmpty() && !testCaseName.isEmpty(), return);
+
+ if (m_squishTools->state() != TestSquishTools::Idle)
+ return;
+ const QDir suitePath = QFileInfo(m_suites.value(suiteName)).absoluteDir();
+ if (!suitePath.exists() || !suitePath.isReadable()) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Test Suite Path Not Accessible"),
+ tr("The path \"%1\" does not exist or is not accessible.\n"
+ "Refusing to run test case \"%2\".")
+ .arg(QDir::toNativeSeparators(suitePath.absolutePath()))
+ .arg(testCaseName));
+ return;
+ }
+
+ m_squishTools->runTestCases(suitePath.absolutePath(), QStringList() << testCaseName);
+}
+
+void TestSquishFileHandler::runTestSuite(const QString &suiteName)
+{
+ QTC_ASSERT(!suiteName.isEmpty(), return);
+
+ if (m_squishTools->state() != TestSquishTools::Idle)
+ return;
+
+ const QString suiteConf = m_suites.value(suiteName);
+ const QDir suitePath = QFileInfo(suiteConf).absoluteDir();
+ if (!suitePath.exists() || !suitePath.isReadable()) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Test Suite Path Not Accessible"),
+ tr("The path \"%1\" does not exist or is not accessible.\n"
+ "Refusing to run test cases.")
+ .arg(QDir::toNativeSeparators(suitePath.absolutePath())));
+ return;
+ }
+ QStringList testCases = TestTreeModel::instance()->getSelectedSquishTestCases(suiteConf);
+ m_squishTools->runTestCases(suitePath.absolutePath(), testCases);
+}
+
} // namespace Internal
} // namespace Autotest
diff --git a/plugins/autotest/testsquishfilehandler.h b/plugins/autotest/testsquishfilehandler.h
index a63d435929..1a09952604 100644
--- a/plugins/autotest/testsquishfilehandler.h
+++ b/plugins/autotest/testsquishfilehandler.h
@@ -30,11 +30,14 @@
namespace Autotest {
namespace Internal {
+class TestSquishTools;
+
class TestSquishFileHandler : public QObject
{
Q_OBJECT
public:
explicit TestSquishFileHandler(QObject *parent = 0);
+ ~TestSquishFileHandler();
static TestSquishFileHandler *instance();
void openTestSuites();
@@ -46,8 +49,13 @@ signals:
void testTreeItemModified(const TestTreeItem &item, TestTreeModel::Type type, const QString &file);
void testTreeItemsRemoved(const QString &filePath, TestTreeModel::Type type);
+public slots:
+ void runTestCase(const QString &suiteName, const QString &testCaseName);
+ void runTestSuite(const QString &suiteName);
+
private:
QMap<QString, QString> m_suites;
+ TestSquishTools *m_squishTools;
};
diff --git a/plugins/autotest/testsquishtools.cpp b/plugins/autotest/testsquishtools.cpp
new file mode 100644
index 0000000000..49739271ee
--- /dev/null
+++ b/plugins/autotest/testsquishtools.cpp
@@ -0,0 +1,678 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at
+** http://www.qt.io/contact-us
+**
+** This file is part of the Qt Creator Enterprise Auto Test Add-on.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://www.qt.io/contact-us
+**
+****************************************************************************/
+
+#include "autotestplugin.h"
+#include "squishsettings.h"
+#include "testsquishtools.h"
+#include "testresultspane.h"
+
+#include <QDebug> // TODO remove
+
+#include <coreplugin/icore.h>
+
+#include <utils/environment.h>
+#include <utils/hostosinfo.h>
+#include <utils/qtcassert.h>
+
+#include <QApplication>
+#include <QDateTime>
+#include <QDir>
+#include <QFile>
+#include <QFileSystemWatcher>
+#include <QMessageBox>
+#include <QTimer>
+#include <QWindow>
+
+namespace Autotest {
+namespace Internal {
+
+// make this configurable?
+static const QString resultsDirectory = QFileInfo(QDir::home(),
+ QLatin1String(".squishQC/Test Results")
+ ).absoluteFilePath();
+
+TestSquishTools::TestSquishTools(QObject *parent)
+ : QObject(parent),
+ m_serverProcess(0),
+ m_runnerProcess(0),
+ m_serverPort(-1),
+ m_request(None),
+ m_state(Idle),
+ m_currentResultsXML(0),
+ m_resultsFileWatcher(0),
+ m_testRunning(false)
+{
+ connect(this, &TestSquishTools::logOutputReceived,
+ TestResultsPane::instance(), &TestResultsPane::addLogoutput, Qt::QueuedConnection);
+}
+
+TestSquishTools::~TestSquishTools()
+{
+ // TODO add confirmation dialog somewhere
+ if (m_runnerProcess) {
+ m_runnerProcess->terminate();
+ if (!m_runnerProcess->waitForFinished(5000))
+ m_runnerProcess->kill();
+ delete m_runnerProcess;
+ m_runnerProcess = 0;
+ }
+
+ if (m_serverProcess) {
+ m_serverProcess->terminate();
+ if (!m_serverProcess->waitForFinished(5000))
+ m_serverProcess->kill();
+ delete m_serverProcess;
+ m_serverProcess = 0;
+ }
+}
+
+struct SquishToolsSettings
+{
+ SquishToolsSettings()
+ : serverPath(QLatin1String("squishserver"))
+ , runnerPath(QLatin1String("squishrunner"))
+ , isLocalServer(true)
+ , verboseLog(false)
+ , serverHost(QLatin1String("localhost"))
+ , serverPort(9999)
+ {}
+
+ QString squishPath;
+ QString serverPath;
+ QString runnerPath;
+ bool isLocalServer;
+ bool verboseLog;
+ QString serverHost;
+ int serverPort;
+ QString licenseKeyPath;
+};
+
+void TestSquishTools::runTestCases(const QString &suitePath, const QStringList &testCases,
+ const QStringList &additionalServerArgs,
+ const QStringList &additionalRunnerArgs)
+{
+ if (m_state != Idle) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Error"),
+ tr("Squish Tools in unexpected state (%1).\n"
+ "Refusing to run a test case.").arg(m_state));
+ return;
+ }
+ // create test results directory (if necessary) and return on fail
+ if (!QDir().mkpath(resultsDirectory)) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Error"),
+ tr("Could not create test results folder. Canceling test run."));
+ return;
+ }
+
+ m_suitePath = suitePath;
+ m_testCases = testCases;
+ m_reportFiles.clear();
+ m_additionalServerArguments = additionalServerArgs;
+
+ const QString dateTimeString
+ = QDateTime::currentDateTime().toString(QLatin1String("yyyy-MM-ddTHH-mm-ss"));
+ m_currentResultsDirectory = QFileInfo(QDir(resultsDirectory),
+ dateTimeString).absoluteFilePath();
+
+ m_additionalRunnerArguments = additionalRunnerArgs;
+ m_additionalRunnerArguments << QLatin1String("--interactive")
+ << QLatin1String("--resultdir")
+ << QDir::toNativeSeparators(m_currentResultsDirectory);
+
+ m_testRunning = true;
+ emit squishTestRunStarted();
+ startSquishServer(RunTestRequested);
+}
+
+void TestSquishTools::setState(TestSquishTools::State state)
+{
+ // TODO check whether state transition is legal
+ m_state = state;
+
+ switch (m_state) {
+ case Idle:
+ m_request = None;
+ m_suitePath = QString();
+ m_testCases.clear();
+ m_reportFiles.clear();
+ m_additionalRunnerArguments.clear();
+ m_additionalServerArguments.clear();
+ m_testRunning = false;
+ m_currentResultsDirectory.clear();
+ m_lastTopLevelWindows.clear();
+ break;
+ case ServerStarted:
+ if (m_request == RunTestRequested) {
+ startSquishRunner();
+ } else if (m_request == RecordTestRequested) {
+
+ } else if (m_request == RunnerQueryRequested) {
+
+ } else {
+ QTC_ASSERT(false, qDebug() << m_state << m_request);
+ }
+ break;
+ case ServerStartFailed:
+ m_state = Idle;
+ m_request = None;
+ if (m_testRunning) {
+ emit squishTestRunFinished();
+ m_testRunning = false;
+ }
+ restoreQtCreatorWindows();
+ break;
+ case ServerStopped:
+ m_state = Idle;
+ if (m_request == ServerStopRequested) {
+ m_request = None;
+ if (m_testRunning) {
+ emit squishTestRunFinished();
+ m_testRunning = false;
+ }
+ restoreQtCreatorWindows();
+ } else if (m_request == KillOldBeforeRunRunner) {
+ startSquishServer(RunTestRequested);
+ } else if (m_request == KillOldBeforeRecordRunner) {
+ startSquishServer(RecordTestRequested);
+ } else if (m_request == KillOldBeforeQueryRunner) {
+ startSquishServer(RunnerQueryRequested);
+ } else {
+ QTC_ASSERT(false, qDebug() << m_state << m_request);
+ }
+ break;
+ case ServerStopFailed:
+ if (m_serverProcess && m_serverProcess->state() != QProcess::NotRunning) {
+ m_serverProcess->terminate();
+ if (!m_serverProcess->waitForFinished(5000)) {
+ m_serverProcess->kill();
+ delete m_serverProcess;
+ m_serverProcess = 0;
+ }
+ }
+ m_state = Idle;
+ break;
+ case RunnerStartFailed:
+ case RunnerStopped:
+ if (m_testCases.isEmpty()) {
+ m_request = ServerStopRequested;
+ stopSquishServer();
+ // TODO merge result files
+ logrotateTestResults();
+ } else {
+ startSquishRunner();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+// will be populated by calling setupSquishTools() with current settings
+static SquishToolsSettings toolsSettings;
+
+void setupSquishTools()
+{
+ QSharedPointer<SquishSettings> squishSettings = AutotestPlugin::instance()->squishSettings();
+ toolsSettings.squishPath = squishSettings->squishPath.toString();
+
+ toolsSettings.serverPath
+ = Utils::HostOsInfo::withExecutableSuffix(QLatin1String("squishserver"));
+ toolsSettings.runnerPath
+ = Utils::HostOsInfo::withExecutableSuffix(QLatin1String("squishrunner"));
+
+ if (!toolsSettings.squishPath.isEmpty()) {
+ const QDir squishBin(toolsSettings.squishPath + QDir::separator() + QLatin1String("bin"));
+ toolsSettings.serverPath = QFileInfo(squishBin,
+ toolsSettings.serverPath).absoluteFilePath();
+ toolsSettings.runnerPath = QFileInfo(squishBin,
+ toolsSettings.runnerPath).absoluteFilePath();
+ }
+
+ toolsSettings.isLocalServer = squishSettings->local;
+ toolsSettings.serverHost = squishSettings->serverHost;
+ toolsSettings.serverPort = squishSettings->serverPort;
+ toolsSettings.verboseLog = squishSettings->verbose;
+ toolsSettings.licenseKeyPath = squishSettings->licensePath.toString();
+}
+
+void TestSquishTools::startSquishServer(Request request)
+{
+ m_request = request;
+ if (m_serverProcess) {
+ if (QMessageBox::question(Core::ICore::dialogParent(), tr("Squish Server Already Running"),
+ tr("There is still an old Squish server instance running.\n"
+ "This will cause problems later on.\n\n"
+ "If you continue the old instance will be terminated.\n"
+ "Do you want to continue?")) == QMessageBox::Yes) {
+ switch (m_request) {
+ case RunTestRequested:
+ m_request = KillOldBeforeRunRunner;
+ break;
+ case RecordTestRequested:
+ m_request = KillOldBeforeRecordRunner;
+ break;
+ case RunnerQueryRequested:
+ m_request = KillOldBeforeQueryRunner;
+ break;
+ default:
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Error"),
+ tr("Unexpected state or request while starting Squish "
+ "server. (state: %1, request: %2)")
+ .arg(m_state).arg(m_request));
+ }
+ stopSquishServer();
+ }
+ return;
+ }
+
+ setupSquishTools();
+ m_serverPort = -1;
+
+ const Utils::FileName squishServer
+ = Utils::Environment::systemEnvironment().searchInPath(toolsSettings.serverPath);
+ if (squishServer.isEmpty()) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Squish Server Error"),
+ tr("\"%1\" could not be found or is not executable.\n"
+ "Check the settings.")
+ .arg(QDir::toNativeSeparators(toolsSettings.serverPath)));
+ setState(Idle);
+ return;
+ }
+ toolsSettings.serverPath = squishServer.toString();
+
+ if (true) // TODO squish setting of minimize QC on squish run/record
+ minimizeQtCreatorWindows();
+ else
+ m_lastTopLevelWindows.clear();
+
+ m_serverProcess = new QProcess;
+ m_serverProcess->setProgram(toolsSettings.serverPath);
+ QStringList arguments;
+ // TODO if isLocalServer is false we should start a squishserver on remote device
+ if (toolsSettings.isLocalServer)
+ arguments << QLatin1String("--local"); // for now - although Squish Docs say "don't use it"
+ else
+ arguments << QLatin1String("--port") << QString::number(toolsSettings.serverPort);
+
+ if (toolsSettings.verboseLog)
+ arguments << QLatin1String("--verbose");
+
+ m_serverProcess->setArguments(arguments);
+ m_serverProcess->setProcessEnvironment(squishEnvironment());
+
+ connect(m_serverProcess, &QProcess::readyReadStandardOutput,
+ this, &TestSquishTools::onServerOutput);
+ connect(m_serverProcess, &QProcess::readyReadStandardError,
+ this, &TestSquishTools::onServerErrorOutput);
+ connect(m_serverProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
+ this, SLOT(onServerFinished(int,QProcess::ExitStatus)));
+
+ setState(ServerStarting);
+ m_serverProcess->start();
+ if (!m_serverProcess->waitForStarted()) {
+ setState(ServerStartFailed);
+ qWarning() << "squishserver did not start within 30s";
+ }
+}
+
+void TestSquishTools::stopSquishServer()
+{
+ if (m_serverProcess && m_serverPort > 0) {
+ QProcess serverKiller;
+ serverKiller.setProgram(m_serverProcess->program());
+ QStringList args;
+ args << QLatin1String("--stop") << QLatin1String("--port") << QString::number(m_serverPort);
+ serverKiller.setArguments(args);
+ serverKiller.setProcessEnvironment(m_serverProcess->processEnvironment());
+ serverKiller.start();
+ if (serverKiller.waitForStarted()) {
+ if (!serverKiller.waitForFinished()) {
+ qWarning() << "Could not shutdown server within 30s";
+ setState(ServerStopFailed);
+ }
+ } else {
+ qWarning() << "Could not shutdown server within 30s";
+ setState(ServerStopFailed);
+ }
+ } else {
+ qWarning() << "either no process running or port < 1?" << m_serverProcess << m_serverPort;
+ setState(ServerStopFailed);
+ }
+}
+
+void TestSquishTools::startSquishRunner()
+{
+ if (!m_serverProcess) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("No Squish Server"),
+ tr("Squish server does not seem to be running.\n"
+ "(state: %1, request: %2)\n"
+ "Try again.").arg(m_state).arg(m_request));
+ setState(Idle);
+ return;
+ }
+ if (m_serverPort == -1) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("No Squish Server Port"),
+ tr("Failed to get the server port.\n"
+ "(state: %1, request: %2)\n"
+ "Try again.").arg(m_state).arg(m_request));
+ // setting state to ServerStartFailed will terminate/kill the current unusable server
+ setState(ServerStartFailed);
+ return;
+ }
+
+ if (m_runnerProcess) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Squish Runner Running"),
+ tr("Squish runner seems to be running already.\n"
+ "(state: %1, request: %2)\n"
+ "Wait until it has finished and try again.")
+ .arg(m_state).arg(m_request));
+ return;
+ }
+
+ const Utils::FileName squishRunner
+ = Utils::Environment::systemEnvironment().searchInPath(toolsSettings.runnerPath);
+ if (squishRunner.isEmpty()) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Squish Runner Error"),
+ tr("\"%1\" could not be found or is not executable.\n"
+ "Check the settings.")
+ .arg(QDir::toNativeSeparators(toolsSettings.runnerPath)));
+ setState(RunnerStopped);
+ return;
+ }
+ toolsSettings.runnerPath = squishRunner.toString();
+
+ m_runnerProcess = new QProcess;
+
+ QStringList args;
+ args << m_additionalServerArguments;
+ if (!toolsSettings.isLocalServer)
+ args << QLatin1String("--host") << toolsSettings.serverHost;
+ args << QLatin1String("--port") << QString::number(m_serverPort);
+ args << QLatin1String("--debugLog") << QLatin1String("alpw"); // TODO make this configurable?
+
+ const QFileInfo testCasePath(QDir(m_suitePath), m_testCases.takeFirst());
+ args << QLatin1String("--testcase") << testCasePath.absoluteFilePath();
+ args << QLatin1String("--suitedir") << m_suitePath;
+
+ args << m_additionalRunnerArguments;
+
+ const QString caseReportFilePath = QFileInfo(QString::fromLatin1("%1/%2/%3/results.xml")
+ .arg(m_currentResultsDirectory,
+ QDir(m_suitePath).dirName(),
+ testCasePath.baseName())).absoluteFilePath();
+ m_reportFiles.append(caseReportFilePath);
+
+ args << QLatin1String("--reportgen")
+ << QString::fromLatin1("xml2.2,%1").arg(caseReportFilePath);
+
+ m_runnerProcess->setProgram(toolsSettings.runnerPath);
+ m_runnerProcess->setArguments(args);
+ m_runnerProcess->setProcessEnvironment(squishEnvironment());
+
+ connect(m_runnerProcess, &QProcess::readyReadStandardError,
+ this, &TestSquishTools::onRunnerErrorOutput);
+ connect(m_runnerProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
+ this, SLOT(onRunnerFinished(int,QProcess::ExitStatus)));
+
+ setState(RunnerStarting);
+
+ // set up the file system watcher for being able to read the results.xml file
+ m_resultsFileWatcher = new QFileSystemWatcher;
+ // on second run this directory exists and won't emit changes, so use the current subdirectory
+ if (QDir(m_currentResultsDirectory).exists())
+ m_resultsFileWatcher->addPath(m_currentResultsDirectory + QDir::separator() + QDir(m_suitePath).dirName());
+ else
+ m_resultsFileWatcher->addPath(QFileInfo(m_currentResultsDirectory).absolutePath());
+
+ connect(m_resultsFileWatcher, &QFileSystemWatcher::directoryChanged,
+ this, &TestSquishTools::onResultsDirChanged);
+
+ m_runnerProcess->start();
+ if (!m_runnerProcess->waitForStarted()) {
+ QMessageBox::critical(Core::ICore::dialogParent(), tr("Squish Runner Error"),
+ tr("Squish runner failed to start within given timeframe."));
+ delete m_resultsFileWatcher;
+ m_resultsFileWatcher = 0;
+ setState(RunnerStartFailed);
+ return;
+ }
+ setState(RunnerStarted);
+ m_currentResultsXML = new QFile(caseReportFilePath);
+}
+
+QProcessEnvironment TestSquishTools::squishEnvironment() const
+{
+ Utils::Environment environment = Utils::Environment::systemEnvironment();
+ if (!toolsSettings.licenseKeyPath.isEmpty())
+ environment.prependOrSet(QLatin1String("SQUISH_LICENSEKEY_DIR"),
+ toolsSettings.licenseKeyPath);
+ environment.prependOrSet(QLatin1String("SQUISH_PREFIX"), toolsSettings.squishPath);
+ return environment.toProcessEnvironment();
+}
+
+void TestSquishTools::onServerFinished(int, QProcess::ExitStatus)
+{
+ delete m_serverProcess;
+ m_serverProcess = 0;
+ m_serverPort = -1;
+ setState(ServerStopped);
+}
+
+void TestSquishTools::onRunnerFinished(int, QProcess::ExitStatus)
+{
+ delete m_runnerProcess;
+ m_runnerProcess = 0;
+
+ if (m_resultsFileWatcher) {
+ delete m_resultsFileWatcher;
+ m_resultsFileWatcher = 0;
+ }
+ if (m_currentResultsXML) {
+ if (m_currentResultsXML->isOpen())
+ m_currentResultsXML->close();
+ delete m_currentResultsXML;
+ m_currentResultsXML = 0;
+ }
+ setState(RunnerStopped);
+}
+
+void TestSquishTools::onServerOutput()
+{
+ // output used for getting the port information of the current squishserver
+ const QByteArray output = m_serverProcess->readAllStandardOutput();
+ foreach (const QByteArray &line, output.split('\n')) {
+ const QByteArray trimmed = line.trimmed();
+ if (trimmed.isEmpty())
+ continue;
+ if (trimmed.startsWith("Port:")) {
+ if (m_serverPort == -1) {
+ bool ok;
+ int port = trimmed.mid(6).toInt(&ok);
+ if (ok) {
+ m_serverPort = port;
+ setState(ServerStarted);
+ } else {
+ qWarning() << "could not get port number" << trimmed.mid(6);
+ setState(ServerStartFailed);
+ }
+ } else {
+ qWarning() << "got a Port output - don't know why...";
+ }
+ }
+ emit logOutputReceived(QLatin1String("Server: ") + QLatin1String(trimmed));
+ }
+}
+
+void TestSquishTools::onServerErrorOutput()
+{
+ // output that must be send to the Runner/Server Log
+ const QByteArray output = m_serverProcess->readAllStandardError();
+ foreach (const QByteArray &line, output.split('\n')) {
+ const QByteArray trimmed = line.trimmed();
+ if (!trimmed.isEmpty())
+ emit logOutputReceived(QLatin1String("Server: ") + QLatin1String(trimmed));
+ }
+}
+
+static char firstNonWhitespace(const QByteArray &text)
+{
+ for (int i = 0, limit = text.size(); i < limit; ++i)
+ if (isspace(text.at(i)))
+ continue;
+ else
+ return text.at(i);
+ return 0;
+}
+
+static int positionAfterLastClosingTag(const QByteArray &text)
+{
+ QList<QByteArray> possibleEndTags;
+ possibleEndTags << "</description>" << "</message>" << "</verification>" << "</result>"
+ << "</test>" << "</prolog>" << "</epilog>" << "</SquishReport>";
+
+ int positionStart = text.lastIndexOf("</");
+ if (positionStart == -1)
+ return -1;
+
+ int positionEnd = text.indexOf('>', positionStart);
+ if (positionEnd == -1)
+ return -1;
+
+ QByteArray endTag = text.mid(positionStart, positionEnd + 1 - positionStart);
+ if (possibleEndTags.contains(endTag))
+ return positionEnd + 1;
+ else
+ return positionAfterLastClosingTag(text.mid(0, positionStart));
+}
+
+void TestSquishTools::onRunnerOutput(const QString)
+{
+ // buffer for already read, but not processed content
+ static QByteArray buffer;
+ const qint64 currentSize = m_currentResultsXML->size();
+
+ if (currentSize <= m_readResultsCount)
+ return;
+
+ QByteArray output = m_currentResultsXML->read(currentSize - m_readResultsCount);
+ if (output.isEmpty())
+ return;
+
+ if (!buffer.isEmpty())
+ output.prepend(buffer);
+ // we might read only partial written stuff - so we have to figure out how much we can
+ // pass on for further processing and buffer the rest for the next reading
+ const int endTag = positionAfterLastClosingTag(output);
+ if (endTag < output.size()) {
+ buffer = output.mid(endTag);
+ output.truncate(endTag);
+ } else {
+ buffer.clear();
+ }
+
+ m_readResultsCount += output.size();
+
+ if (firstNonWhitespace(output) == '<') {
+ // output that must be used for the TestResultsPane
+ qDebug() << "RunnerOutput:" << output;
+ } else {
+ foreach (const QByteArray &line, output.split('\n')) {
+ const QByteArray trimmed = line.trimmed();
+ if (!trimmed.isEmpty())
+ emit logOutputReceived(QLatin1String("Runner: ") + QLatin1String(trimmed));
+ }
+ }
+}
+
+void TestSquishTools::onRunnerErrorOutput()
+{
+ // output that must be send to the Runner/Server Log
+ const QByteArray output = m_runnerProcess->readAllStandardError();
+ foreach (const QByteArray &line, output.split('\n')) {
+ const QByteArray trimmed = line.trimmed();
+ if (!trimmed.isEmpty())
+ emit logOutputReceived(QLatin1String("Runner: ") + QLatin1String(trimmed));
+ }
+}
+
+void TestSquishTools::onResultsDirChanged(const QString &filePath)
+{
+ if (m_currentResultsXML->exists()) {
+ delete m_resultsFileWatcher;
+ m_resultsFileWatcher = 0;
+ m_readResultsCount = 0;
+ if (m_currentResultsXML->open(QFile::ReadOnly)) {
+ m_resultsFileWatcher = new QFileSystemWatcher;
+ m_resultsFileWatcher->addPath(m_currentResultsXML->fileName());
+ connect(m_resultsFileWatcher, &QFileSystemWatcher::fileChanged,
+ this, &TestSquishTools::onRunnerOutput);
+ } else {
+ // TODO set a flag to process results.xml as soon the complete test run has finished
+ qWarning() << "could not open results.xml although it exists"
+ << filePath << m_currentResultsXML->error()
+ << m_currentResultsXML->errorString();
+ }
+ } else {
+ disconnect(m_resultsFileWatcher);
+ // results.xml is created as soon some output has been opened - so try again in a second
+ QTimer::singleShot(1000, this, [this, filePath] () {
+ onResultsDirChanged(filePath);
+ });
+ }
+}
+
+void TestSquishTools::logrotateTestResults()
+{
+ // make this configurable?
+ const int maxNumberOfTestResults = 10;
+ const QStringList existing = QDir(resultsDirectory).entryList(QDir::Dirs | QDir::NoDotAndDotDot,
+ QDir::Name);
+
+ for (int i = 0, limit = existing.size() - maxNumberOfTestResults; i < limit; ++i) {
+ QDir current(resultsDirectory + QDir::separator() + existing.at(i));
+ if (!current.removeRecursively())
+ qWarning() << "could not remove" << current.absolutePath();
+ }
+}
+
+void TestSquishTools::minimizeQtCreatorWindows()
+{
+ m_lastTopLevelWindows = QApplication::topLevelWindows();
+ QWindowList toBeRemoved;
+ foreach (QWindow *window, m_lastTopLevelWindows) {
+ if (window->isVisible())
+ window->showMinimized();
+ else
+ toBeRemoved.append(window);
+ }
+
+ foreach (QWindow *window, toBeRemoved)
+ m_lastTopLevelWindows.removeOne(window);
+}
+
+void TestSquishTools::restoreQtCreatorWindows()
+{
+ foreach (QWindow *window, m_lastTopLevelWindows) {
+ window->requestActivate();
+ window->showNormal();
+ }
+}
+
+} // namespace Internal
+} // namespace Autotest
diff --git a/plugins/autotest/testsquishtools.h b/plugins/autotest/testsquishtools.h
new file mode 100644
index 0000000000..65e23312ac
--- /dev/null
+++ b/plugins/autotest/testsquishtools.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at
+** http://www.qt.io/contact-us
+**
+** This file is part of the Qt Creator Enterprise Auto Test Add-on.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://www.qt.io/contact-us
+**
+****************************************************************************/
+
+#ifndef TESTSQUISHTOOLS_H
+#define TESTSQUISHTOOLS_H
+
+#include <QObject>
+#include <QProcess>
+#include <QStringList>
+#include <QWindowList>
+
+QT_BEGIN_NAMESPACE
+class QFile;
+class QFileSystemWatcher;
+QT_END_NAMESPACE
+
+namespace Autotest {
+namespace Internal {
+
+class TestSquishTools : public QObject
+{
+ Q_OBJECT
+public:
+ explicit TestSquishTools(QObject *parent = 0);
+ ~TestSquishTools();
+
+ enum State
+ {
+ Idle,
+ ServerStarting,
+ ServerStarted,
+ ServerStartFailed,
+ ServerStopped,
+ ServerStopFailed,
+ RunnerStarting,
+ RunnerStarted,
+ RunnerStartFailed,
+ RunnerStopped
+ };
+
+ State state() const { return m_state; }
+
+ void runTestCases(const QString &suitePath, const QStringList &testCases = QStringList(),
+ const QStringList &additionalServerArgs = QStringList(),
+ const QStringList &additionalRunnerArgs = QStringList());
+
+signals:
+ void logOutputReceived(const QString &output);
+ void squishTestRunStarted();
+ void squishTestRunFinished();
+
+private:
+ enum Request
+ {
+ None,
+ ServerStopRequested,
+ ServerQueryRequested,
+ RunnerQueryRequested,
+ RunTestRequested,
+ RecordTestRequested,
+ KillOldBeforeRunRunner,
+ KillOldBeforeRecordRunner,
+ KillOldBeforeQueryRunner
+ };
+
+ void setState(State state);
+
+ void startSquishServer(Request request);
+ void stopSquishServer();
+ void startSquishRunner();
+ QProcessEnvironment squishEnvironment() const;
+ Q_SLOT void onServerFinished(int exitCode, QProcess::ExitStatus status = QProcess::NormalExit);
+ Q_SLOT void onRunnerFinished(int exitCode, QProcess::ExitStatus status = QProcess::NormalExit);
+ void onServerOutput();
+ void onServerErrorOutput();
+ void onRunnerOutput(const QString);
+ void onRunnerErrorOutput();
+ void onResultsDirChanged(const QString &filePath);
+ void logrotateTestResults();
+ void minimizeQtCreatorWindows();
+ void restoreQtCreatorWindows();
+
+ QProcess *m_serverProcess;
+ QProcess *m_runnerProcess;
+ int m_serverPort;
+ QString m_serverHost;
+ Request m_request;
+ State m_state;
+ QString m_suitePath;
+ QStringList m_testCases;
+ QStringList m_reportFiles;
+ QString m_currentResultsDirectory;
+ QFile *m_currentResultsXML;
+ QFileSystemWatcher *m_resultsFileWatcher;
+ QStringList m_additionalServerArguments;
+ QStringList m_additionalRunnerArguments;
+ QWindowList m_lastTopLevelWindows;
+ bool m_testRunning;
+ qint64 m_readResultsCount;
+};
+
+} // namespace Internal
+} // namespace Autotest
+
+#endif // TESTSQUISHTOOLS_H
diff --git a/plugins/autotest/testtreemodel.cpp b/plugins/autotest/testtreemodel.cpp
index 1e40493c02..b0451a40d7 100644
--- a/plugins/autotest/testtreemodel.cpp
+++ b/plugins/autotest/testtreemodel.cpp
@@ -715,6 +715,27 @@ bool TestTreeModel::hasUnnamedQuickTests() const
return false;
}
+QStringList TestTreeModel::getSelectedSquishTestCases(const QString &suiteConfPath)
+{
+ QStringList result;
+ const int count = m_squishTestRootItem->childCount();
+ if (count) {
+ for (int row = 0; row < count; ++row) {
+ TestTreeItem *child = m_squishTestRootItem->child(row);
+ if (child->filePath() == suiteConfPath) {
+ const int testCaseCount = child->childCount();
+ for (int testCaseRow = 0; testCaseRow < testCaseCount; ++testCaseRow) {
+ TestTreeItem *grandChild = child->child(testCaseRow);
+ if (grandChild->checked())
+ result.append(grandChild->name());
+ }
+ break;
+ }
+ }
+ }
+ return result;
+}
+
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
diff --git a/plugins/autotest/testtreemodel.h b/plugins/autotest/testtreemodel.h
index 8fa854f443..a4a7a5a1e3 100644
--- a/plugins/autotest/testtreemodel.h
+++ b/plugins/autotest/testtreemodel.h
@@ -80,6 +80,8 @@ public:
QSet<QString> qmlFilesForProFile(const QString &proFile) const;
bool hasUnnamedQuickTests() const;
+ QStringList getSelectedSquishTestCases(const QString &suiteConfPath);
+
#ifdef WITH_TESTS
int autoTestsCount() const;
int namedQuickTestsCount() const;