summaryrefslogtreecommitdiffstats
path: root/old/libqsystemtest/qsystemtest_p.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'old/libqsystemtest/qsystemtest_p.cpp')
-rw-r--r--old/libqsystemtest/qsystemtest_p.cpp909
1 files changed, 909 insertions, 0 deletions
diff --git a/old/libqsystemtest/qsystemtest_p.cpp b/old/libqsystemtest/qsystemtest_p.cpp
new file mode 100644
index 0000000..dd0600c
--- /dev/null
+++ b/old/libqsystemtest/qsystemtest_p.cpp
@@ -0,0 +1,909 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of QtUiTest.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qsystemtest.h>
+#include "qsystemtestmaster_p.h"
+#include "qtestverifydlg_p.h"
+#include "qtestide.h"
+#include "recordevent_p.h"
+#include "qtuitest_config.h"
+#include "ui_manualverificationdlg.h"
+#include "ui_failuredlg.h"
+#include "ui_recorddlg.h"
+
+#include <QProcess>
+#include <QTextEdit>
+#include <QMessageBox>
+#include <QMetaEnum>
+
+
+#undef qLog
+#define qLog(A) if (!m_verbose); else (QDebug(QtDebugMsg) << #A ":")
+
+#define OBJECT_EXIST_TIMEOUT 1000
+
+#define BT(message) (\
+ message["location"] = QString("%1:%2%3").arg(__FILE__).arg(__LINE__).arg(!message["location"].toString().isEmpty() ? QString("\n") + message["location"].toString() : QString()),\
+ message)
+
+/*!
+ \internal
+ Determines whether to learn the new pixmap by displaying a prompt to the user.
+ \a actual is the current snapshot, while \a expected is the stored pixmap (if any).
+ If the user does not accept the new snapshot, a failure is generated for \a file at \a line, and
+ \c false is returned. Otherwise the new snapshot replaces the previous stored one, and \c true is returned.
+ \a comment is a comment intended to help the user decide whether or not to accept the pixmap.
+
+ If this is a GUI application, the prompt is displayed directly. Otherwise it sends a message to
+ any connected IDE's to display the message.
+*/
+bool QSystemTest::learnImage(const QImage &actual, const QImage &expected, const QString &comment)
+{
+ if (QTestIDE::instance()->isConnected()) {
+ return QTestIDE::instance()->verifyImage(actual.isNull() ? QPixmap() : QPixmap::fromImage(actual),
+ expected.isNull() ? QPixmap() : QPixmap::fromImage(expected),
+ comment);
+ } else {
+ QTestVerifyDlg dlg;
+ dlg.setWindowFlags(dlg.windowFlags() | Qt::WindowStaysOnTopHint);
+ QString fullComment("Does the 'Actual' image show the correct output?\n" + comment);
+
+ dlg.addTab("Actual", actual.isNull() ? QPixmap() : QPixmap::fromImage(actual));
+ if (!expected.isNull()) {
+ dlg.addTab("Expected", QPixmap::fromImage(expected));
+ dlg.setComment(fullComment);
+ } else {
+ dlg.setComment(fullComment + "\n"
+ "If you click on 'Yes' the 'Actual' image will become the\n"
+ "expected image, and will be used for future comparisons.\n");
+ }
+
+ if ( dlg.exec() == QDialog::Accepted )
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ \internal
+ Compares the \a actual and \a expected images and returns true if the images are
+ the same. If \a strict is false, small differences between the images are allowed.
+*/
+bool QSystemTest::imagesAreEqual( const QImage &actual, const QImage &expected, bool strict )
+{
+ // images are considered the same if both contain a null image
+ if (actual.isNull() && expected.isNull())
+ return true;
+
+ // images are not the same if one images contains a null image
+ if (actual.isNull() || expected.isNull())
+ return false;
+
+ // images do not have the same size
+ if (actual.size() != expected.size())
+ return false;
+
+ QImage a = actual.convertToFormat(QImage::Format_ARGB32);
+ QImage e = expected.convertToFormat(QImage::Format_ARGB32);
+
+ bool equal = true;
+ int threshold = 80;
+ for ( int y=0; y<a.height(); y++ ) {
+ for ( int x=0; x<a.width(); x++ ) {
+ QRgb actPix = a.pixel( x, y );
+ QRgb expPix = e.pixel( x, y );
+
+ if ( qAlpha(actPix)==0 && qAlpha(expPix)==0 )
+ continue;
+ if ( actPix != expPix) {
+ if (strict ||
+ (qAbs(qRed(actPix) - qRed(expPix)) +
+ qAbs(qGreen(actPix) - qGreen(expPix)) +
+ qAbs(qBlue(actPix) - qBlue(expPix)) > threshold)
+ ) {
+ equal = false;
+ goto done;
+ }
+ }
+ }
+ }
+
+done:
+ return equal;
+}
+
+/*!
+ Appends \a description to the list of manual test execution steps.
+ The complete list will be shown:
+ \list
+ \o When in \a -force-manual mode: in a prompt dialog at the end of the test function.
+ \o When NOT in \a -force-manual mode: in a prompt dialog immediately before any test automation
+function is called, i.e. currentTitle(), verify(), getText(), etc.
+ \endlist
+
+ \sa prompt
+*/
+void QSystemTest::manualTest( const QString &description )
+{
+ if (m_auto_mode) {
+ skip("Manual test skipped", SkipSingle);
+ return;
+ }
+
+ QString descr = description;
+ if (descr.startsWith("MAGIC_COMMAND:")) {
+ descr.remove("MAGIC_COMMAND:");
+ } else {
+ bool magic_data_replaced = false;
+ int pos = descr.indexOf("MAGIC_DATA");
+ while (pos >= 0
+ && m_manual_command_data.count() > 0) {
+ descr.remove(pos,10);
+ descr.insert(pos,m_manual_command_data.takeFirst());
+ magic_data_replaced = true;
+ pos = descr.indexOf("MAGIC_DATA");
+ }
+
+ if (m_manual_command_data.count() > 1) {
+ if (!m_manual_command_data[0].contains("MAGIC_DATA")
+ && m_manual_command_data[1].contains("MAGIC_DATA")) {
+ // swap data 1 and data 0 ...
+ QString tmp = m_manual_command_data.takeFirst();
+ descr.append(m_manual_command_data.takeFirst());
+ magic_data_replaced = true;
+ descr.replace("MAGIC_DATA", tmp);
+ }
+ }
+
+ while (m_manual_command_data.count() > 0) {
+ descr += " " + m_manual_command_data.takeFirst();
+ }
+
+ if (descr.contains("MAGIC_DATA") && alternative_command_data.count() > 0) {
+ descr.replace("MAGIC_DATA", alternative_command_data.takeFirst());
+ }
+ }
+ m_manual_commands += QString("%1 %2\n").arg(++m_query_count).arg(descr);
+
+ m_manual_command_data.clear();
+ alternative_command_data.clear();
+}
+
+/*!
+ \internal
+ Adds 'magic' data to a fifo. This data is used by \c manualTest to replace 'MAGIC_DATA' placeholders
+ in their text.
+*/
+void QSystemTest::manualTestData( const QString &description, bool isAlternative )
+{
+ if (isAlternative) {
+ alternative_command_data.append(description);
+ } else {
+ QString descr = description;
+ int pos = descr.lastIndexOf("MAGIC_DATA");
+ while (pos >= 0
+ && m_manual_command_data.count() > 0) {
+ descr.remove(pos,10);
+ descr.insert(pos,m_manual_command_data.takeLast());
+ pos = descr.lastIndexOf("MAGIC_DATA");
+ }
+ m_manual_command_data.append(descr);
+ }
+}
+
+/*!
+ \internal
+ Sends a query test message \a msg to the Application Under Test (AUT) identified by \a queryPath and returns the reply message. An error is logged in the testresult if the AUT doesn't respond within \a timeout ms.
+
+ Note that it is seldom necessery to call query directly.
+*/
+QTestMessage QSystemTest::query( const QTestMessage &msg, const QString &queryPath, int timeout )
+{
+// manualTest( msg.event() + QString("--") + msg.msgId() + "--" + msg.toString() );
+ if (m_run_as_manual_test) {
+ manualTest( QString("%1 -- %2 -- $3").arg(msg.event()).arg(msg.msgId()).arg(msg.toString()) );
+ QTestMessage reply;
+ reply["status"] = "OK";
+ return reply;
+ } else {
+ if (!m_manual_commands.isEmpty())
+ showPromptDialog();
+ }
+
+ QTestMessage message(msg);
+ message["queryPath"] = queryPath;
+ query_failed = false;
+ m_error_msg = QTestMessage();
+ m_last_msg_sent = QTestMessage();
+
+ if (-1 != fatal_timeouts && timeouts >= fatal_timeouts) {
+ QTestMessage reply;
+ reply["status"] = "ERROR_FATAL_TIMEOUT";
+ setQueryError(reply);
+ return reply;
+ }
+
+ QTestMessage reply;
+ reply["status"] = "ERROR_UNSET_REPLY";
+
+ m_last_msg_sent = message;
+
+ if (m_test_app) {
+ if (!m_test_app->sendMessage( message, reply, timeout )) {
+ setQueryError(reply);
+ return reply;
+ }
+ } else {
+ setQueryError( "No Application Under Test (AUT) available" );
+ }
+
+ QString warning = reply["warning"].toString();
+ if (!warning.isEmpty()) {
+ qWarning("In response to '%s', received warning from test system: %s", qPrintable(message.event()), qPrintable(warning));
+ }
+ if (reply["status"] == "ERROR_REPLY_TIMEOUT" || reply["status"] == "ERROR_NO_CONNECTION") ++timeouts;
+
+ return reply;
+}
+
+/*!
+ \internal
+ Sends a query specified with \a message to the Application Under Test (AUT). If the query was successful and \a reply is not null the reply message is returned in \a reply.
+
+ The function returns true if the query result string equals \a passResult and false if it equals
+ \a failResult. The function also returns false in cases where a query failure has occurred.
+
+ The \a queryPath can be used to specify which specific application/object the query is intended for.
+
+ The \a timeout parameter can be used to limit the time the query will wait for a reply.
+
+ If the function is NOT called from within a test macro such as QVERIFY, QCOMPARE, etc, the function
+ will also write a warning message to the display describing the error.
+*/
+bool QSystemTest::queryPassed( const QStringList &passResult, const QStringList &failResult,
+ const QTestMessage &message, const QString &queryPath, QTestMessage *reply, int timeout )
+{
+ QTime t;
+ if (m_verbose) t.start();
+
+ QTestMessage msg(message);
+
+ if (m_expect_app_close) {
+ msg["expectClose"] = "yes";
+ }
+
+ QTestMessage ret = query( BT(msg), queryPath, timeout );
+ if (reply) *reply = ret;
+ QString query_msg = message.event();
+ if (query_msg == "getProperty")
+ query_msg += "(" + message["property"].toString() + ")";
+
+ if (!queryFailed()) {
+ if (passResult.contains( ret["status"].toString() )) {
+ if (m_verbose) {
+ qLog(QtUitest) << QString("query '%1' took %2 ms").arg(query_msg).arg(t.elapsed()).toLatin1();
+ }
+ return true;
+ }
+ if (failResult.contains( ret["status"].toString() )) {
+ if (m_verbose) {
+ qLog(QtUitest) << QString("query '%1' took %2 ms").arg(query_msg).arg(t.elapsed()).toLatin1();
+ }
+ return false;
+ }
+ }
+
+ if (m_verbose) {
+ qLog(QtUitest) << QString("query '%1' took %2 ms").arg(message.event()).arg(t.elapsed()).toLatin1();
+ }
+ return setQueryError( ret );
+}
+
+/*!
+ \internal
+ \overload
+ Equivalent to queryPassed() with passResult and failResult a single-element string list.
+*/
+bool QSystemTest::queryPassed( const QString &passResult, const QString &failResult,
+ const QTestMessage &message, const QString &queryPath, QTestMessage *reply, int timeout )
+{
+ QStringList pass;
+ QStringList fail;
+ if (!passResult.isEmpty()) pass << passResult;
+ if (!failResult.isEmpty()) fail << failResult;
+ return queryPassed(pass, fail, message, queryPath, reply, timeout);
+}
+
+
+/*!
+ \internal
+ Implementation for the two public getSetting functions.
+*/
+QVariant QSystemTest::getSetting( const QString &org, const QString &app, const QString &file, const QString &group, const QString &key )
+{
+ QTestMessage message("getSetting");
+ if (!org.isNull()) message["org"] = org;
+ if (!app.isNull()) message["app"] = app;
+ if (!file.isNull()) message["path"] = file;
+ message["group"] = group;
+ message["key"] = key;
+
+ QVariant out;
+ QTestMessage reply;
+ if (!queryPassed("OK", "", BT(message), "", &reply)) return out;
+ if (!reply.contains("getSetting")) {
+ reply["status"] = "reply was missing return value";
+ setQueryError(reply);
+ return QVariant();
+ }
+ return reply["getSetting"];
+}
+
+/*!
+ \internal
+ Implementation for the two public setSetting functions.
+*/
+void QSystemTest::setSetting( const QString &org, const QString &app, const QString &file, const QString &group, const QString &key, const QVariant &value )
+{
+ QTestMessage message("setSetting");
+ if (!org.isNull()) message["org"] = org;
+ if (!app.isNull()) message["app"] = app;
+ if (!file.isNull()) message["path"] = file;
+ message["group"] = group;
+ message["key"] = key;
+ message["value"] = value;
+
+ queryPassed("OK", "", BT(message));
+}
+
+/*!
+ \internal
+ Processes incoming messages from the system under test
+*/
+void QSystemTest::processMessage( const QTestMessage &msg )
+{
+ if (msg.event().startsWith("key")) {
+ resetEventTimer();
+ if (recorded_events_as_code)
+ parseKeyEventToCode( msg );
+ return;// true;
+ } else if (msg.event().startsWith("mouse")) {
+ resetEventTimer();
+ return;// true;
+ } else if (msg.event() == "information") {
+ if (recorded_events_as_code)
+ recordedEvent( msg );
+ return;// true;
+ } else if (msg.event() == "command") {
+ if (recorded_events_as_code)
+ recordedEvent( msg );
+ return;// true;
+ } else if (msg.event() == "recordedEvents") {
+ recordedEvent( msg );
+ return;// true;
+ } else if (msg.event() == "appGainedFocus") {
+ QString new_app = (msg)["appName"].toString();
+ if (new_app != current_application) {
+ current_application = new_app;
+ current_app_version = (msg)["appVersion"].toString();
+ emit appGainedFocus(current_application);
+ }
+ return;// true;
+ } else if (msg.event() == "appBecameIdle") {
+ emit appBecameIdle((msg)["appName"].toString());
+ }
+
+ return;// false;
+}
+
+struct StaticQtMetaObject : public QObject
+{
+ static const QMetaObject *get()
+ { return &static_cast<StaticQtMetaObject*> (0)->staticQtMetaObject; }
+};
+
+/*!
+ \internal
+*/
+void QSystemTest::parseKeyEventToCode( const QTestMessage &msg) {
+ bool pressed = (msg.event() == "keyPress");
+
+ Qt::Key key = (Qt::Key)(msg)["key"].toInt();
+ Qt::KeyboardModifiers mod = (Qt::KeyboardModifiers)(msg)["mod"].toInt();
+ bool autoRepeat = (msg)["rep"].toBool();
+ if (autoRepeat) return;
+
+ QMetaObject const *qt = StaticQtMetaObject::get();
+ QMetaEnum key_enum = qt->enumerator(qt->indexOfEnumerator("Key"));
+ char const *key_str = key_enum.valueToKey(key);
+
+ if (pressed) {
+ key_hold_started = QTime::currentTime();
+ return;
+ }
+
+ int timePressed = key_hold_started.msecsTo(QTime::currentTime());
+ bool is_script = true; //inherits("QScriptSystemTest",QString());
+
+ QString key_identifier = (is_script) ? "Qt." : "Qt::";
+ key_identifier += QString::fromLatin1(key_str);
+
+ QString code;
+ if (!is_script) code += "QCHECKED( ";
+ if (timePressed > 300) {
+ code += QString("keyClickhold( %1, %2 )").arg( key_identifier ).arg( timePressed );
+ } else {
+ code += QString("keyClick( %1 )").arg( key_identifier );
+ }
+ if (!is_script) code += " )";
+ code += ";";
+
+ QTestMessage out("CODE");
+ out["code"] = code;
+ recordedEvent( out );
+}
+
+/*!
+ \internal
+*/
+bool QSystemTest::recentEventRecorded()
+{
+ if (event_timer == 0) {
+ return false;
+ } else {
+ return event_timer->elapsed() < 4800;
+ }
+}
+
+/*!
+ \internal
+*/
+void QSystemTest::resetEventTimer()
+{
+ if (event_timer == 0) {
+ event_timer = new QTime();
+ }
+
+ event_timer->start();
+}
+
+/*!
+ \internal
+ Signals the test that event recording needs to be switched on.
+*/
+void QSystemTest::recordPrompt()
+{
+ record_prompt = true;
+}
+
+void compressCode(QString &code)
+{
+ QString old_c;
+ QString c = code;
+ while (old_c != c) {
+ old_c = c;
+
+ // toggling a checkbox also causes it to be selected.
+ static QRegExp setCheckedSelect(
+ "setChecked\\( (true|false), ([^\n]+) \\);\n"
+ "select\\( \\2 \\);\n",
+ Qt::CaseSensitive, QRegExp::RegExp2);
+ while (-1 != setCheckedSelect.indexIn(c))
+ c.replace(setCheckedSelect, "setChecked( \\1, \\2 );\n");
+ static QRegExp selectSetChecked(
+ "select\\( ([^\n]+) \\);\n"
+ "setChecked\\( (true|false), \\1 \\);\n",
+ Qt::CaseSensitive, QRegExp::RegExp2);
+ while (-1 != selectSetChecked.indexIn(c))
+ c.replace(selectSetChecked, "setChecked( \\2, \\1 );\n");
+
+ // We get an enter event for each key press typically, so
+ // compress enter("f"), enter("fo"), enter("foo") into enter("foo")
+ static QRegExp enter(
+ "enter\\( [^\n]+(, [^\n]+) \\);\n"
+ "enter\\( ([^\n]+)\\1 \\);\n",
+ Qt::CaseSensitive, QRegExp::RegExp2);
+ while (-1 != enter.indexIn(c))
+ c.replace(enter, "enter( \\2\\1 );\n");
+
+ // Do the above also when there is no querypath
+ static QRegExp enter2(
+ "enter\\( \"(?:[^\"\\\\]*(?:\\\\.)*)*\" \\);\n"
+ "enter\\( \"((?:[^\"\\\\]*(?:\\\\.)*)*)\" \\);\n",
+ Qt::CaseSensitive, QRegExp::RegExp2);
+ while (-1 != enter2.indexIn(c))
+ c.replace(enter2, "enter( \"\\1\" );\n");
+
+ // We get a select tab bar event each time we switch tabs, so
+ // compress them.
+ static QRegExp selectTabBar(
+ "select\\( [^\n]+, tabBar\\(\\) \\);\n"
+ "select\\( ([^\n]+), tabBar\\(\\) \\);\n",
+ Qt::CaseSensitive, QRegExp::RegExp2);
+ while (-1 != selectTabBar.indexIn(c))
+ c.replace(selectTabBar, "select( \\1, tabBar() );\n");
+
+ // Not sure how to handle empty selects. Strip them out.
+ static QRegExp selectNothing(
+ "select\\( (\"\")? \\);\n");
+ while (-1 != selectNothing.indexIn(c))
+ c.replace(selectNothing, QString());
+
+ // Strip out redundant selects (eg, when selecting from combobox)
+ static QRegExp selectTestAbstractItemView(
+ "select\\( \"[^\n]+\", \"TestAbstractItemView\\[\\w*\\]\" \\);\n");
+ while (-1 != selectTestAbstractItemView.indexIn(c))
+ c.replace(selectTestAbstractItemView, QString());
+
+ }
+ code = c;
+}
+
+QString escapeString(QString const& in)
+{
+ QString out;
+ foreach (QChar c, in) {
+ if (c == '"')
+ out += "\\\"";
+ else if (c == '\\')
+ out += "\\\\";
+ else if (c == '\n')
+ out += "\\n";
+ else
+ out += c;
+ }
+ return out;
+}
+
+QString widgetParameter(QString const& widget)
+{
+ QString ret;
+ ret = '"' + escapeString(widget) + '"';
+ return ret;
+}
+
+QString recordedEventsToCode( QList<RecordEvent> const &events )
+{
+ QStringList statements;
+ QString focusWidget;
+ foreach (RecordEvent e, events) {
+ QString cmd;
+ switch (e.type) {
+ case RecordEvent::GotFocus:
+ // There is currently no API for simply navigating to a widget.
+ // If one were added, we would do something like this:
+ // statements << QString("setFocus( \"%1\" );").arg(e.widget);
+ focusWidget = e.widget;
+ break;
+
+ case RecordEvent::Entered:
+ cmd = QString("enter( \"%1\"").arg(escapeString(e.data.toString()));
+ if (!e.widget.isEmpty() && e.widget != focusWidget)
+ cmd += QString(", %1").arg(widgetParameter(e.widget));
+ cmd += " );";
+ statements << cmd;
+ focusWidget = e.widget;
+ break;
+
+ case RecordEvent::Activated:
+ statements << QString("select( %1 );").arg(widgetParameter(e.widget));
+ focusWidget = QString();
+ break;
+
+ case RecordEvent::Selected:
+ cmd = QString("select( \"%1\"").arg(escapeString(e.data.toString()));
+ if (!e.widget.isEmpty() && e.widget != focusWidget)
+ cmd += QString(", %1").arg(widgetParameter(e.widget));
+ cmd += " );";
+ statements << cmd;
+ focusWidget = QString();
+ break;
+
+ case RecordEvent::CheckStateChanged:
+ cmd = QString("setChecked( %1").arg(e.data.toBool() ? "true" : "false");
+ if (!e.widget.isEmpty() && e.widget != focusWidget)
+ cmd += QString(", %1").arg(widgetParameter(e.widget));
+ cmd += " );";
+ statements << cmd;
+ focusWidget = e.widget;
+ break;
+
+ case RecordEvent::TitleChanged:
+ statements << QString("waitForTitle( \"%1\" );").arg(escapeString(e.data.toString()));
+ focusWidget = QString();
+ break;
+
+ case RecordEvent::MessageBoxShown: {
+ QVariantMap map = e.data.toMap();
+ QString title = map["title"].toString();
+ QString text = map["text"].toString();
+ statements << QString("// Message box shown, title: \"%1\", text: \"%2\"").arg(escapeString(title)).arg(escapeString(text));
+ focusWidget = QString();
+ break;
+ }
+
+ default:
+ statements << QString("// unknown event: %1 %2 %3").arg(e.type).arg(widgetParameter(e.widget)).arg(escapeString(e.data.toString()));
+ break;
+ }
+ }
+
+ return statements.join("\n");
+}
+
+/*!
+ \internal
+*/
+void QSystemTest::recordedEvent( const QTestMessage &msg ) {
+ if (msg.event() == "recordedEvents") {
+ recordEvents( (msg)["events"].value< QList<RecordEvent> >() );
+ }
+}
+
+void QSystemTest::recordEvents(QList<RecordEvent> const& events)
+{
+ recorded_events << events;
+ QString out = recordedEventsToCode( recorded_events );
+ recorded_events.clear();
+ if (!out.isEmpty()) {
+ m_recorded_code += out + "\n";
+
+ // For debugging purposes, allow code compression to be skipped.
+ static bool skip_compression = !qgetenv("QTUITEST_DEBUG_RECORD").isEmpty();
+ if (!skip_compression) compressCode(m_recorded_code);
+ if (m_recorded_events_edit && m_recorded_events_edit->isVisible()) {
+ m_recorded_events_edit->setPlainText(m_recorded_code);
+ m_recorded_events_edit->moveCursor(QTextCursor::End);
+ m_recorded_events_edit->ensureCursorVisible();
+ }
+ QTestIDE::instance()->recordedCode(m_recorded_code);
+ }
+}
+/*
+ Returns the PATH environment variable, i.e. the part 'after' PATH=.
+*/
+QString QSystemTest::PATH()
+{
+ QStringList env = QProcess::systemEnvironment();
+ foreach (QString line, env) {
+ if (line.startsWith("PATH=", Qt::CaseInsensitive))
+ return line.mid(line.indexOf("=")+1);
+ }
+ return "";
+}
+
+/*
+ Traverses through the environment PATH setting to find app \a appName.
+ On Mac, this might include the '<appName>.app/Contents/MacOS/<appName>' location.
+*/
+QString QSystemTest::which( const QString &appName )
+{
+ QString path = PATH();
+ if (!path.isEmpty()) {
+ QStringList path_list;
+#if defined Q_OS_WIN32
+ path_list = path.split(";");
+#else
+ path_list = path.split(":");
+#endif
+ foreach (QString p, path_list) {
+ QString fname = p + QDir::separator() + appName;
+ if (QFile::exists(fname)) return fname;
+#if defined Q_OS_MAC
+ QString mac_name = fname + ".app/Contents/MacOS/" + appName;
+ if (QFile::exists(mac_name)) return mac_name;
+#endif
+#if defined Q_OS_WIN32
+ if (QFile::exists(fname+".exe")) return fname+".exe";
+ if (QFile::exists(fname+".bat")) return fname+".bat";
+#endif
+ }
+ }
+ return "";
+}
+
+QString QSystemTest::processEnvironment( QString const& in ) const
+{
+ struct SystemEnvironment {
+ static QMap<QString,QString> get() {
+ QMap<QString,QString> ret;
+ QStringList env = QProcess::systemEnvironment();
+ foreach (QString str, env) {
+ if (str.contains('=')) {
+ ret[str.left(str.indexOf('=')).toUpper()] = str.mid(str.indexOf('=') + 1);
+ }
+ }
+ return ret;
+ }
+ };
+ static const QMap<QString,QString> environment( SystemEnvironment::get() );
+
+ QString out;
+ static QRegExp re("\\$[{(]?([A-Za-z0-9_]+)[})]?");
+ int offset = 0;
+ while (true) {
+ int index = re.indexIn(in, offset);
+ if (-1 == index) {
+ out += in.mid(offset);
+ break;
+ }
+ out += in.mid(offset, index - offset);
+ out += environment.value(re.cap(1).toUpper());
+ offset += re.matchedLength();
+ }
+
+ return out;
+}
+
+QStringList QSystemTest::processEnvironment( QStringList const& in ) const
+{
+ QStringList out;
+ foreach (QString string, in) {
+ out << processEnvironment(string);
+ }
+ return out;
+}
+
+void QSystemTest::showPromptDialog( const QString &text )
+{
+ QString promptText;
+ if (text.isNull()) {
+ promptText = m_manual_commands;
+ m_manual_commands.clear();
+ } else {
+ promptText = text;
+ }
+
+ if (promptText.isEmpty()) {
+ recordEvents( "" );
+ return;
+ }
+
+ int level1_count = 0;
+ int level2_count = 0;
+ int level3_count = 0;
+ QStringList prompt_text = promptText.split("\n");
+ for (int i=0; i<prompt_text.count(); i++) {
+ if (prompt_text[i].startsWith("* ")) {
+ prompt_text[i] = QString("%1. %2").arg(++level1_count).arg(prompt_text[i].mid(1));
+ level2_count=0;
+ } else if (prompt_text[i].startsWith("** ")) {
+ prompt_text[i] = QString(" %1. %2").arg(++level2_count).arg(prompt_text[i].mid(2));
+ level3_count=0;
+ } else if (prompt_text[i].startsWith("*** ")) {
+ prompt_text[i] = QString(" %1. %2").arg(++level2_count).arg(prompt_text[i].mid(3));
+ }
+ }
+
+#ifdef Q_OS_MAC
+ QString active_window;
+ if (!runAsManualTest()) active_window = activeWindow();
+#endif
+
+ QDialog promptWindow(0, Qt::WindowStaysOnTopHint);
+ Ui::ManualVerificationDlg promptUi;
+ promptUi.setupUi(&promptWindow);
+
+ if (!isConnected()) {
+ promptUi.learnButton->hide();
+ }
+
+ promptUi.test_steps->setText( prompt_text.join("\n") );
+ if (QTest::currentTestFunction() != NULL) {
+ if (QTest::currentDataTag() != NULL) {
+ promptWindow.setWindowTitle( QString("Manual Verification: %1 (%2)").arg(QTest::currentTestFunction()).arg(QTest::currentDataTag()) );
+ } else {
+ promptWindow.setWindowTitle( QString("Manual Verification: %1").arg(QTest::currentTestFunction()) );
+ }
+ }
+
+ QDialog failureWindow(&promptWindow);
+ Ui::FailureDlg failureUi;
+ failureUi.setupUi(&failureWindow);
+ connect( promptUi.cancelButton, SIGNAL(clicked()), &failureWindow, SLOT(exec()) );
+ connect( promptUi.learnButton, SIGNAL(clicked()), this, SLOT(recordPrompt()) );
+ connect( promptUi.learnButton, SIGNAL(clicked()), &promptWindow, SLOT(close()) );
+ connect( promptUi.abort_button, SIGNAL(clicked()), &promptWindow, SLOT(close()) );
+//FIXME connect( promptUi.abort_button, SIGNAL(clicked()), this, SLOT(abortPrompt()) );
+
+ connect( failureUi.buttonBox, SIGNAL(accepted()), &promptWindow, SLOT(reject()) );
+
+ record_prompt = false;
+ abort_prompt = false;
+ promptWindow.exec();
+
+ if (abort_prompt) {
+ skip( "Manual verification step has been aborted", SkipSingle );
+#ifdef Q_OS_MAC
+ if (!runAsManualTest()) activateWindow(active_window);
+#endif
+ return;
+ }
+
+ if (record_prompt) {
+ recordEvents( prompt_text.join("\n") );
+#ifdef Q_OS_MAC
+ if (!runAsManualTest()) activateWindow(active_window);
+#endif
+ return;
+ }
+ bool ret = (promptWindow.result() == QDialog::Accepted);
+
+ /* If we fail with no text, encourage user to enter text */
+ if (learnMode() == LearnNone) {
+ while (!ret && failureUi.failureEdit->toPlainText().isEmpty() ) {
+ int clicked = QMessageBox::warning( 0, "No message entered",
+ "You have not entered a failure message. This may make it difficult to determine "
+ "the reason for failure from the test logs. Continue without entering a message?",
+ QMessageBox::Ok, QMessageBox::Cancel );
+ if (clicked == QMessageBox::Ok) break;
+ QMetaObject::invokeMethod(promptUi.cancelButton, "click", Qt::QueuedConnection);
+ promptWindow.exec();
+ ret = (promptWindow.result() == QDialog::Accepted);
+ }
+ }
+
+ QString failureMessage = failureUi.failureEdit->toPlainText();
+
+ if (ret) {
+#ifdef Q_OS_MAC
+ if (!runAsManualTest()) activateWindow(active_window);
+#endif
+ return;
+ }
+
+ QString message = "Manual verification steps failed";
+ if (!failureMessage.isEmpty()) {
+ message += "; " + failureMessage;
+ }
+ fail( message );
+
+#ifdef Q_OS_MAC
+ if (!runAsManualTest()) activateWindow(active_window);
+#endif
+}
+
+bool QSystemTest::isConnected()
+{
+ return m_test_app && m_test_app->isConnected();
+}
+