| /**************************************************************************** |
| ** |
| ** 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; |
| } |