diff options
author | Joerg Bornemann <joerg.bornemann@qt.io> | 2021-11-18 14:52:19 +0100 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@qt.io> | 2021-11-23 21:11:45 +0100 |
commit | 3f56950862181f4d50f30d66f577c933795522c3 (patch) | |
tree | d6d47e2693605b3b207e22614860563ee606aff8 /tests/auto | |
parent | 1e9f9a4b7d1efa7efd5d501754f2de000a507cc5 (diff) |
Move macdeployqt and windeployqt from qttools to qtbase
Having all *deployqt tools in qtbase will allow us to couple deployment
support more tightly with the build system.
Change-Id: I299efdacfa6b66a303bb3996ff3ff84e723210a5
Reviewed-by: Kai Koehne <kai.koehne@qt.io>
Diffstat (limited to 'tests/auto')
-rw-r--r-- | tests/auto/tools/CMakeLists.txt | 9 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/CMakeLists.txt | 10 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro | 1 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/source_basicapp/main.cpp | 44 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp | 36 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro | 2 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp | 35 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro | 2 | ||||
-rw-r--r-- | tests/auto/tools/macdeployqt/tst_macdeployqt.cpp | 316 | ||||
-rw-r--r-- | tests/auto/tools/windeployqt/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/auto/tools/windeployqt/test/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/auto/tools/windeployqt/testapp/CMakeLists.txt | 21 | ||||
-rw-r--r-- | tests/auto/tools/windeployqt/testapp/main.cpp | 46 | ||||
-rw-r--r-- | tests/auto/tools/windeployqt/tst_windeployqt.cpp | 181 |
14 files changed, 718 insertions, 0 deletions
diff --git a/tests/auto/tools/CMakeLists.txt b/tests/auto/tools/CMakeLists.txt index f1a9ced60e..8a827d11e4 100644 --- a/tests/auto/tools/CMakeLists.txt +++ b/tests/auto/tools/CMakeLists.txt @@ -15,3 +15,12 @@ if(TARGET Qt::DBus) add_subdirectory(qdbuscpp2xml) add_subdirectory(qdbusxml2cpp) endif() +if(QT_FEATURE_process AND NOT CMAKE_CROSSCOMPILING) + if(QT_FEATURE_macdeployqt) + add_subdirectory(macdeployqt) + endif() + if(QT_FEATURE_windeployqt AND BUILD_SHARED_LIBS) + # windeployqt does not work with static Qt builds. See QTBUG-69427. + add_subdirectory(windeployqt) + endif() +endif() diff --git a/tests/auto/tools/macdeployqt/CMakeLists.txt b/tests/auto/tools/macdeployqt/CMakeLists.txt new file mode 100644 index 0000000000..073c2a9e70 --- /dev/null +++ b/tests/auto/tools/macdeployqt/CMakeLists.txt @@ -0,0 +1,10 @@ +# Generated from macdeployqt.pro. + +##################################################################### +## tst_macdeployqt Test: +##################################################################### + +qt_internal_add_test(tst_macdeployqt + SOURCES + tst_macdeployqt.cpp +) diff --git a/tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro b/tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro new file mode 100644 index 0000000000..bba41b9c12 --- /dev/null +++ b/tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro @@ -0,0 +1 @@ +SOURCES = main.cpp diff --git a/tests/auto/tools/macdeployqt/source_basicapp/main.cpp b/tests/auto/tools/macdeployqt/source_basicapp/main.cpp new file mode 100644 index 0000000000..093a882f32 --- /dev/null +++ b/tests/auto/tools/macdeployqt/source_basicapp/main.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QRasterWindow> +#include <QScreen> +#include <QTimer> + +// Simple test application just to verify that it comes up properly + +int main(int argc, char ** argv) +{ + QGuiApplication app(argc, argv); + QRasterWindow w; + w.setTitle("macdeployqt test application"); + w.show(); + QTimer::singleShot(200, &w, &QCoreApplication::quit); + return app.exec(); +} diff --git a/tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp b/tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp new file mode 100644 index 0000000000..31e2e8117c --- /dev/null +++ b/tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtSql> + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + return db.isValid() ? 0 : 1; +} diff --git a/tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro b/tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro new file mode 100644 index 0000000000..e8183d3cee --- /dev/null +++ b/tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro @@ -0,0 +1,2 @@ +SOURCES = main.cpp +QT += sql diff --git a/tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp b/tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp new file mode 100644 index 0000000000..7d1070b2c5 --- /dev/null +++ b/tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtNetwork> + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + return QSslSocket::supportsSsl() ? 0 : 1; +} diff --git a/tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro b/tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro new file mode 100644 index 0000000000..23954f5941 --- /dev/null +++ b/tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro @@ -0,0 +1,2 @@ +SOURCES = main.cpp +QT += network diff --git a/tests/auto/tools/macdeployqt/tst_macdeployqt.cpp b/tests/auto/tools/macdeployqt/tst_macdeployqt.cpp new file mode 100644 index 0000000000..8922025570 --- /dev/null +++ b/tests/auto/tools/macdeployqt/tst_macdeployqt.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore> +#include <QtTest> + +bool g_testDirectoryBuild = false; // toggle to keep build output for debugging. +QTemporaryDir *g_temporaryDirectory; +QString g_macdeployqtBinary; +QString g_qmakeBinary; +QString g_makeBinary; +QString g_installNameToolBinary; + +#if QT_CONFIG(process) + +static const QString msgProcessError(const QProcess &process, const QString &what) +{ + QString result; + QTextStream(&result) << what << ": \"" << process.program() << ' ' + << process.arguments().join(QLatin1Char(' ')) << "\": " << process.errorString(); + return result; +} + +static bool runProcess(const QString &binary, + const QStringList &arguments, + QString *errorMessage, + const QString &workingDir = QString(), + const QProcessEnvironment &env = QProcessEnvironment(), + int timeOut = 10000, + QByteArray *stdOut = nullptr, QByteArray *stdErr = nullptr) +{ + QProcess process; + if (!env.isEmpty()) + process.setProcessEnvironment(env); + if (!workingDir.isEmpty()) + process.setWorkingDirectory(workingDir); + process.start(binary, arguments, QIODevice::ReadOnly); + if (!process.waitForStarted()) { + *errorMessage = msgProcessError(process, "Failed to start"); + return false; + } + if (!process.waitForFinished(timeOut)) { + *errorMessage = msgProcessError(process, "Timed out"); + process.terminate(); + if (!process.waitForFinished(300)) + process.kill(); + return false; + } + if (stdOut) + *stdOut = process.readAllStandardOutput(); + if (stdErr) + *stdErr= process.readAllStandardError(); + if (process.exitStatus() != QProcess::NormalExit) { + *errorMessage = msgProcessError(process, "Crashed"); + return false; + } + if (process.exitCode() != QProcess::NormalExit) { + *errorMessage = msgProcessError(process, "Exit code " + QString::number(process.exitCode())); + return false; + } + return true; +} + +#else + +static bool runProcess(const QString &binary, + const QStringList &arguments, + QString *arguments, + const QString &workingDir = QString(), + const QProcessEnvironment &env = QProcessEnvironment(), + int timeOut = 5000, + QByteArray *stdOut = Q_NULLPTR, QByteArray *stdErr = Q_NULLPTR) +{ + Q_UNUSED(binary); + Q_UNUSED(arguments); + Q_UNUSED(arguments); + Q_UNUSED(workingDir); + Q_UNUSED(env); + Q_UNUSED(timeOut); + Q_UNUSED(stdOut); + Q_UNUSED(stdErr); + return false; +} + +#endif + +QString sourcePath(const QString &name) +{ + return "source_" + name; +} + +QString buildPath(const QString &name) +{ + if (g_testDirectoryBuild) + return "build_" + name; + return g_temporaryDirectory->path() + "/build_" + name; +} + +bool qmake(const QString &source, const QString &destination, QString *errorMessage) +{ + QStringList args = QStringList() << source; + return runProcess(g_qmakeBinary, args, errorMessage, destination); +} + +bool make(const QString &destination, QString *errorMessage) +{ + QStringList args; + return runProcess(g_makeBinary, args, errorMessage, destination, + {}, 60000); +} + +void build(const QString &name) +{ + // Build the app or framework according to the convention used + // by this test: + // source_name (source code) + // build_name (build artifacts) + + QString source = sourcePath(name); + QString build = buildPath(name); + QString profile = name + ".pro"; + + QString sourcePath = QFINDTESTDATA(source); + QVERIFY(!sourcePath.isEmpty()); + + // Clear/set up build dir + QString buildPath = build; + QVERIFY(QDir(buildPath).removeRecursively()); + QVERIFY(QDir().mkdir(buildPath)); + QVERIFY(QDir(buildPath).exists()); + + // Build application + QString sourceProFile = QDir(sourcePath).canonicalPath() + '/' + profile; + QString errorMessage; + QVERIFY2(qmake(sourceProFile, buildPath, &errorMessage), qPrintable(errorMessage)); + QVERIFY2(make(buildPath, &errorMessage), qPrintable(errorMessage)); +} + +bool changeInstallName(const QString &path, const QString &binary, const QString &from, const QString &to) +{ + QStringList args = QStringList() << binary << "-change" << from << to; + QString errorMessage; + return runProcess(g_installNameToolBinary, args, &errorMessage, path); +} + +bool deploy(const QString &name, const QStringList &options, QString *errorMessage) +{ + QString bundle = name + ".app"; + QString path = buildPath(name); + QStringList args = QStringList() << bundle << options; + return runProcess(g_macdeployqtBinary, args, errorMessage, path); +} + +bool debugDeploy(const QString &name, const QStringList &options, QString *errorMessage) +{ + QString bundle = name + ".app"; + QString path = buildPath(name); + QStringList args = QStringList() << bundle << options << "-verbose=3"; + QByteArray stdOut; + QByteArray stdErr; + bool exitOK = runProcess(g_macdeployqtBinary, args, errorMessage, path, QProcessEnvironment(), + 10000, &stdOut, &stdErr); + + qDebug() << "macdeployqt exit OK" << exitOK; + qDebug() << qPrintable(stdOut); + qDebug() << qPrintable(stdErr); + + return exitOK; +} + +bool run(const QString &name, QString *errorMessage) +{ + QString path = buildPath(name); + QStringList args; + QString binary = name + ".app/Contents/MacOS/" + name; + return runProcess(binary, args, errorMessage, path); +} + +bool runPrintLibraries(const QString &name, QString *errorMessage, QByteArray *stdErr) +{ + QString binary = name + ".app/Contents/MacOS/" + name; + QString path = buildPath(name); + QStringList args; + QProcessEnvironment env = QProcessEnvironment(); + env.insert("DYLD_PRINT_LIBRARIES", "true"); + QByteArray stdOut; + return runProcess(binary, args, errorMessage, path, env, 5000, &stdOut, stdErr); +} + +void runVerifyDeployment(const QString &name) +{ + QString errorMessage; + // Verify that the application runs after deployment and that it loads binaries from + // the application bundle only. + QByteArray libraries; + QVERIFY2(runPrintLibraries(name, &errorMessage, &libraries), qPrintable(errorMessage)); + const QList<QString> parts = QString::fromLocal8Bit(libraries).split("dyld: loaded:"); + const QString qtPath = QLibraryInfo::path(QLibraryInfo::PrefixPath); + // Let assume Qt is not installed in system + foreach (QString part, parts) { + part = part.trimmed(); + if (part.isEmpty()) + continue; + QVERIFY(!parts.startsWith(qtPath)); + } +} + +class tst_macdeployqt : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + void cleanupTestCase(); + void basicapp(); + void plugins_data(); + void plugins(); +}; + +void tst_macdeployqt::initTestCase() +{ +#ifdef QT_NO_PROCESS + QSKIP("This test requires QProcess support"); +#endif + + // Set up test-global unique temporary directory + g_temporaryDirectory = new QTemporaryDir(); + QVERIFY(g_temporaryDirectory->isValid()); + + // Locate build and deployment tools + g_macdeployqtBinary = QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/macdeployqt"; + QVERIFY(!g_macdeployqtBinary.isEmpty()); + g_qmakeBinary = QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/qmake"; + QVERIFY(!g_qmakeBinary.isEmpty()); + g_makeBinary = QStandardPaths::findExecutable("make"); + QVERIFY(!g_makeBinary.isEmpty()); + g_installNameToolBinary = QStandardPaths::findExecutable("install_name_tool"); + QVERIFY(!g_installNameToolBinary.isEmpty()); +} + +void tst_macdeployqt::cleanupTestCase() +{ + delete g_temporaryDirectory; +} + +// Verify that deployment of a basic Qt Gui application works +void tst_macdeployqt::basicapp() +{ +#ifdef QT_NO_PROCESS + QSKIP("This test requires QProcess support"); +#endif + + QString errorMessage; + QString name = "basicapp"; + + // Build and verify that the application runs before deployment + build(name); + QVERIFY2(run(name, &errorMessage), qPrintable(errorMessage)); + + // Deploy application + QVERIFY2(deploy(name, QStringList(), &errorMessage), qPrintable(errorMessage)); + + // Verify deployment + runVerifyDeployment(name); +} + +void tst_macdeployqt::plugins_data() +{ + QTest::addColumn<QString>("name"); + QTest::newRow("sqlite") << "plugin_sqlite"; + QTest::newRow("tls") << "plugin_tls"; +} + +void tst_macdeployqt::plugins() +{ + QFETCH(QString, name); + + build(name); + + // Verify that the test app runs before deployment. + QString errorMessage; + if (!run(name, &errorMessage)) { + qDebug() << qPrintable(errorMessage); + QSKIP("Could not run test application before deployment"); + } + + QVERIFY2(deploy(name, QStringList(), &errorMessage), qPrintable(errorMessage)); + runVerifyDeployment(name); +} + +QTEST_MAIN(tst_macdeployqt) +#include "tst_macdeployqt.moc" diff --git a/tests/auto/tools/windeployqt/CMakeLists.txt b/tests/auto/tools/windeployqt/CMakeLists.txt new file mode 100644 index 0000000000..5eae1ca164 --- /dev/null +++ b/tests/auto/tools/windeployqt/CMakeLists.txt @@ -0,0 +1,4 @@ +# Generated from windeployqt.pro. + +add_subdirectory(testapp) +add_subdirectory(test) diff --git a/tests/auto/tools/windeployqt/test/CMakeLists.txt b/tests/auto/tools/windeployqt/test/CMakeLists.txt new file mode 100644 index 0000000000..d412249a7f --- /dev/null +++ b/tests/auto/tools/windeployqt/test/CMakeLists.txt @@ -0,0 +1,11 @@ +# Generated from test.pro. + +##################################################################### +## tst_windeployqt Test: +##################################################################### + +qt_internal_add_test(tst_windeployqt + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../" # special case + SOURCES + ../tst_windeployqt.cpp +) diff --git a/tests/auto/tools/windeployqt/testapp/CMakeLists.txt b/tests/auto/tools/windeployqt/testapp/CMakeLists.txt new file mode 100644 index 0000000000..83851dae65 --- /dev/null +++ b/tests/auto/tools/windeployqt/testapp/CMakeLists.txt @@ -0,0 +1,21 @@ +# Generated from testapp.pro. + +##################################################################### +## testapp Binary: +##################################################################### + +qt_internal_add_executable(windeploy_testapp # special case + GUI + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/" + SOURCES + main.cpp + PUBLIC_LIBRARIES + Qt::Gui +) + +# special case begin +set_target_properties(windeploy_testapp + PROPERTIES + OUTPUT_NAME testapp +) +# special case end diff --git a/tests/auto/tools/windeployqt/testapp/main.cpp b/tests/auto/tools/windeployqt/testapp/main.cpp new file mode 100644 index 0000000000..4dc03cdad4 --- /dev/null +++ b/tests/auto/tools/windeployqt/testapp/main.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QRasterWindow> +#include <QScreen> +#include <QTimer> + +// Simple test application just to verify that it comes up properly + +int main(int argc, char ** argv) +{ + QGuiApplication app(argc, argv); + QRasterWindow w; + w.setTitle("windeployqt test application"); + const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry(); + w.resize(availableGeometry.size() / 4); + w.show(); + QTimer::singleShot(200, &w, &QCoreApplication::quit); + return app.exec(); +} diff --git a/tests/auto/tools/windeployqt/tst_windeployqt.cpp b/tests/auto/tools/windeployqt/tst_windeployqt.cpp new file mode 100644 index 0000000000..38403c90fc --- /dev/null +++ b/tests/auto/tools/windeployqt/tst_windeployqt.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QLibraryInfo> +#include <QtCore/QProcess> +#include <QtCore/QProcessEnvironment> +#include <QtCore/QStandardPaths> +#include <QtCore/QTextStream> +#include <QtTest/QtTest> + +static const QString msgProcessError(const QProcess &process, const QString &what, + const QByteArray &stdOut = QByteArray(), + const QByteArray &stdErr = QByteArray()) +{ + QString result; + QTextStream str(&result); + str << what << ": \"" << process.program() << ' ' + << process.arguments().join(QLatin1Char(' ')) << "\": " << process.errorString(); + if (!stdOut.isEmpty()) + str << "\nStandard output:\n" << stdOut; + if (!stdErr.isEmpty()) + str << "\nStandard error:\n" << stdErr; + return result; +} + +static bool runProcess(const QString &binary, + const QStringList &arguments, + QString *errorMessage, + const QString &workingDir = QString(), + const QProcessEnvironment &env = QProcessEnvironment(), + int timeOut = 5000, + QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr) +{ + QProcess process; + if (!env.isEmpty()) + process.setProcessEnvironment(env); + if (!workingDir.isEmpty()) + process.setWorkingDirectory(workingDir); + qDebug().noquote().nospace() << "Running: " << QDir::toNativeSeparators(binary) + << ' ' << arguments.join(QLatin1Char(' ')); + process.start(binary, arguments, QIODevice::ReadOnly); + if (!process.waitForStarted()) { + *errorMessage = msgProcessError(process, "Failed to start"); + return false; + } + if (!process.waitForFinished(timeOut)) { + *errorMessage = msgProcessError(process, "Timed out"); + process.terminate(); + if (!process.waitForFinished(300)) + process.kill(); + return false; + } + const QByteArray stdOut = process.readAllStandardOutput(); + const QByteArray stdErr = process.readAllStandardError(); + if (stdOutIn) + *stdOutIn = stdOut; + if (stdErrIn) + *stdErrIn = stdErr; + if (process.exitStatus() != QProcess::NormalExit) { + *errorMessage = msgProcessError(process, "Crashed", stdOut, stdErr); + return false; + } + if (process.exitCode() != QProcess::NormalExit) { + *errorMessage = msgProcessError(process, "Exit code " + QString::number(process.exitCode()), + stdOut, stdErr); + return false; + } + return true; +} + +class tst_windeployqt : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + void help(); + void deploy(); + +private: + QString m_windeployqtBinary; + QString m_testApp; + QString m_testAppBinary; +}; + +void tst_windeployqt::initTestCase() +{ + m_windeployqtBinary = QStandardPaths::findExecutable("windeployqt"); + QVERIFY(!m_windeployqtBinary.isEmpty()); + m_testApp = QFINDTESTDATA("testapp"); + QVERIFY(!m_testApp.isEmpty()); + const QFileInfo testAppBinary(m_testApp + QLatin1String("/testapp.exe")); + QVERIFY2(testAppBinary.isFile(), qPrintable(testAppBinary.absoluteFilePath())); + m_testAppBinary = testAppBinary.absoluteFilePath(); +} + +void tst_windeployqt::help() +{ + QString errorMessage; + QByteArray stdOut; + QByteArray stdErr; + QVERIFY2(runProcess(m_windeployqtBinary, QStringList("--help"), &errorMessage, + QString(), QProcessEnvironment(), 5000, &stdOut, &stdErr), + qPrintable(errorMessage)); + QVERIFY2(!stdOut.isEmpty(), stdErr); +} + +// deploy(): Deploys the test application and launches it with Qt removed from the environment +// to verify it runs stand-alone. + +void tst_windeployqt::deploy() +{ + QString errorMessage; + // Deploy application + QStringList deployArguments; + deployArguments << QLatin1String("--no-translations") << QDir::toNativeSeparators(m_testAppBinary); + QVERIFY2(runProcess(m_windeployqtBinary, deployArguments, &errorMessage, QString(), QProcessEnvironment(), 20000), + qPrintable(errorMessage)); + + // Create environment with Qt and all "lib" paths removed. + const QString qtBinDir = QDir::toNativeSeparators(QLibraryInfo::path(QLibraryInfo::BinariesPath)); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + const QString pathKey = QLatin1String("PATH"); + const QChar pathSeparator(QLatin1Char(';')); // ### fixme: Qt 5.6: QDir::listSeparator() + const QString origPath = env.value(pathKey); + QString newPath; + const QStringList pathElements = origPath.split(pathSeparator, Qt::SkipEmptyParts); + for (const QString &pathElement : pathElements) { + if (pathElement.compare(qtBinDir, Qt::CaseInsensitive) + && !pathElement.contains(QLatin1String("\\lib"), Qt::CaseInsensitive)) { + if (!newPath.isEmpty()) + newPath.append(pathSeparator); + newPath.append(pathElement); + } + } + if (newPath == origPath) + qWarning() << "Unable to remove Qt from PATH"; + env.insert(pathKey, newPath); + + // Create qt.conf to enforce usage of local plugins + QFile qtConf(QFileInfo(m_testAppBinary).absolutePath() + QLatin1String("/qt.conf")); + QVERIFY2(qtConf.open(QIODevice::WriteOnly | QIODevice::Text), + qPrintable(qtConf.fileName() + QLatin1String(": ") + qtConf.errorString())); + QVERIFY(qtConf.write("[Paths]\nPrefix = .\n")); + qtConf.close(); + + // Verify that application still runs + QVERIFY2(runProcess(m_testAppBinary, QStringList(), &errorMessage, QString(), env, 10000), + qPrintable(errorMessage)); +} + +QTEST_MAIN(tst_windeployqt) +#include "tst_windeployqt.moc" |