diff --git a/drkonqi/CMakeLists.txt b/drkonqi/CMakeLists.txt
--- a/drkonqi/CMakeLists.txt
+++ b/drkonqi/CMakeLists.txt
@@ -17,7 +17,6 @@
add_subdirectory( data )
add_subdirectory( parser )
-add_subdirectory( tests )
if ( WIN32 )
add_subdirectory( kdbgwin )
endif ()
@@ -114,3 +113,7 @@
endif()
install(TARGETS drkonqi DESTINATION ${KDE_INSTALL_LIBEXECDIR})
+
+# Only go into tests once we have a drkonqi target so the tests can reference
+# it.
+add_subdirectory( tests )
diff --git a/drkonqi/aboutbugreportingdialog.cpp b/drkonqi/aboutbugreportingdialog.cpp
--- a/drkonqi/aboutbugreportingdialog.cpp
+++ b/drkonqi/aboutbugreportingdialog.cpp
@@ -130,7 +130,7 @@
"you "
"need to have an account on the KDE bug tracking system. If you do "
"not have one, you can create one here: %1",
- QStringLiteral(KDE_BUGZILLA_CREATE_ACCOUNT_URL)),
+ KDE_BUGZILLA_CREATE_ACCOUNT_URL),
xi18nc("@info/rich","Then, enter your username and password and "
"press the Login button. You can use this login to directly access the "
"KDE bug tracking system later."),
diff --git a/drkonqi/bugreportaddress.h b/drkonqi/bugreportaddress.h
--- a/drkonqi/bugreportaddress.h
+++ b/drkonqi/bugreportaddress.h
@@ -26,13 +26,12 @@
public:
inline BugReportAddress() : QString() {}
inline BugReportAddress(const QString & address)
- : QString(address == QLatin1String("submit@bugs.kde.org") ?
- QLatin1String(KDE_BUGZILLA_URL) : address)
+ : QString(address == QLatin1String("submit@bugs.kde.org") ? KDE_BUGZILLA_URL : address)
{}
inline bool isKdeBugzilla() const
{
- return *this == QLatin1String(KDE_BUGZILLA_URL);
+ return *this == KDE_BUGZILLA_URL;
}
inline bool isEmail() const
diff --git a/drkonqi/bugzillaintegration/reportassistantpages_base.cpp b/drkonqi/bugzillaintegration/reportassistantpages_base.cpp
--- a/drkonqi/bugzillaintegration/reportassistantpages_base.cpp
+++ b/drkonqi/bugzillaintegration/reportassistantpages_base.cpp
@@ -104,6 +104,10 @@
BacktraceParser::Usefulness use =
DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness();
+ if (DrKonqi::ignoreQuality()) {
+ return true;
+ }
+
if ((use == BacktraceParser::InvalidUsefulness || use == BacktraceParser::ProbablyUseless
|| use == BacktraceParser::Useless) && m_backtraceWidget->canInstallDebugPackages()) {
if ( KMessageBox::Yes == KMessageBox::questionYesNo(this,
@@ -137,6 +141,8 @@
DrKonqi::crashedApplication()->name()));
connect(ui.m_rememberGroup, static_cast(&QButtonGroup::buttonClicked), this, &BugAwarenessPage::updateCheckBoxes);
+ // Also listen to toggle so radio buttons are covered.
+ connect(ui.m_rememberGroup, static_cast(&QButtonGroup::buttonToggled), this, &BugAwarenessPage::updateCheckBoxes);
ui.m_appSpecificDetailsExamples->setVisible(reportInterface()->appDetailsExamples()->hasExamples());
ui.m_appSpecificDetailsExamples->setContextMenuPolicy(Qt::NoContextMenu);
@@ -309,7 +315,7 @@
"Reporting Guide by clicking on the "
"Help button."));
//but this guide doesn't mention bt packages? that's techbase
- //->>and the help guide mention techbase page...
+ //->>and the help guide mention techbase page...
}
break;
}
diff --git a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp
--- a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp
+++ b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla.cpp
@@ -59,7 +59,7 @@
static const char kWalletEntryUsername[] = "username";
static const char kWalletEntryPassword[] = "password";
-static const char konquerorKWalletEntryName[] = KDE_BUGZILLA_URL "index.cgi#login";
+static QString konquerorKWalletEntryName = KDE_BUGZILLA_URL + "index.cgi#login";
static const char konquerorKWalletEntryUsername[] = "Bugzilla_login";
static const char konquerorKWalletEntryPassword[] = "Bugzilla_password";
@@ -102,7 +102,7 @@
"one, you can freely create one here. "
"Please do not use disposable email accounts.",
DrKonqi::crashedApplication()->bugReportAddress(),
- QLatin1String(KDE_BUGZILLA_CREATE_ACCOUNT_URL)));
+ KDE_BUGZILLA_CREATE_ACCOUNT_URL));
}
bool BugzillaLoginPage::isComplete()
@@ -202,15 +202,15 @@
ui.m_passwordEdit->setText(password);
}
}
- } else if (kWalletEntryExists(QLatin1String(konquerorKWalletEntryName))) {
+ } else if (kWalletEntryExists(konquerorKWalletEntryName)) {
//If the DrKonqi entry is empty, but a Konqueror entry exists, use and copy it.
openWallet();
if (m_wallet) {
m_wallet->setFolder(KWallet::Wallet::FormDataFolder());
//Fetch Konqueror data
QMap values;
- m_wallet->readMap(QLatin1String(konquerorKWalletEntryName), values);
+ m_wallet->readMap(konquerorKWalletEntryName, values);
QString username = values.value(QLatin1String(konquerorKWalletEntryUsername));
QString password = values.value(QLatin1String(konquerorKWalletEntryPassword));
@@ -256,7 +256,7 @@
QLatin1String("/modules/kcookiejar"),
QLatin1String("org.kde.KCookieServer"));
QDBusReply advice = kcookiejar.call(QLatin1String("getDomainAdvice"),
- QLatin1String(KDE_BUGZILLA_URL));
+ KDE_BUGZILLA_URL);
if (!advice.isValid()) {
KMessageBox::error(this, i18n("Failed to communicate with KCookieServer."));
@@ -279,9 +279,9 @@
"to set cookies", "No, do not allow"));
if (KMessageBox::warningYesNo(this, msg, QString(), yesItem, noItem) == KMessageBox::Yes) {
- QDBusReply success = kcookiejar.call(QLatin1String("setDomainAdvice"),
- QLatin1String(KDE_BUGZILLA_URL),
- QLatin1String("Accept"));
+ QDBusReply success = kcookiejar.call(QStringLiteral("setDomainAdvice"),
+ KDE_BUGZILLA_URL,
+ QStringLiteral("Accept"));
if (!success.isValid() || !success.value()) {
qWarning() << "Failed to set domain advice in KCookieServer";
return false;
diff --git a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp
--- a/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp
+++ b/drkonqi/bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp
@@ -938,6 +938,11 @@
connect(ui.buttonGroupProceed, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed()));
connect(ui.buttonGroupProceedQuestion, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed()));
+ // Also listen to toggle so radio buttons are covered.
+ connect(ui.buttonGroupProceed, static_cast(&QButtonGroup::buttonToggled),
+ this, &BugzillaReportConfirmationDialog::checkProceed);
+ connect(ui.buttonGroupProceedQuestion, static_cast(&QButtonGroup::buttonToggled),
+ this, &BugzillaReportConfirmationDialog::checkProceed);
if (!m_showProceedQuestion) {
ui.proceedLabel->setEnabled(false);
diff --git a/drkonqi/bugzillaintegration/reportinterface.cpp b/drkonqi/bugzillaintegration/reportinterface.cpp
--- a/drkonqi/bugzillaintegration/reportinterface.cpp
+++ b/drkonqi/bugzillaintegration/reportinterface.cpp
@@ -54,7 +54,7 @@
}
void ReportInterface::setBugAwarenessPageData(bool rememberSituation,
- Reproducible reproducible, bool actions,
+ Reproducible reproducible, bool actions,
bool unusual, bool configuration)
{
//Save the information the user can provide about the crash from the assistant page
@@ -367,6 +367,10 @@
bool ReportInterface::isWorthReporting() const
{
+ if (DrKonqi::ignoreQuality()) {
+ return true;
+ }
+
//Evaluate if the provided information is useful enough to enable the automatic report
bool needToReport = false;
diff --git a/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_information.ui b/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_information.ui
--- a/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_information.ui
+++ b/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_information.ui
@@ -44,6 +44,9 @@
+
+ Information about the crash text
+ false
diff --git a/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_login.ui b/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_login.ui
--- a/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_login.ui
+++ b/drkonqi/bugzillaintegration/ui/assistantpage_bugzilla_login.ui
@@ -48,6 +48,9 @@
+
+ Password input
+ true
@@ -58,6 +61,9 @@
+
+ Username input
+ true
diff --git a/drkonqi/drkonqi.h b/drkonqi/drkonqi.h
--- a/drkonqi/drkonqi.h
+++ b/drkonqi/drkonqi.h
@@ -63,6 +63,8 @@
static bool isRestarted();
static bool isKeepRunning();
static int thread();
+ static bool ignoreQuality();
+ static const QString &kdeBugzillaURL();
private:
DrKonqi();
diff --git a/drkonqi/drkonqi.cpp b/drkonqi/drkonqi.cpp
--- a/drkonqi/drkonqi.cpp
+++ b/drkonqi/drkonqi.cpp
@@ -327,3 +327,26 @@
{
return instance()->m_thread;
}
+
+bool DrKonqi::ignoreQuality()
+{
+ return qEnvironmentVariableIsSet("DRKONQI_IGNORE_QUALITY");
+}
+
+const QString &DrKonqi::kdeBugzillaURL()
+{
+ // NB: for practical reasons this cannot use the shared instance. Initing the instances requires
+ // knowing the URL already, so we'd have an init loop. Use a local static instead.
+ static QString url;
+ if (!url.isEmpty()) {
+ return url;
+ }
+
+ url = QString::fromLocal8Bit(qgetenv("DRKONQI_KDE_BUGZILLA_URL"));
+ if (!url.isEmpty()) {
+ return url;
+ }
+
+ url = QStringLiteral("https://bugs.kde.org/");
+ return url;
+}
diff --git a/drkonqi/drkonqi_globals.h b/drkonqi/drkonqi_globals.h
--- a/drkonqi/drkonqi_globals.h
+++ b/drkonqi/drkonqi_globals.h
@@ -20,6 +20,8 @@
#include
#include
+#include "drkonqi.h"
+
/** This class provides a custom constructor to fill the "toolTip"
* and "whatsThis" texts of KGuiItem with the same text.
*/
@@ -31,8 +33,8 @@
};
/* Urls are defined globally here, so that they can change easily */
-#define KDE_BUGZILLA_URL "https://bugs.kde.org/"
-#define KDE_BUGZILLA_CREATE_ACCOUNT_URL KDE_BUGZILLA_URL "createaccount.cgi"
+#define KDE_BUGZILLA_URL DrKonqi::kdeBugzillaURL()
+#define KDE_BUGZILLA_CREATE_ACCOUNT_URL KDE_BUGZILLA_URL + QStringLiteral("createaccount.cgi")
#define KDE_BUGZILLA_SHORT_URL "bugs.kde.org"
#define TECHBASE_HOWTO_DOC "https://community.kde.org/Guidelines_and_HOWTOs/Debugging/How_to_create_useful_crash_reports#Preparing_your_KDE_packages"
diff --git a/drkonqi/drkonqidialog.cpp b/drkonqi/drkonqidialog.cpp
--- a/drkonqi/drkonqidialog.cpp
+++ b/drkonqi/drkonqidialog.cpp
@@ -42,8 +42,7 @@
#endif
static const char ABOUT_BUG_REPORTING_URL[] = "#aboutbugreporting";
-static const char DRKONQI_REPORT_BUG_URL[] =
- KDE_BUGZILLA_URL "enter_bug.cgi?product=drkonqi&format=guided";
+static QString DRKONQI_REPORT_BUG_URL = KDE_BUGZILLA_URL + QStringLiteral("enter_bug.cgi?product=drkonqi&format=guided");
DrKonqiDialog::DrKonqiDialog(QWidget * parent) :
QDialog(parent),
@@ -121,7 +120,7 @@
"to the KDE bug tracking system. Do not forget to include "
"the backtrace from the Developer Information "
"tab.",
- QLatin1String(DRKONQI_REPORT_BUG_URL));
+ DRKONQI_REPORT_BUG_URL);
} else if (DrKonqi::isSafer()) {
reportMessage = xi18nc("@info", "The reporting assistant is disabled because "
"the crash handler dialog was started in safe mode."
@@ -279,7 +278,7 @@
{
if (link == QLatin1String(ABOUT_BUG_REPORTING_URL)) {
showAboutBugReporting();
- } else if (link == QLatin1String(DRKONQI_REPORT_BUG_URL)) {
+ } else if (link == DRKONQI_REPORT_BUG_URL) {
QDesktopServices::openUrl(QUrl(link));
} else if (link.startsWith(QLatin1String("http"))) {
qWarning() << "unexpected link";
diff --git a/drkonqi/main.cpp b/drkonqi/main.cpp
--- a/drkonqi/main.cpp
+++ b/drkonqi/main.cpp
@@ -100,6 +100,7 @@
QCommandLineOption keepRunningOption(QStringLiteral("keeprunning"), i18nc("@info:shell","Keep the program running and generate "
"the backtrace at startup"));
QCommandLineOption threadOption(QStringLiteral("thread"), i18nc("@info:shell","The of the failing thread"), QStringLiteral("threadid"));
+ QCommandLineOption dialogOption(QStringLiteral("dialog"), i18nc("@info:shell","Do not show a notification but launch the debug dialog directly"));
parser.addOption(signalOption);
parser.addOption(appNameOption);
@@ -114,6 +115,7 @@
parser.addOption(restartedOption);
parser.addOption(keepRunningOption);
parser.addOption(threadOption);
+ parser.addOption(dialogOption);
aboutData.setupCommandLine(&parser);
parser.process(qa);
@@ -131,6 +133,7 @@
DrKonqi::setRestarted(parser.isSet(restartedOption));
DrKonqi::setKeepRunning(parser.isSet(keepRunningOption));
DrKonqi::setThread(parser.value(threadOption).toInt());
+ auto forceDialog = parser.isSet(dialogOption);
#if HAVE_X11
const QString startupId = parser.value(startupIdOption);
@@ -156,7 +159,7 @@
// if no notification service is running (eg. shell crashed, or other desktop environment)
// and we didn't auto-restart the app, open DrKonqi dialog instead of showing an SNI
// and emitting a desktop notification
- if (!restarted && !StatusNotifier::notificationServiceRegistered()) {
+ if (forceDialog || (!restarted && !StatusNotifier::notificationServiceRegistered())) {
openDrKonqiDialog();
} else {
StatusNotifier *statusNotifier = new StatusNotifier();
diff --git a/drkonqi/tests/CMakeLists.txt b/drkonqi/tests/CMakeLists.txt
--- a/drkonqi/tests/CMakeLists.txt
+++ b/drkonqi/tests/CMakeLists.txt
@@ -3,3 +3,30 @@
if(KF5XmlRpcClient_FOUND)
add_subdirectory(bugzillalibtest)
endif()
+
+if(NOT RUBY_EXECTUABLE)
+ find_program(RUBY_EXECTUABLE ruby)
+endif()
+if(RUBY_EXECUTABLE)
+ execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'atspi'"
+ RESULT_VARIABLE RUBY_ATSPI)
+ execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'xmlrpc/server'"
+ RESULT_VARIABLE RUBY_XMLRPC)
+endif()
+if(NOT XVFB_RUN_EXECTUABLE)
+ find_program(XVFB_RUN_EXECTUABLE xvfb-run)
+endif()
+if(NOT ATSPI_BUS_LAUNCHER_EXECUTABLE)
+ find_program(ATSPI_BUS_LAUNCHER_EXECUTABLE
+ NAMES at-spi-bus-launcher
+ PATHS /usr/lib/at-spi2-core/
+ DOC "AT-SPI accessibility dbus launcher")
+endif()
+
+if(RUBY_EXECUTABLE AND XVFB_RUN_EXECTUABLE AND ATSPI_BUS_LAUNCHER_EXECUTABLE
+ AND RUBY_ATSPI EQUAL 0 AND RUBY_XMLRPC EQUAL 0)
+ set(WITH_DRKONI_INTEGRATION_TESTING TRUE)
+endif()
+add_feature_info(DrKonqiIntegrationTesting WITH_DRKONI_INTEGRATION_TESTING
+ "Needs Ruby, functional atspi and xmlrpc gems, as well as xvfb-run.")
+add_subdirectory(integration)
diff --git a/drkonqi/tests/integration/CMakeLists.txt b/drkonqi/tests/integration/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/drkonqi/tests/integration/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_test(NAME drkonqi_integration_suite
+ COMMAND ${RUBY_EXECTUABLE}
+ ${CMAKE_CURRENT_SOURCE_DIR}/suite
+ --drkonqi $
+ --at-spi-bus-launcher ${ATSPI_BUS_LAUNCHER_EXECUTABLE})
+
+# Hack to get rb files to show in qtc.
+file(GLOB RUBIES suite *.rb)
+add_custom_target(Rubies ALL echo SOURCES ${RUBIES})
diff --git a/drkonqi/tests/integration/duplicate_attach_test.rb b/drkonqi/tests/integration/duplicate_attach_test.rb
new file mode 100644
--- /dev/null
+++ b/drkonqi/tests/integration/duplicate_attach_test.rb
@@ -0,0 +1,215 @@
+# Copyright (C) 2017 Harald Sitter
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License or (at your option) version 3 or any later version
+# accepted by the membership of KDE e.V. (or its successor approved
+# by the membership of KDE e.V.), which shall act as a proxy
+# defined in Section 14 of version 3 of the license.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+require_relative 'test_helper'
+
+require 'xmlrpc/client'
+require 'xmlrpc/server'
+
+# Monkey patch the xmlrpc server to let us handle regular GET requests.
+# Drkonqi partially goes through regular bugzilla cgi's simply requesting xml
+# output.
+module XMLServerInterceptor
+ # raw xml data is here
+ # def process(*args)
+ # warn "+++ #{__method__} +++"
+ # p args
+ # warn "--- #{__method__} ---"
+ # super
+ # end
+
+ def service(req, resp)
+ # Where webrick comes in with request, server rejects non-xmlrpc requests
+ # so we'll manually handle GET requests as necessary and forward to an
+ # actual bugzilla so we don't have to reimplement everything.
+ warn "+++ #{__method__} +++"
+ if req.request_method == 'GET'
+ if req.request_uri.path.include?('buglist.cgi') # Returns CSV.
+ resp.body = <<-EOF
+bug_id,"bug_severity","priority","bug_status","product","short_desc","resolution"
+375161,"crash","NOR","NEEDSINFO","dolphin","Dolphin crash, copy from Samba share","BACKTRACE"
+ EOF
+ return
+ end
+
+ if req.request_uri.path.include?('show_bug.cgi')
+ uri = req.request_uri.dup
+ uri.host = 'bugstest.kde.org'
+ uri.scheme = 'https'
+ uri.port = nil
+ resp.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, uri.to_s)
+ return
+ end
+ end
+ warn "--- #{__method__} ---"
+ super
+ end
+end
+
+class XMLRPC::Server
+ prepend XMLServerInterceptor
+
+ # Expose webrick server so we can get our port :|
+ # https://github.com/ruby/xmlrpc/issues/17
+ attr_accessor :server
+end
+
+class TestDuplicateAttach < ATSPITest
+ def setup
+ server = XMLRPC::Server.new(0)
+ port = server.server.config.fetch(:Port)
+ ENV['DRKONQI_KDE_BUGZILLA_URL'] = "http://localhost:#{port}/"
+
+ @got_comment = false
+
+ server.set_default_handler do |name, args|
+ puts '+++ handler +++'
+ p name, args
+ if name == 'User.login'
+ next {"id"=>12345, "token"=>"12345-cJ5o717AbC"}
+ end
+ if name == 'Bug.update'
+ id = args.fetch('ids').fetch(0)
+ cc_to_add = args.fetch('cc').fetch('add')
+ next {"bugs"=>[{"last_change_time"=>DateTime.now, "id"=>id, "changes"=>{"cc"=>{"removed"=>"", "added"=>cc_to_add}}, "alias"=>[]}]}
+ end
+ if name == 'Bug.add_attachment'
+ # Check for garbage string from test
+ @got_comment = args.fetch('comment').include?('yyyyyyyyyyyyyyyy')
+ next { "ids" => [1234] }
+ end
+ puts '~~~ bugzilla ~~~'
+ # Pipe request through bugstest.
+ # The arguments are killing me.
+ client = XMLRPC::Client.new('bugstest.kde.org', '/xmlrpc.cgi', 443,
+ nil, nil, nil, nil, true)
+ bugzilla = client.call(name, *args)
+ p bugzilla
+ next bugzilla
+ end
+
+ @xml_server_thread = Thread.start { server.serve }
+
+ @tracee = fork { loop { sleep(999_999_999) } }
+
+ assert File.exist?(DRKONQI_PATH), "drkonqi not at #{DRKONQI_PATH}"
+ # Will die with our Xephyr in case of errors.
+ pid = spawn("#{DRKONQI_PATH} --signal 11 --pid #{@tracee} --bugaddress submit@bugs.kde.org --dialog")
+ sleep 4 # Grace to give time to appear on at-spi
+ assert_nil Process.waitpid(pid, Process::WNOHANG),
+ 'drkonqi failed to start or died'
+ end
+
+ def teardown
+ Process.kill('KILL', @tracee)
+ Process.waitpid2(@tracee)
+ @xml_server_thread.kill
+ @xml_server_thread.join
+ end
+
+ # When evaluating duplicates
+ def test_duplicate_attach
+ drkonqi = ATSPI.desktops[0].applications.find { |x| x.name == 'drkonqi' }
+ refute_nil drkonqi
+
+ accessible = find_in(drkonqi.windows[-1], name: 'Report Bug')
+ press(accessible)
+
+ find_in(drkonqi, name: 'Crash Reporting Assistant') do |window|
+ accessible = find_in(window, name: 'Next')
+ press(accessible)
+
+ accessible = find_in(window, name: 'Yes')
+ toggle_on(accessible)
+
+ accessible = find_in(window, name: /^What I was doing when the application.+/)
+ toggle_on(accessible)
+
+ accessible = find_in(window, name: 'Next')
+ press(accessible)
+
+ loop do
+ # Drkonqi is now doing the trace, wait until it is done.
+ accessible = find_in(window, name: 'Next')
+ refute_nil accessible
+ if accessible.states.include?(:sensitive)
+ press(accessible)
+ break
+ end
+ warn accessible.states
+ sleep 2
+ end
+
+ # Set pseudo login data if there are none.
+ accessible = find_in(window, name: 'Username input')
+ accessible.text.set_to 'xxx' if accessible.text.length <= 0
+ accessible = find_in(window, name: 'Password input')
+ accessible.text.set_to 'yyy' if accessible.text.length <= 0
+
+ accessible = find_in(window, name: 'Login')
+ press(accessible)
+
+ sleep 2 # Wait for login and bug listing
+
+ accessible = find_in(window, name: '375161')
+ toggle_on(accessible)
+
+ accessible = find_in(window, name: 'Open selected report')
+ press(accessible)
+ end
+
+ find_in(drkonqi, name: 'Bug Description') do |window|
+ accessible = find_in(window, name: 'Suggest this crash is related')
+ press(accessible)
+ end
+
+ find_in(drkonqi, name: 'Related Bug Report') do |window|
+ accessible = find_in(window, name: /^Completely sure: attach my information.+/)
+ toggle_on(accessible)
+
+ accessible = find_in(window, name: 'Continue')
+ press(accessible)
+ end
+
+ find_in(drkonqi, name: 'Crash Reporting Assistant') do |window|
+ accessible = find_in(window, name: /^The report is going to be attached.+/)
+ refute_nil accessible
+
+ accessible = find_in(window, name: 'Next')
+ press(accessible)
+
+ accessible = find_in(window, name: 'Information about the crash text')
+ accessible.text.set_to(accessible.text.to_s +
+ Array.new(128).collect { 'y' }.join)
+
+ accessible = find_in(window, name: 'Next')
+ press(accessible)
+
+ accessible = find_in(window, name: 'Next')
+ press(accessible)
+
+ accessible = find_in(window, name: /.*Crash report sent.*/)
+ refute_nil accessible
+
+ accessible = find_in(window, name: 'Finish')
+ press(accessible)
+ end
+
+ assert @got_comment # only true iff the server go tour yyyyyy garbage string
+ end
+end
diff --git a/drkonqi/tests/integration/suite b/drkonqi/tests/integration/suite
new file mode 100755
--- /dev/null
+++ b/drkonqi/tests/integration/suite
@@ -0,0 +1,81 @@
+#!/usr/bin/env ruby
+#
+# Copyright (C) 2017 Harald Sitter
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License or (at your option) version 3 or any later version
+# accepted by the membership of KDE e.V. (or its successor approved
+# by the membership of KDE e.V.), which shall act as a proxy
+# defined in Section 14 of version 3 of the license.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+require 'fileutils'
+require 'optparse'
+require 'tmpdir'
+
+# Isolates GUI via xephyr
+class XephyrIsolator
+ def run(cmd)
+ ephemeral('Xephyr -screen 1024x768x24+32 :666') do
+ ENV['DISPLAY'] = ':666'
+ system(cmd) || raise
+ end
+ end
+
+ private
+
+ def ephemeral(*args)
+ pid = spawn(*args)
+ yield
+ ensure
+ Process.kill('KILL', pid) if pid
+ Process.waitpid2(pid) if pid
+ end
+end
+
+# Isolates GUI via xvfb-run
+class XvfbIsolator
+ def run(cmd)
+ system("xvfb-run -a --server-args=\"-screen 0 1024x768x24\" #{cmd}") || raise
+ end
+end
+
+OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} ARGS"
+
+ opts.separator('')
+
+ opts.on('--drkonqi PATH', 'Path to drkonqi bin to test.') do |v|
+ ENV['DRKONQI_PATH'] = v
+ end
+
+ opts.on('--at-spi-bus-launcher PATH',
+ 'Path to --at-spi-bus-launcher bin to use for testing.') do |v|
+ ENV['AT_SPI_BUS_LAUNCHER_PATH'] = v
+ end
+end.parse!
+
+ENV['DRKONQI_PATH'] ||= '/usr/lib/x86_64-linux-gnu/libexec/drkonqi'
+ENV['AT_SPI_BUS_LAUNCHER_PATH'] ||= '/usr/lib/at-spi2-core/at-spi-bus-launcher'
+
+# Isolate ourselves by forcing into a separate home and unsetting the XDG path
+# variables. Then spin up a suitable virtual X, run a new dbus session bus
+# and our test in that environment.
+Dir.mktmpdir do |tmpdir|
+ ENV['HOME'] = tmpdir
+ ENV.keys.each { |k| ENV.delete(k) if k.start_with?('XDG_') }
+ Dir.glob("#{__dir__}/*_test.rb").each do |test|
+ isolator = ENV['XEPHYR'] ? XephyrIsolator.new : XvfbIsolator.new
+ isolator.run("dbus-run-session -- ruby #{test} -p")
+ end
+ sleep 8 # Wait a bit to make sure all children are dead.
+end
diff --git a/drkonqi/tests/integration/test_helper.rb b/drkonqi/tests/integration/test_helper.rb
new file mode 100644
--- /dev/null
+++ b/drkonqi/tests/integration/test_helper.rb
@@ -0,0 +1,85 @@
+# Copyright (C) 2017 Harald Sitter
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License or (at your option) version 3 or any later version
+# accepted by the membership of KDE e.V. (or its successor approved
+# by the membership of KDE e.V.), which shall act as a proxy
+# defined in Section 14 of version 3 of the license.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+ENV['DRKONQI_IGNORE_QUALITY'] = '1'
+ENV['KDE_FORK_SLAVES'] = '1'
+
+DRKONQI_PATH = ENV['DRKONQI_PATH']
+AT_SPI_BUS_LAUNCHER_PATH = ENV['AT_SPI_BUS_LAUNCHER_PATH']
+warn "Testing against #{DRKONQI_PATH} with #{AT_SPI_BUS_LAUNCHER_PATH}"
+
+# Dies together with our dbus.
+spawn "#{AT_SPI_BUS_LAUNCHER_PATH} --launch-immediately"
+
+require 'atspi'
+require 'minitest/autorun'
+
+# Adds convenience methods for ATSPI on top of minitest.
+class ATSPITest < Minitest::Test
+ def find_in(parent, name: nil, recursion: false)
+ raise 'no accessible' if parent.nil?
+ accessibles = parent.children.collect do |child|
+ ret = []
+ if child.children.size != 0 # recurse
+ ret += find_in(child, name: name, recursion: true)
+ end
+ if name && child.states.include?(:showing)
+ if (name.is_a?(Regexp) && child.name.match(name)) ||
+ (name.is_a?(String) && child.name == name)
+ ret << child
+ end
+ end
+ ret
+ end.compact.uniq.flatten
+ return accessibles if recursion
+ raise "not exactly one accessible for #{name} => #{accessibles.collect {|x| x.name}.join(', ')}" if accessibles.size > 1
+ raise "cannot find accessible(#{name})" if accessibles.size < 1
+ yield accessibles[0] if block_given?
+ accessibles[0]
+ end
+
+ def press(accessible)
+ raise 'no accessible' if accessible.nil?
+ action = accessible.actions.find { |x| x.name == 'Press' }
+ refute_nil action, 'expected accessible to be pressable'
+ action.do_it!
+ sleep 0.25
+ end
+
+ def focus(accessible)
+ raise 'no accessible' if accessible.nil?
+ action = accessible.actions.find { |x| x.name == 'SetFocus' }
+ refute_nil action, 'expected accessible to be focusable'
+ action.do_it!
+ sleep 0.1
+ end
+
+ def toggle(accessible)
+ raise 'no accessible' if accessible.nil?
+ action = accessible.actions.find { |x| x.name == 'Toggle' }
+ refute_nil action, 'expected accessible to be toggle'
+ action.do_it!
+ sleep 0.1
+ end
+
+ def toggle_on(accessible)
+ raise 'no accessible' if accessible.nil?
+ return if accessible.states.any? { |x| %i[checked selected].include?(x) }
+ toggle(accessible)
+ end
+end