blob: 6a738b098ed1937a1bd1be66b28ba4fc435beaf8 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtCore/QCommandLineParser>
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QRegExp>
#include <QtCore/QStringList>
#include <QtCore/QMap>
#include <QtCore/QLoggingCategory>
#include <iostream>
#include "runner.h"
QT_USE_NAMESPACE
int main(int argc, char *argv[])
{
// If logging rules are set via env variable, we pass these to the application we are running.
// winrtrunner behaves different from other applications in the regard that its logging rules
// have to be enabled explicitly. Setting "*=true" will not enable extended logging. Reason is
// CI setting "*=true" if an auto test fails and additional winrtrunner output might just
// confuse users.
const QByteArray loggingRules = qgetenv("QT_LOGGING_RULES");
const QList<QByteArray> rules = loggingRules.split(';');
QRegExp runnerExp(QLatin1String("^qt\\.winrtrunner.*\\s*=\\s*true\\s*$"));
bool runnerRuleFound = false;
for (const QByteArray &rule : rules) {
if (runnerExp.indexIn(QLatin1String(rule)) != -1) {
runnerRuleFound = true;
break;
}
}
if (!runnerRuleFound)
qunsetenv("QT_LOGGING_RULES");
QCoreApplication a(argc, argv);
QCommandLineParser parser;
parser.setApplicationDescription(QLatin1String("winrtrunner installs, runs, and collects test "
"results for packages made with Qt."));
parser.addPositionalArgument(QStringLiteral("package [arguments]"),
QLatin1String("The executable or package manifest to act upon. "
"Arguments after the package name will be passed "
"to the application when it starts."));
QCommandLineOption testOption(QStringLiteral("test"),
QLatin1String("Install, start, collect output, stop (if needed), "
"and uninstall the package. This is the "
"default action of winrtrunner."));
parser.addOption(testOption);
QCommandLineOption startOption(QStringLiteral("start"),
QLatin1String("Start the package. The package is installed if "
"it is not already installed. Pass --install to "
"force reinstallation."));
parser.addOption(startOption);
QCommandLineOption debugOption(QStringLiteral("debug"),
QLatin1String("Start the package with the debugger attached. "
"The package is installed if it is not already "
"installed. Pass --install to force "
"reinstallation."),
QLatin1String("debugger"));
parser.addOption(debugOption);
QCommandLineOption debuggerArgumentsOption(QStringLiteral("debugger-arguments"),
QLatin1String("Arguments that are passed to the "
"debugger when --debug is used. If no "
"debugger was provided this option is "
"ignored."),
QLatin1String("arguments"));
parser.addOption(debuggerArgumentsOption);
QCommandLineOption suspendOption(QStringLiteral("suspend"),
QLatin1String("Suspend a running package. When combined "
"with --stop or --test, the app will be "
"suspended before being terminated."));
parser.addOption(suspendOption);
QCommandLineOption stopOption(QStringLiteral("stop"),
QLatin1String("Terminate a running package. Can be be "
"combined with --start and --suspend."));
parser.addOption(stopOption);
QCommandLineOption waitOption(QStringLiteral("wait"),
QLatin1String("If the package is running, waits the given "
"number of seconds before continuing to the next "
"task. Passing 0 causes the runner to wait "
"indefinitely."),
QStringLiteral("seconds"));
parser.addOption(waitOption);
QCommandLineOption installOption(QStringLiteral("install"),
QStringLiteral("(Re)installs the package."));
parser.addOption(installOption);
QCommandLineOption removeOption(QStringLiteral("remove"),
QStringLiteral("Uninstalls the package."));
parser.addOption(removeOption);
QCommandLineOption deviceOption(QStringLiteral("device"),
QLatin1String("Specifies the device to target as a device name "
"or index. Use --list-devices to find available "
"devices. The default device is the first device "
"found for the active run profile."),
QStringLiteral("name|index"));
parser.addOption(deviceOption);
QCommandLineOption profileOption(QStringLiteral("profile"),
QStringLiteral("Force a particular run profile."),
QStringLiteral("name"));
parser.addOption(profileOption);
QCommandLineOption listDevicesOption(QStringLiteral("list-devices"),
QLatin1String("List the available devices "
"(for use with --device)."));
parser.addOption(listDevicesOption);
QCommandLineOption verbosityOption(QStringLiteral("verbose"),
QLatin1String("The verbosity level of the message output "
"(0 - silent, 1 - info, 2 - debug). Defaults to 1."),
QStringLiteral("level"), QStringLiteral("1"));
parser.addOption(verbosityOption);
QCommandLineOption ignoreErrorsOption(QStringLiteral("ignore-errors"),
QStringLiteral("Always exit with code 0, regardless of the error state."));
parser.addOption(ignoreErrorsOption);
QCommandLineOption loopbackExemptOption(QStringLiteral("loopbackexempt"),
QLatin1String("Enables localhost communication for clients,"
"servers or both. Adding this possibility "
"for servers needs elevated rights and "
"might ask for these in a dialog."
"Possible values: client, server, clientserver"),
QStringLiteral("mode"));
parser.addOption(loopbackExemptOption);
parser.addHelpOption();
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
QStringList arguments = QCoreApplication::arguments();
parser.parse(arguments);
QStringList filterRules = QStringList() // Default logging rules
<< QStringLiteral("qt.winrtrunner.warning=true")
<< QStringLiteral("qt.winrtrunner.critical=true")
<< QStringLiteral("qt.winrtrunner.app=true");
if (parser.isSet(verbosityOption)) {
bool ok;
uint verbosity = parser.value(verbosityOption).toUInt(&ok);
if (!ok || verbosity > 2) {
qCCritical(lcWinRtRunner) << "Incorrect value specified for verbosity.";
parser.showHelp(1);
}
switch (verbosity) {
case 2: // Enable debug print
filterRules.append(QStringLiteral("qt.winrtrunner.debug=true"));
break;
case 1: // Remove warnings
filterRules.removeFirst();
// fall through
case 0: // Silent
filterRules.removeFirst();
// fall through
default: // Impossible
break;
}
}
bool loopbackExemptClient = false;
bool loopbackExemptServer = false;
if (parser.isSet(loopbackExemptOption)) {
const QString value = parser.value(loopbackExemptOption);
if (value == QStringLiteral("client")) {
loopbackExemptClient = true;
} else if (value == QStringLiteral("server")) {
loopbackExemptServer = true;
} else if (value == QStringLiteral("clientserver")) {
loopbackExemptClient = true;
loopbackExemptServer = true;
} else {
qCCritical(lcWinRtRunner) << "Incorrect value specified for loopbackexempt.";
parser.showHelp(1);
}
}
QLoggingCategory::setFilterRules(filterRules.join(QLatin1Char('\n')));
if (parser.isSet(listDevicesOption)) {
std::wcout << "Available devices:\n";
const QMap<QString, QStringList> deviceNames = Runner::deviceNames();
for (auto it = deviceNames.cbegin(), end = deviceNames.cend(); it != end; ++it) {
std::wcout << reinterpret_cast<const wchar_t *>(it.key().utf16()) << ":\n";
int index = 0;
for (const QString &device : it.value()) {
std::wcout << " " << index++ << ' '
<< reinterpret_cast<const wchar_t *>(device.utf16()) << '\n';
}
}
std::wcout << std::endl;
return 0;
}
// Process front-end args
if (parser.positionalArguments().count() < 1)
parser.showHelp(parser.isSet(QStringLiteral("help")) ? 0 : 1);
const QString app = parser.positionalArguments().first();
const int appArgsPos = arguments.indexOf(app) + 1;
const QStringList mainArgs = arguments.mid(0, appArgsPos);
QStringList appArgs = arguments.mid(appArgsPos);
parser.process(mainArgs);
// Exit codes:
// 1 - Bad arguments
// 2 - Bad package or no backend available
// 3 - Installation failed
// 4 - Removal failed
// 5 - Start failed
// 6 - Suspend failed
// 7 - Stop failed
// 8 - Test setup failed
// 9 - Test results retrieval failed
// 10 - Enabling debugging failed
// In "test" mode, the exit code of the app is returned
bool ignoreErrors = parser.isSet(ignoreErrorsOption);
bool testEnabled = parser.isSet(testOption);
bool startEnabled = testEnabled || parser.isSet(startOption) || parser.isSet(debugOption);
bool suspendEnabled = parser.isSet(suspendOption);
bool waitEnabled = testEnabled || parser.isSet(waitOption);
bool stopEnabled = !testEnabled && parser.isSet(stopOption); // test and stop are mutually exclusive
bool installEnabled = testEnabled || startEnabled || parser.isSet(installOption);
bool removeBeforeInstall = testEnabled || parser.isSet(installOption);
bool removeEnabled = testEnabled || parser.isSet(removeOption);
// Default to test mode if no conflicting arguments were passed
if (!testEnabled && !installEnabled && !startEnabled && !stopEnabled && !suspendEnabled && !removeEnabled)
testEnabled = installEnabled = removeBeforeInstall = startEnabled = waitEnabled = stopEnabled = removeEnabled = true;
int waitTime = parser.value(waitOption).toInt();
if (!waitTime && testEnabled)
waitTime = 300; // The maximum wait period for test cases is 300 seconds (5 minutes)
// Set up runner
Runner runner(app, appArgs, parser.value(profileOption), parser.value(deviceOption));
if (!runner.isValid())
return ignoreErrors ? 0 : 2;
if (testEnabled && !runner.setupTest()) {
qCDebug(lcWinRtRunner) << "Test setup failed, exiting with code 8.";
return ignoreErrors ? 0 : 8;
}
if (installEnabled && !runner.install(removeBeforeInstall)) {
qCDebug(lcWinRtRunner) << "Installation failed, exiting with code 3.";
return ignoreErrors ? 0 : 3;
}
if (loopbackExemptClient && !runner.setLoopbackExemptClientEnabled(true)) {
qCDebug(lcWinRtRunner) << "Could not enable loopback exemption for client, "
"exiting with code 3.";
return ignoreErrors ? 0 : 3;
}
if (loopbackExemptServer && !runner.setLoopbackExemptServerEnabled(true)) {
qCDebug(lcWinRtRunner) << "Could not enable loopback exemption for server, "
"exiting with code 3.";
return ignoreErrors ? 0 : 3;
}
if (!loggingRules.isNull() && !runner.setLoggingRules(loggingRules)) {
qCDebug(lcWinRtRunner) << "Could not set logging rules, exiting with code 3.";
return ignoreErrors ? 0 : 3;
}
if (parser.isSet(debugOption)) {
const QString &debuggerExecutable = parser.value(debugOption);
const QString &debuggerArguments = parser.value(debuggerArgumentsOption);
qCDebug(lcWinRtRunner) << "Debugger: " << debuggerExecutable;
qCDebug(lcWinRtRunner) << "Debugger Options: " << debuggerArguments;
if (debuggerExecutable.isEmpty()
|| !runner.enableDebugging(debuggerExecutable, debuggerArguments)) {
qCDebug(lcWinRtRunner) << "Failed to enable debugging, exiting with code 10.";
return ignoreErrors ? 0 : 10;
}
}
bool startFailed = startEnabled && !runner.start();
if (parser.isSet(debugOption) && !runner.disableDebugging())
qCDebug(lcWinRtRunner) << "Failed to disable debugging";
if (startFailed) {
qCDebug(lcWinRtRunner) << "Start failed, exiting with code 5.";
return ignoreErrors ? 0 : 5;
}
qint64 pid = runner.pid();
if (pid != -1)
qCWarning(lcWinRtRunner) << "App started with process ID" << pid;
if (waitEnabled)
runner.wait(waitTime);
if (loopbackExemptClient)
runner.setLoopbackExemptClientEnabled(false);
if (loopbackExemptServer)
runner.setLoopbackExemptServerEnabled(false);
if (suspendEnabled && !runner.suspend()) {
qCDebug(lcWinRtRunner) << "Suspend failed, exiting with code 6.";
return ignoreErrors ? 0 : 6;
}
if (stopEnabled && !runner.stop()) {
qCDebug(lcWinRtRunner) << "Stop failed, exiting with code 7.";
return ignoreErrors ? 0 : 7;
}
if (testEnabled && !runner.collectTest()) {
qCDebug(lcWinRtRunner) << "Collect test failed, exiting with code 9.";
return ignoreErrors ? 0 : 9;
}
if (removeEnabled && !runner.remove()) {
qCDebug(lcWinRtRunner) << "Remove failed, exiting with code 4.";
return ignoreErrors ? 0 : 4;
}
if (stopEnabled) {
int exitCode = runner.exitCode();
if (exitCode == -1)
return 0; // Exit code unknown; not necessarily an error
qCWarning(lcWinRtRunner) << "App exited with code" << exitCode;
return ignoreErrors ? 0 : exitCode;
}
return 0;
}