diff options
Diffstat (limited to 'tests/auto/blackbox/tst_blackbox.cpp')
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.cpp | 1077 |
1 files changed, 675 insertions, 402 deletions
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 20d85d543..e9aae5c6d 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -53,6 +53,7 @@ #include <QtCore/qsettings.h> #include <QtCore/qtemporarydir.h> #include <QtCore/qtemporaryfile.h> +#include <QtCore/qversionnumber.h> #include <algorithm> #include <functional> @@ -62,7 +63,6 @@ #define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir) using qbs::Internal::HostOsInfo; -using qbs::Profile; class MacosTarHealer { public: @@ -138,7 +138,7 @@ QString TestBlackbox::findArchiver(const QString &fileName, int *status) QString binary = findExecutable(QStringList(fileName)); if (binary.isEmpty()) { const SettingsPtr s = settings(); - Profile p(profileName(), s.get()); + qbs::Profile p(profileName(), s.get()); binary = findExecutable(p.value("archiver.command").toStringList()); } return binary; @@ -298,7 +298,7 @@ void TestBlackbox::textTemplate() static QStringList sortedFileList(const QByteArray &ba) { - auto list = QString::fromUtf8(ba).split(QRegularExpression("[\r\n]"), QBS_SKIP_EMPTY_PARTS); + auto list = QString::fromUtf8(ba).split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); std::sort(list.begin(), list.end()); return list; } @@ -698,7 +698,7 @@ void TestBlackbox::buildDirectories() QDir::setCurrent(projectDir); QCOMPARE(runQbs(), 0); const QStringList outputLines - = QString::fromLocal8Bit(m_qbsStdout.trimmed()).split('\n', QBS_SKIP_EMPTY_PARTS); + = QString::fromLocal8Bit(m_qbsStdout.trimmed()).split('\n', Qt::SkipEmptyParts); QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p1")), m_qbsStdout.constData()); QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p2")), @@ -707,6 +707,45 @@ void TestBlackbox::buildDirectories() QVERIFY2(outputLines.contains(projectDir), m_qbsStdout.constData()); } +void TestBlackbox::buildDirPlaceholders_data() +{ + QTest::addColumn<QString>("buildDir"); + QTest::addColumn<bool>("setProjectFile"); + QTest::addColumn<bool>("successExpected"); + + QTest::newRow("normal dir, with project file") << "somedir" << true << true; + QTest::newRow("normal dir, without project file") << "somedir" << false << true; + QTest::newRow("@project, with project file") << "somedir/@project" << true << true; + QTest::newRow("@project, without project file") << "somedir/@project" << false << false; + QTest::newRow("@path, with project file") << "somedir/@path" << true << true; + QTest::newRow("@path, without project file") << "somedir/@path" << false << false; +} + +void TestBlackbox::buildDirPlaceholders() +{ + QFETCH(QString, buildDir); + QFETCH(bool, setProjectFile); + QFETCH(bool, successExpected); + + const QString projectDir = testDataDir + "/build-dir-placeholders"; + rmDirR(projectDir); + QVERIFY(QDir().mkpath(projectDir)); + QDir::setCurrent(projectDir); + QFile projectFile("build-dir-placeholders.qbs"); + QVERIFY(projectFile.open(QIODevice::WriteOnly)); + projectFile.write("Product {\n}\n"); + projectFile.flush(); + rmDirR(relativeBuildDir()); + QbsRunParameters params; + params.buildDirectory = buildDir; + if (setProjectFile) { + params.arguments << "-f" + << "build-dir-placeholders.qbs"; + } + params.expectFailure = !successExpected; + QCOMPARE(runQbs(params) == 0, successExpected); +} + void TestBlackbox::buildEnvChange() { QDir::setCurrent(testDataDir + "/buildenv-change"); @@ -1055,22 +1094,68 @@ void TestBlackbox::dependencyScanningLoop() void TestBlackbox::deprecatedProperty() { + QFETCH(QString, version); + QFETCH(QString, mode); + QFETCH(bool, expiringWarning); + QFETCH(bool, expiringError); + QDir::setCurrent(testDataDir + "/deprecated-property"); QbsRunParameters params(QStringList("-q")); params.expectFailure = true; + params.environment.insert("REMOVAL_VERSION", version); + if (!mode.isEmpty()) + params.arguments << "--deprecation-warnings" << mode; QVERIFY(runQbs(params) != 0); m_qbsStderr = QDir::fromNativeSeparators(QString::fromLocal8Bit(m_qbsStderr)).toLocal8Bit(); - QVERIFY2(m_qbsStderr.contains("deprecated-property.qbs:6:24 The property 'oldProp' is " - "deprecated and will be removed in Qbs 99.9.0."), m_qbsStderr.constData()); - QVERIFY2(m_qbsStderr.contains("deprecated-property.qbs:7:28 The property 'veryOldProp' can no " - "longer be used. It was removed in Qbs 1.3.0."), m_qbsStderr.constData()); + const bool hasExpiringWarning = m_qbsStderr.contains(QByteArray( + "deprecated-property.qbs:4:29 The property 'expiringProp' is " + "deprecated and will be removed in Qbs ") + version.toLocal8Bit()); + QVERIFY2(expiringWarning == hasExpiringWarning, m_qbsStderr.constData()); + const bool hasRemovedOutput = m_qbsStderr.contains( + "deprecated-property.qbs:5:28 The property 'veryOldProp' can no " + "longer be used. It was removed in Qbs 1.3.0."); + QVERIFY2(hasRemovedOutput == !expiringError, m_qbsStderr.constData()); QVERIFY2(m_qbsStderr.contains("Property 'forgottenProp' was scheduled for removal in version " "1.8.0, but is still present."), m_qbsStderr.constData()); QVERIFY2(m_qbsStderr.contains("themodule/m.qbs:22:5 Removal version for 'forgottenProp' " "specified here."), m_qbsStderr.constData()); - QVERIFY2(m_qbsStderr.count("Use newProp instead.") == 2, m_qbsStderr.constData()); - QVERIFY2(m_qbsStderr.count("is deprecated") == 1, m_qbsStderr.constData()); - QVERIFY2(m_qbsStderr.count("was removed") == 1, m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.count("Use newProp instead.") == 1 + + int(expiringWarning && !expiringError), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.count("is deprecated") == int(expiringWarning), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.count("was removed") == int(!expiringError), m_qbsStderr.constData()); +} + +void TestBlackbox::deprecatedProperty_data() +{ + QTest::addColumn<QString>("version"); + QTest::addColumn<QString>("mode"); + QTest::addColumn<bool>("expiringWarning"); + QTest::addColumn<bool>("expiringError"); + + const auto current = QVersionNumber::fromString(QBS_VERSION); + const QString next = QVersionNumber(current.majorVersion(), current.minorVersion() + 1) + .toString(); + const QString nextNext = QVersionNumber(current.majorVersion(), current.minorVersion() + 2) + .toString(); + const QString nextMajor = QVersionNumber(current.majorVersion() + 1).toString(); + + QTest::newRow("default/next") << next << QString() << true << false; + QTest::newRow("default/nextnext") << nextNext << QString() << false << false; + QTest::newRow("default/nextmajor") << nextMajor << QString() << true << false; + QTest::newRow("error/next") << next << QString("error") << true << true; + QTest::newRow("error/nextnext") << nextNext << QString("error") << true << true; + QTest::newRow("error/nextmajor") << nextMajor << QString("error") << true << true; + QTest::newRow("on/next") << next << QString("on") << true << false; + QTest::newRow("on/nextnext") << nextNext << QString("on") << true << false; + QTest::newRow("on/nextmajor") << nextMajor << QString("on") << true << false; + QTest::newRow("before-removal/next") << next << QString("before-removal") << true << false; + QTest::newRow("before-removal/nextnext") << nextNext << QString("before-removal") + << false << false; + QTest::newRow("before-removal/nextmajor") << nextMajor << QString("before-removal") + << true << false; + QTest::newRow("off/next") << next << QString("off") << false << false; + QTest::newRow("off/nextnext") << nextNext << QString("off") << false << false; + QTest::newRow("off/nextmajor") << nextMajor << QString("off") << false << false; } void TestBlackbox::disappearedProfile() @@ -1179,6 +1264,13 @@ void TestBlackbox::discardUnusedData_data() QTest::newRow("default") << QString() << true; } +void TestBlackbox::dotDotPcFile() +{ + QDir::setCurrent(testDataDir + "/dot-dot-pc-file"); + + QCOMPARE(runQbs(), 0); +} + void TestBlackbox::driverLinkerFlags() { QDir::setCurrent(testDataDir + QLatin1String("/driver-linker-flags")); @@ -1333,9 +1425,9 @@ void TestBlackbox::variantSuffix_data() std::make_pair(QString("unix"), QStringList())}); } -static bool waitForProcessSuccess(QProcess &p) +static bool waitForProcessSuccess(QProcess &p, int msecs = 30000) { - if (!p.waitForStarted() || !p.waitForFinished()) { + if (!p.waitForStarted(msecs) || !p.waitForFinished(msecs)) { qDebug() << p.errorString(); return false; } @@ -1540,14 +1632,14 @@ void TestBlackbox::versionCheck_data() void TestBlackbox::versionScript() { - const SettingsPtr s = settings(); - Profile buildProfile(profileName(), s.get()); - QStringList toolchain = profileToolchain(buildProfile); - if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux) - QSKIP("version script test only applies to Linux"); QDir::setCurrent(testDataDir + "/versionscript"); - QCOMPARE(runQbs(QbsRunParameters(QStringList("-q") - << ("qbs.installRoot:" + QDir::currentPath()))), 0); + QCOMPARE(runQbs(QbsRunParameters("resolve", {"qbs.installRoot:" + QDir::currentPath()})), 0); + const bool isLinuxGcc = m_qbsStdout.contains("is gcc for Linux: true"); + const bool isNotLinuxGcc = m_qbsStdout.contains("is gcc for Linux: false"); + if (isNotLinuxGcc) + QSKIP("version script test only applies to Linux"); + QVERIFY(isLinuxGcc); + QCOMPARE(runQbs(QbsRunParameters(QStringList("-q"))), 0); const QString output = QString::fromLocal8Bit(m_qbsStderr); const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"), QRegularExpression::DotMatchesEverythingOption); @@ -1665,7 +1757,7 @@ void TestBlackbox::clean() QVERIFY(!QFile(appExeFilePath).exists()); QVERIFY(!QFile(depObjectFilePath).exists()); QVERIFY(!QFile(depLibFilePath).exists()); - for (const QString &symLink : qAsConst(symlinks)) + for (const QString &symLink : std::as_const(symlinks)) QVERIFY2(!symlinkExists(symLink), qPrintable(symLink)); // Remove all, with a forced re-resolve in between. @@ -1684,7 +1776,7 @@ void TestBlackbox::clean() QVERIFY(!QFile(appExeFilePath).exists()); QVERIFY(!QFile(depObjectFilePath).exists()); QVERIFY(!QFile(depLibFilePath).exists()); - for (const QString &symLink : qAsConst(symlinks)) + for (const QString &symLink : std::as_const(symlinks)) QVERIFY2(!symlinkExists(symLink), qPrintable(symLink)); // Dry run. @@ -1696,7 +1788,7 @@ void TestBlackbox::clean() QVERIFY(regularFileExists(appExeFilePath)); QVERIFY(regularFileExists(depObjectFilePath)); QVERIFY(regularFileExists(depLibFilePath)); - for (const QString &symLink : qAsConst(symlinks)) + for (const QString &symLink : std::as_const(symlinks)) QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); // Product-wise, dependency only. @@ -1710,7 +1802,7 @@ void TestBlackbox::clean() QVERIFY(regularFileExists(appExeFilePath)); QVERIFY(!QFile(depObjectFilePath).exists()); QVERIFY(!QFile(depLibFilePath).exists()); - for (const QString &symLink : qAsConst(symlinks)) + for (const QString &symLink : std::as_const(symlinks)) QVERIFY2(!symlinkExists(symLink), qPrintable(symLink)); // Product-wise, dependent product only. @@ -1724,17 +1816,10 @@ void TestBlackbox::clean() QVERIFY(!QFile(appExeFilePath).exists()); QVERIFY(regularFileExists(depObjectFilePath)); QVERIFY(regularFileExists(depLibFilePath)); - for (const QString &symLink : qAsConst(symlinks)) + for (const QString &symLink : std::as_const(symlinks)) QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); } -void TestBlackbox::concurrentExecutor() -{ - QDir::setCurrent(testDataDir + "/concurrent-executor"); - QCOMPARE(runQbs(QStringList() << "-j" << "2"), 0); - QVERIFY2(!m_qbsStderr.contains("ASSERT"), m_qbsStderr.constData()); -} - void TestBlackbox::conditionalExport() { QDir::setCurrent(testDataDir + "/conditional-export"); @@ -1914,6 +1999,42 @@ void TestBlackbox::conanfileProbe() QCOMPARE(actualResults, expectedResults); } +void TestBlackbox::conflictingPropertyValues_data() +{ + QTest::addColumn<bool>("overrideInProduct"); + QTest::newRow("don't override in product") << false; + QTest::newRow("override in product") << true; +} + +void TestBlackbox::conflictingPropertyValues() +{ + QFETCH(bool, overrideInProduct); + + QDir::setCurrent(testDataDir + "/conflicting-property-values"); + if (overrideInProduct) + REPLACE_IN_FILE("conflicting-property-values.qbs", "// low.prop: name", "low.prop: name"); + else + REPLACE_IN_FILE("conflicting-property-values.qbs", "low.prop: name", "// low.prop: name"); + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(QString("resolve")), 0); + if (overrideInProduct) { + // Binding in product itself overrides everything else, module-level conflicts + // are irrelevant. + QVERIFY2(m_qbsStdout.contains("final prop value: toplevel"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + } else { + // Only the conflicts in the highest-level modules are reported, lower-level conflicts + // are irrelevant. + // prop2 does not cause a conflict, because the values are the same. + QVERIFY2(m_qbsStdout.contains("final prop value: highest"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStderr.contains("Conflicting scalar values for property 'prop'"), + m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.count("values.qbs") == 2, m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("values.qbs:20:23"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("values.qbs:30:23"), m_qbsStderr.constData()); + } +} + void TestBlackbox::cpuFeatures() { QDir::setCurrent(testDataDir + "/cpu-features"); @@ -1945,6 +2066,13 @@ void TestBlackbox::cpuFeatures() } } +void TestBlackbox::dateProperty() +{ + QDir::setCurrent(testDataDir + "/date-property"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("The stored date was 1999-12-31"), m_qbsStdout.constData()); +} + void TestBlackbox::renameDependency() { QDir::setCurrent(testDataDir + "/renameDependency"); @@ -1978,10 +2106,13 @@ void TestBlackbox::separateDebugInfo() const bool isDarwin = m_qbsStdout.contains("is darwin: yes"); const bool isNotDarwin = m_qbsStdout.contains("is darwin: no"); QVERIFY(isDarwin != isNotDarwin); + const bool isGcc = m_qbsStdout.contains("is gcc: yes"); + const bool isNotGcc = m_qbsStdout.contains("is gcc: no"); + QVERIFY(isGcc != isNotGcc); + const bool isMsvc = m_qbsStdout.contains("is msvc: yes"); + const bool isNotMsvc = m_qbsStdout.contains("is msvc: no"); + QVERIFY(isMsvc != isNotMsvc); - const SettingsPtr s = settings(); - Profile buildProfile(profileName(), s.get()); - QStringList toolchain = profileToolchain(buildProfile); if (isDarwin) { QVERIFY(directoryExists(relativeProductBuildDir("app1") + "/app1.app.dSYM")); QVERIFY(regularFileExists(relativeProductBuildDir("app1") @@ -2061,7 +2192,7 @@ void TestBlackbox::separateDebugInfo() + "/bar4.bundle.dSYM/Contents/Resources/DWARF") .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); QVERIFY(regularFileExists(relativeProductBuildDir("bar5") + "/bar5.bundle.dwarf")); - } else if (toolchain.contains("gcc")) { + } else if (isGcc) { const QString exeSuffix = isWindows ? ".exe" : ""; const QString dllPrefix = isWindows ? "" : "lib"; const QString dllSuffix = isWindows ? ".dll" : ".so"; @@ -2075,7 +2206,7 @@ void TestBlackbox::separateDebugInfo() + '/' + dllPrefix + "bar1" + dllSuffix + ".debug")); QVERIFY(!QFile::exists(relativeProductBuildDir("bar2") + '/' + dllPrefix + "bar2" + dllSuffix + ".debug")); - } else if (toolchain.contains("msvc")) { + } else if (isMsvc) { QVERIFY(QFile::exists(relativeProductBuildDir("app1") + "/app1.pdb")); QVERIFY(QFile::exists(relativeProductBuildDir("foo1") + "/foo1.pdb")); QVERIFY(QFile::exists(relativeProductBuildDir("bar1") + "/bar1.pdb")); @@ -2132,6 +2263,9 @@ void TestBlackbox::trackExternalProductChanges() QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + const bool isGcc = m_qbsStdout.contains("is gcc: true"); + const bool isNotGcc = m_qbsStdout.contains("is gcc: false"); + QbsRunParameters params; params.environment.insert("QBS_TEST_PULL_IN_FILE_VIA_ENV", "1"); QCOMPARE(runQbs(params), 0); @@ -2181,12 +2315,11 @@ void TestBlackbox::trackExternalProductChanges() QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); QVERIFY(m_qbsStdout.contains("compiling fileExists.cpp")); + if (isNotGcc) + QSKIP("The remainder of this test requires a GCC-like toolchain"); + QVERIFY(isGcc); + rmDirR(relativeBuildDir()); - const SettingsPtr s = settings(); - const Profile profile(profileName(), s.get()); - const QStringList toolchainTypes = profileToolchain(profile); - if (!toolchainTypes.contains("gcc")) - QSKIP("Need GCC-like compiler to run this test"); params.environment = QbsRunParameters::defaultEnvironment(); params.environment.insert("INCLUDE_PATH_TEST", "1"); params.expectFailure = true; @@ -2451,8 +2584,8 @@ void TestBlackbox::referenceErrorInExport() QbsRunParameters params; params.expectFailure = true; QVERIFY(runQbs(params) != 0); - QVERIFY(m_qbsStderr.contains( - "referenceErrorInExport.qbs:15:12 ReferenceError: Can't find variable: includePaths")); + QVERIFY2(m_qbsStderr.contains("referenceErrorInExport.qbs:5:27 'includePaths' is not defined"), + m_qbsStderr.constData()); } void TestBlackbox::removeDuplicateLibraries_data() @@ -2479,22 +2612,21 @@ void TestBlackbox::removeDuplicateLibraries() void TestBlackbox::reproducibleBuild() { - const SettingsPtr s = settings(); - const Profile profile(profileName(), s.get()); - const QStringList toolchains = profileToolchain(profile); - if (!toolchains.contains("gcc")) - QSKIP("reproducible builds only supported for gcc"); - if (toolchains.contains("clang")) - QSKIP("reproducible builds are not supported for clang"); - QFETCH(bool, reproducible); QDir::setCurrent(testDataDir + "/reproducible-build"); - QbsRunParameters params; + QbsRunParameters params("resolve"); params.arguments << QString("modules.cpp.enableReproducibleBuilds:") + (reproducible ? "true" : "false"); rmDirR(relativeBuildDir()); QCOMPARE(runQbs(params), 0); + const bool isGcc = m_qbsStdout.contains("is gcc: true"); + const bool isNotGcc = m_qbsStdout.contains("is gcc: false"); + if (isNotGcc) + QSKIP("reproducible builds only supported for gcc"); + QVERIFY(isGcc); + + QCOMPARE(runQbs(), 0); QFile object(relativeProductBuildDir("the product") + '/' + inputDirHash(".") + '/' + objectFileName("file1.cpp", profileName())); QVERIFY2(object.open(QIODevice::ReadOnly), qPrintable(object.fileName())); @@ -2502,6 +2634,7 @@ void TestBlackbox::reproducibleBuild() object.close(); QCOMPARE(runQbs(QbsRunParameters("clean")), 0); QVERIFY(!object.exists()); + params.command = "build"; QCOMPARE(runQbs(params), 0); if (reproducible) { QVERIFY(object.open(QIODevice::ReadOnly)); @@ -2563,6 +2696,32 @@ void TestBlackbox::retaggedOutputArtifact() QVERIFY2(!QFile::exists(a3), qPrintable(a3)); } +void TestBlackbox::rpathlinkDeduplication() +{ + QDir::setCurrent(testDataDir + "/rpathlink-deduplication"); + QbsRunParameters resolveParams{"resolve"}; + QCOMPARE(runQbs(resolveParams), 0); + const bool useRPathLink = m_qbsStdout.contains("useRPathLink: true"); + const bool dontUseRPathLink = m_qbsStdout.contains("useRPathLink: false"); + QVERIFY2(useRPathLink || dontUseRPathLink, m_qbsStdout); + if (dontUseRPathLink) + QSKIP("Only applies to toolchains that support rPathLink"); + const QString output = QString::fromLocal8Bit(m_qbsStdout); + const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*===(.*)===.*"), + QRegularExpression::DotMatchesEverythingOption); + const QRegularExpressionMatch match = pattern.match(output); + QVERIFY2(match.hasMatch(), qPrintable(output)); + QCOMPARE(pattern.captureCount(), 1); + const QString linkFlag = match.captured(1); + + QbsRunParameters buildParams; + buildParams.arguments = QStringList({"--command-echo-mode", "command-line"}); + QCOMPARE(runQbs(buildParams), 0); + // private DynamicLibraryA is a dependency for 2 other libs but should only appear once + const auto libDir = QFileInfo(relativeProductBuildDir("DynamicLibraryA")).absoluteFilePath(); + QCOMPARE(m_qbsStdout.count((linkFlag + libDir).toUtf8()), 1); +} + void TestBlackbox::ruleConditions() { QDir::setCurrent(testDataDir + "/ruleConditions"); @@ -2647,6 +2806,25 @@ void TestBlackbox::ruleWithNonRequiredInputs() QVERIFY2(m_qbsStdout.contains("generating"), m_qbsStdout.constData()); } +void TestBlackbox::runMultiplexed() +{ + QDir::setCurrent(testDataDir + "/run-multiplexed"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + + QbsRunParameters params("run"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + params.arguments = QStringList{"-p", "app"}; + QVERIFY(runQbs(params) != 0); + params.expectFailure = false; + params.arguments.last() = "app {\"buildVariant\":\"debug\"}"; + QCOMPARE(runQbs(params), 0); + params.arguments.last() = "app {\"buildVariant\":\"release\"}"; + QCOMPARE(runQbs(params), 0); +} + void TestBlackbox::sanitizer_data() { QTest::addColumn<QString>("sanitizer"); @@ -3051,20 +3229,35 @@ void TestBlackbox::pathProbe() QVERIFY2(m_qbsStderr.contains("Probe failed to find files"), m_qbsStderr); } +void TestBlackbox::pathListInProbe() +{ + QDir::setCurrent(testDataDir + "/path-list-in-probe"); + QCOMPARE(runQbs(), 0); +} + void TestBlackbox::pchChangeTracking() { QDir::setCurrent(testDataDir + "/pch-change-tracking"); - QCOMPARE(runQbs(), 0); + bool success = runQbs() == 0; + if (!success && m_qbsStderr.contains("mingw32_gt_pch_use_address")) + QSKIP("https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91440"); + QVERIFY(success); QVERIFY(m_qbsStdout.contains("precompiling pch.h (cpp)")); WAIT_FOR_NEW_TIMESTAMP(); touch("header1.h"); - QCOMPARE(runQbs(), 0); + success = runQbs() == 0; + if (!success && m_qbsStderr.contains("mingw32_gt_pch_use_address")) + QSKIP("https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91440"); + QVERIFY(success); QVERIFY(m_qbsStdout.contains("precompiling pch.h (cpp)")); QVERIFY(m_qbsStdout.contains("compiling header2.cpp")); QVERIFY(m_qbsStdout.contains("compiling main.cpp")); WAIT_FOR_NEW_TIMESTAMP(); touch("header2.h"); - QCOMPARE(runQbs(), 0); + success = runQbs() == 0; + if (!success && m_qbsStderr.contains("mingw32_gt_pch_use_address")) + QSKIP("https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91440"); + QVERIFY(success); QVERIFY2(!m_qbsStdout.contains("precompiling pch.h (cpp)"), m_qbsStdout.constData()); } @@ -3194,13 +3387,19 @@ void TestBlackbox::pluginDependency() void TestBlackbox::precompiledAndPrefixHeaders() { QDir::setCurrent(testDataDir + "/precompiled-and-prefix-headers"); - QCOMPARE(runQbs(), 0); + const bool success = runQbs() == 0; + if (!success && m_qbsStderr.contains("mingw32_gt_pch_use_address")) + QSKIP("https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91440"); + QVERIFY(success); } void TestBlackbox::precompiledHeaderAndRedefine() { QDir::setCurrent(testDataDir + "/precompiled-headers-and-redefine"); - QCOMPARE(runQbs(), 0); + const bool success = runQbs() == 0; + if (!success && m_qbsStderr.contains("mingw32_gt_pch_use_address")) + QSKIP("https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91440"); + QVERIFY(success); } void TestBlackbox::preventFloatingPointValues() @@ -3333,23 +3532,6 @@ void TestBlackbox::probeInExportedModule() QVERIFY2(m_qbsStdout.contains("listProp: myother,my"), m_qbsStdout.constData()); } -void TestBlackbox::probeInModuleProvider() -{ - QDir::setCurrent(testDataDir + "/probe-in-module-provider"); - - QbsRunParameters params; - params.command = "build"; - params.arguments << "--force-probe-execution"; - QCOMPARE(runQbs(params), 0); - QVERIFY2(m_qbsStdout.contains("Running probe"), m_qbsStdout); - QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout); - WAIT_FOR_NEW_TIMESTAMP(); - touch("probe-in-module-provider.qbs"); - QCOMPARE(runQbs(), 0); - QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout); - QVERIFY2(!m_qbsStdout.contains("Running probe"), m_qbsStdout); -} - void TestBlackbox::probesAndArrayProperties() { QDir::setCurrent(testDataDir + "/probes-and-array-properties"); @@ -3369,13 +3551,6 @@ void TestBlackbox::productProperties() QVERIFY(regularFileExists(relativeExecutableFilePath("blubb_user"))); } -void TestBlackbox::propertyAssignmentOnNonPresentModule() -{ - QDir::setCurrent(testDataDir + "/property-assignment-on-non-present-module"); - QCOMPARE(runQbs(), 0); - QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); -} - void TestBlackbox::propertyAssignmentInFailedModule() { QDir::setCurrent(testDataDir + "/property-assignment-in-failed-module"); @@ -3385,7 +3560,7 @@ void TestBlackbox::propertyAssignmentInFailedModule() QVERIFY(runQbs(failParams) != 0); QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:true"))), 0); QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); - QEXPECT_FAIL(nullptr, "circular dependency between module merging and validation", Continue); + failParams.expectFailure = false; QCOMPARE(runQbs(failParams), 0); } @@ -3714,7 +3889,7 @@ void TestBlackbox::emptyProfile() QDir::setCurrent(testDataDir + "/empty-profile"); const SettingsPtr s = settings(); - const Profile buildProfile(profileName(), s.get()); + const qbs::Profile buildProfile(profileName(), s.get()); bool isMsvc = false; auto toolchainType = buildProfile.value(QStringLiteral("qbs.toolchainType")).toString(); QbsRunParameters params; @@ -3735,7 +3910,7 @@ void TestBlackbox::emptyProfile() QDir::toNativeSeparators( buildProfile.value(QStringLiteral("cpp.toolchainInstallPath")).toString()); auto paths = params.environment.value(QStringLiteral("PATH")) - .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts); if (!tcPath.isEmpty() && !paths.contains(tcPath)) { paths.prepend(tcPath); params.environment.insert( @@ -3835,15 +4010,16 @@ void TestBlackbox::errorInfo() void TestBlackbox::escapedLinkerFlags() { - const SettingsPtr s = settings(); - const Profile buildProfile(profileName(), s.get()); - const QStringList toolchain = profileToolchain(buildProfile); - if (!toolchain.contains("gcc")) - QSKIP("escaped linker flags test only applies with gcc and GNU ld"); - if (targetOs() == HostOsInfo::HostOsMacos) - QSKIP("Does not apply on macOS"); QDir::setCurrent(testDataDir + "/escaped-linker-flags"); - QbsRunParameters params(QStringList("products.app.escapeLinkerFlags:false")); + QbsRunParameters params("resolve", QStringList("products.app.escapeLinkerFlags:false")); + QCOMPARE(runQbs(params), 0); + const bool isGcc = m_qbsStdout.contains("is gcc: true"); + const bool isNotGcc = m_qbsStdout.contains("is gcc: false"); + if (isNotGcc) + QSKIP("escaped linker flags test only applies on plain unix with gcc and GNU ld"); + QVERIFY(isGcc); + + params.command = "build"; QCOMPARE(runQbs(params), 0); params.command = "resolve"; params.arguments = QStringList() << "products.app.escapeLinkerFlags:true"; @@ -3900,26 +4076,24 @@ void TestBlackbox::exportedPropertyInDisabledProduct_data() void TestBlackbox::systemRunPaths() { - const SettingsPtr s = settings(); - const Profile buildProfile(profileName(), s.get()); - switch (targetOs()) { - case HostOsInfo::HostOsLinux: - case HostOsInfo::HostOsMacos: - case HostOsInfo::HostOsOtherUnix: - break; - default: - QSKIP("only applies on Unix"); - } - const QString lddFilePath = findExecutable(QStringList() << "ldd"); if (lddFilePath.isEmpty()) QSKIP("ldd not found"); + QDir::setCurrent(testDataDir + "/system-run-paths"); QFETCH(bool, setRunPaths); rmDirR(relativeBuildDir()); - QbsRunParameters params; + QbsRunParameters params("resolve"); params.arguments << QString("project.setRunPaths:") + (setRunPaths ? "true" : "false"); QCOMPARE(runQbs(params), 0); + const bool isUnix = m_qbsStdout.contains("is unix: true"); + const bool isNotUnix = m_qbsStdout.contains("is unix: false"); + if (isNotUnix) + QSKIP("only applies on Unix"); + QVERIFY(isUnix); + + params.command = "build"; + QCOMPARE(runQbs(params), 0); QProcess ldd; ldd.start(lddFilePath, QStringList() << relativeExecutableFilePath("app")); QVERIFY2(ldd.waitForStarted(), qPrintable(ldd.errorString())); @@ -3970,6 +4144,82 @@ void TestBlackbox::exportToOutsideSearchPath() m_qbsStderr.constData()); } +void TestBlackbox::exportsCMake() +{ + QFETCH(QStringList, arguments); + + QDir::setCurrent(testDataDir + "/exports-cmake"); + rmDirR(relativeBuildDir()); + QbsRunParameters findCMakeParams("resolve", {"-f", "find-cmake.qbs"}); + QCOMPARE(runQbs(findCMakeParams), 0); + const QString output = QString::fromLocal8Bit(m_qbsStdout); + const QRegularExpression pattern( + QRegularExpression::anchoredPattern(".*---(.*)---.*"), + QRegularExpression::DotMatchesEverythingOption); + const QRegularExpressionMatch match = pattern.match(output); + QVERIFY2(match.hasMatch(), qPrintable(output)); + QCOMPARE(pattern.captureCount(), 1); + const QString jsonString = match.captured(1); + const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); + const QJsonObject jsonData = jsonDoc.object(); + + rmDirR(relativeBuildDir()); + const QStringList exporterArgs{"-f", "exports-cmake.qbs"}; + QbsRunParameters exporterRunParams("build", exporterArgs); + exporterRunParams.arguments << arguments; + QCOMPARE(runQbs(exporterRunParams), 0); + + if (!jsonData.value(u"cmakeFound").toBool()) { + QSKIP("cmake is not installed"); + return; + } + + if (jsonData.value(u"crossCompiling").toBool()) { + QSKIP("test is not applicable with cross-compile toolchains"); + return; + } + + const auto cmakeFilePath = jsonData.value(u"cmakeFilePath").toString(); + QVERIFY(!cmakeFilePath.isEmpty()); + + const auto generator = jsonData.value(u"generator").toString(); + if (generator.isEmpty()) { + QSKIP("cannot detect cmake generator"); + return; + } + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + const auto buildEnv = jsonData.value(u"buildEnv").toObject(); + for (auto it = buildEnv.begin(), end = buildEnv.end(); it != end; ++it) { + env.insert(it.key(), it.value().toString()); + } + + const auto installPrefix = jsonData.value(u"installPrefix").toString(); + const auto cmakePrefixPath = QFileInfo(relativeBuildDir()).absoluteFilePath() + "/install-root/" + + installPrefix + "/lib/cmake"; + const auto sourceDirectory = testDataDir + "/exports-cmake/cmake"; + QProcess configure; + configure.setProcessEnvironment(env); + configure.setWorkingDirectory(sourceDirectory); + configure.start( + cmakeFilePath, {".", "-DCMAKE_PREFIX_PATH=" + cmakePrefixPath, "-G" + generator}); + QVERIFY(waitForProcessSuccess(configure, 120000)); + + QProcess build; + build.setProcessEnvironment(env); + build.setWorkingDirectory(sourceDirectory); + build.start(cmakeFilePath, QStringList{"--build", "."}); + QVERIFY(waitForProcessSuccess(build)); +} + +void TestBlackbox::exportsCMake_data() +{ + QTest::addColumn<QStringList>("arguments"); + QTest::newRow("dynamic lib") << QStringList("project.isStatic: false"); + QTest::newRow("static lib") << QStringList("project.isStatic: true"); + QTest::newRow("framework") << QStringList("project.isBundle: true"); +} + void TestBlackbox::exportsPkgconfig() { QDir::setCurrent(testDataDir + "/exports-pkgconfig"); @@ -4027,7 +4277,8 @@ void TestBlackbox::exportsQbs() paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-profile"; paramsExternalBuild.expectFailure = true; QVERIFY(runQbs(paramsExternalBuild) != 0); - QVERIFY2(m_qbsStderr.contains("MyLib could not be loaded"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("Dependency 'MyLib' not found for product 'consumer'"), + m_qbsStderr.constData()); // Removing the condition from the generated module leaves us with two conflicting // candidates. @@ -4338,7 +4589,7 @@ void TestBlackbox::installPackage() cleanOutputLines.push_back(trimmedLine); } QCOMPARE(cleanOutputLines.size(), 3); - for (const QByteArray &line : qAsConst(cleanOutputLines)) { + for (const QByteArray &line : std::as_const(cleanOutputLines)) { QVERIFY2(line.contains("public_tool") || line.contains("mylib") || line.contains("lib.h"), line.constData()); } @@ -4385,6 +4636,33 @@ void TestBlackbox::installTree() QVERIFY(QFile::exists(installRoot + "content/subdir2/baz.txt")); } +void TestBlackbox::invalidArtifactPath_data() +{ + QTest::addColumn<QString>("baseDir"); + QTest::addColumn<bool>("isValid"); + + QTest::newRow("inside, normal case") << "subdir" << true; + QTest::newRow("inside, build dir 1") << "project.buildDirectory" << true; + QTest::newRow("inside, build dir 2") << "subdir/.." << true; + QTest::newRow("outside, absolute") << "/tmp" << false; + QTest::newRow("outside, relative 1") << "../../" << false; + QTest::newRow("outside, relative 2") << "subdir/../../.." << false; +} + +void TestBlackbox::invalidArtifactPath() +{ + QFETCH(QString, baseDir); + QFETCH(bool, isValid); + + rmDirR(relativeBuildDir()); + QDir::setCurrent(testDataDir + "/invalid-artifact-path"); + QbsRunParameters params(QStringList("project.artifactDir:" + baseDir)); + params.expectFailure = !isValid; + QCOMPARE(runQbs(params) == 0, isValid); + if (!isValid) + QVERIFY2(m_qbsStderr.contains("outside of build directory"), m_qbsStderr.constData()); +} + void TestBlackbox::invalidCommandProperty_data() { QTest::addColumn<QString>("errorType"); @@ -4415,7 +4693,7 @@ void TestBlackbox::invalidLibraryNames() QbsRunParameters params(QStringList("project.valueIndex:" + index)); params.expectFailure = !success; QCOMPARE(runQbs(params) == 0, success); - for (const QString &diag : qAsConst(diagnostics)) + for (const QString &diag : std::as_const(diagnostics)) QVERIFY2(m_qbsStderr.contains(diag.toLocal8Bit()), m_qbsStderr.constData()); } @@ -4476,7 +4754,7 @@ void TestBlackbox::cli() QCOMPARE(status, 0); const SettingsPtr s = settings(); - Profile p("qbs_autotests-cli", s.get()); + qbs::Profile p("qbs_autotests-cli", s.get()); const QStringList toolchain = profileToolchain(p); if (!p.exists() || !(toolchain.contains("dotnet") || toolchain.contains("mono"))) QSKIP("No suitable Common Language Infrastructure test profile"); @@ -4744,6 +5022,10 @@ void TestBlackbox::jsExtensionsBinaryFile() QCOMPARE(data.at(5), char(0x05)); QCOMPARE(data.at(6), char(0x06)); QCOMPARE(data.at(7), char(0xFF)); + QFile destination2("destination2.dat"); + QVERIFY(destination2.exists()); + QVERIFY(destination2.open(QIODevice::ReadOnly)); + QCOMPARE(destination2.readAll(), data); } void TestBlackbox::lastModuleCandidateBroken() @@ -4752,7 +5034,8 @@ void TestBlackbox::lastModuleCandidateBroken() QbsRunParameters params; params.expectFailure = true; QVERIFY(runQbs(params) != 0); - QVERIFY2(m_qbsStderr.contains("Module Foo could not be loaded"), m_qbsStderr); + QVERIFY2(m_qbsStderr.contains("Dependency 'Foo' not found for product " + "'last-module-candidate-broken'"), m_qbsStderr); } void TestBlackbox::ld() @@ -4858,6 +5141,7 @@ void TestBlackbox::linkerVariant_data() QTest::newRow("default") << QString(); QTest::newRow("bfd") << QString("bfd"); QTest::newRow("gold") << QString("gold"); + QTest::newRow("mold") << QString("mold"); } void TestBlackbox::linkerVariant() @@ -4991,22 +5275,20 @@ void TestBlackbox::lexyaccOutputs_data() void TestBlackbox::linkerLibraryDuplicates() { - const SettingsPtr s = settings(); - Profile buildProfile(profileName(), s.get()); - QStringList toolchain = profileToolchain(buildProfile); - if (!toolchain.contains("gcc")) - QSKIP("linkerLibraryDuplicates test only applies to GCC toolchain"); - QDir::setCurrent(testDataDir + "/linker-library-duplicates"); rmDirR(relativeBuildDir()); - QFETCH(QString, removeDuplicateLibraries); QStringList runParams; - if (!removeDuplicateLibraries.isEmpty()) { + if (!removeDuplicateLibraries.isEmpty()) runParams.append(removeDuplicateLibraries); - } QCOMPARE(runQbs(QbsRunParameters("resolve", runParams)), 0); + const bool isGcc = m_qbsStdout.contains("is gcc: true"); + const bool isNotGcc = m_qbsStdout.contains("is gcc: false"); + if (isNotGcc) + QSKIP("linkerLibraryDuplicates test only applies to GCC toolchain"); + QVERIFY(isGcc); + QCOMPARE(runQbs(QStringList { "--command-echo-mode", "command-line" }), 0); const QByteArrayList output = m_qbsStdout.split('\n'); QByteArray linkLine; @@ -5071,20 +5353,20 @@ void TestBlackbox::linkerLibraryDuplicates_data() void TestBlackbox::linkerScripts() { - const SettingsPtr s = settings(); - Profile buildProfile(profileName(), s.get()); - QStringList toolchain = profileToolchain(buildProfile); - if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux) - QSKIP("linker script test only applies to Linux "); - - QbsRunParameters runParams(QStringList() -// << "--log-level" << "debug" - << ("qbs.installRoot:" + QDir::currentPath())); const QString sourceDir = QDir::cleanPath(testDataDir + "/linkerscripts"); + QbsRunParameters runParams("resolve", {"qbs.installRoot:" + QDir::currentPath()}); runParams.buildDirectory = sourceDir + "/build"; runParams.workingDir = sourceDir; QCOMPARE(runQbs(runParams), 0); + const bool isGcc = m_qbsStdout.contains("is Linux gcc: true"); + const bool isNotGcc = m_qbsStdout.contains("is Linux gcc: false"); + if (isNotGcc) + QSKIP("linker script test only applies to Linux"); + QVERIFY(isGcc); + + runParams.command = "build"; + QCOMPARE(runQbs(runParams), 0); const QString output = QString::fromLocal8Bit(m_qbsStderr); const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"), QRegularExpression::DotMatchesEverythingOption); @@ -5228,16 +5510,6 @@ void TestBlackbox::require() QCOMPARE(runQbs(), 0); } -void TestBlackbox::requireDeprecated() -{ - QDir::setCurrent(testDataDir + "/require-deprecated"); - QCOMPARE(runQbs(), 0); - QVERIFY2(m_qbsStderr.contains("loadExtension() function is deprecated"), - m_qbsStderr.constData()); - QVERIFY2(m_qbsStderr.contains("loadFile() function is deprecated"), - m_qbsStderr.constData()); -} - void TestBlackbox::rescueTransformerData() { QDir::setCurrent(testDataDir + "/rescue-transformer-data"); @@ -5522,11 +5794,7 @@ void TestBlackbox::propertyPrecedence() switchProfileContents(profile.p, s.get(), false); switchFileContents(nonleafFile, true); QCOMPARE(runQbs(resolveParams), 0); - QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at") - && m_qbsStderr.contains("nonleaf.qbs:4:22") - && m_qbsStderr.contains("dep.qbs:6:26"), - m_qbsStderr.constData()); - QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); QCOMPARE(runQbs(params), 0); QVERIFY2(m_qbsStdout.contains("scalar prop: export\n") && m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"leaf\"]\n"), m_qbsStdout.constData()); @@ -5534,10 +5802,7 @@ void TestBlackbox::propertyPrecedence() // Case 8: [cmdline=0,prod=0,export=1,nonleaf=1,profile=1] switchProfileContents(profile.p, s.get(), true); QCOMPARE(runQbs(resolveParams), 0); - QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at") - && m_qbsStderr.contains("nonleaf.qbs:4:22") - && m_qbsStderr.contains("dep.qbs:6:26"), - m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); QCOMPARE(runQbs(params), 0); QVERIFY2(m_qbsStdout.contains("scalar prop: export\n") && m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"profile\"]\n"), @@ -5859,22 +6124,19 @@ void TestBlackbox::protobuf_data() QTest::addColumn<QStringList>("properties"); QTest::addColumn<bool>("hasModules"); QTest::addColumn<bool>("successExpected"); - QTest::newRow("cpp") << QString("addressbook_cpp.qbs") << QStringList() << false << true; QTest::newRow("cpp-pkgconfig") << QString("addressbook_cpp.qbs") - << QStringList("project.qbsModuleProviders:qbspkgconfig") - << true - << true; + << QStringList({"project.qbsModuleProviders:qbspkgconfig"}) << true << true; QTest::newRow("objc") << QString("addressbook_objc.qbs") << QStringList() << false << true; QTest::newRow("nanopb") << QString("addressbook_nanopb.qbs") << QStringList() << false << true; - QTest::newRow("import") << QString("import.qbs") << QStringList() << false << true; + QTest::newRow("import") << QString("import.qbs") << QStringList() << true << true; QTest::newRow("missing import dir") << QString("needs-import-dir.qbs") - << QStringList() << false << false; + << QStringList() << true << false; QTest::newRow("provided import dir") << QString("needs-import-dir.qbs") - << QStringList("products.app.theImportDir:subdir") << false << true; + << QStringList("products.app.theImportDir:subdir") << true << true; QTest::newRow("create proto library") - << QString("create-proto-library.qbs") << QStringList() << false << true; + << QString("create-proto-library.qbs") << QStringList() << true << true; } void TestBlackbox::protobuf() @@ -5925,19 +6187,6 @@ void TestBlackbox::protobufLibraryInstall() QFileInfo::exists(installRootInclude + "/hello/world.pb.h")); } -// Tests whether it is possible to set providers properties in a Product or from command-line -void TestBlackbox::providersProperties() -{ - QDir::setCurrent(testDataDir + "/providers-properties"); - - QbsRunParameters params("build"); - params.arguments = QStringList("moduleProviders.provider_b.someProp: \"first,second\""); - QCOMPARE(runQbs(params), 0); - QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.listProp: [\"someValue\"]"), m_qbsStdout); - QVERIFY2(m_qbsStdout.contains( - "p.qbsothermodule.listProp: [\"first\",\"second\"]"), m_qbsStdout); -} - void TestBlackbox::pseudoMultiplexing() { // This is "pseudo-multiplexing" on all platforms that initialize qbs.architectures @@ -6047,6 +6296,10 @@ void TestBlackbox::qbsConfig() if (!canWriteToSystemSettings) { QVERIFY2(m_qbsStderr.contains("You do not have permission to write to that location."), m_qbsStderr.constData()); + } else { + // cleanup system settings + params.arguments = QStringList{"--system", "--unset", "key.subkey.scalar"}; + QCOMPARE(runQbs(params) == 0, canWriteToSystemSettings); } } @@ -6111,138 +6364,90 @@ void TestBlackbox::qbsConfigAddProfile_data() << QString("Profile properties must be key/value pairs"); } -// Tests whether it is possible to set qbsModuleProviders in Product and Project items -// and that the order of providers results in correct priority -void TestBlackbox::qbsModuleProviders() +void TestBlackbox::qbsConfigImport() { - QFETCH(QStringList, arguments); - QFETCH(QString, firstProp); - QFETCH(QString, secondProp); + QFETCH(QString, format); - QDir::setCurrent(testDataDir + "/qbs-module-providers"); + QDir::setCurrent(testDataDir + "/qbs-config-import-export"); + + QbsRunParameters params("config"); + QTemporaryDir settingsDir; + QVERIFY(settingsDir.isValid()); + const QStringList settingsDirArgs = QStringList{"--settings-dir", settingsDir.path()}; + params.arguments = settingsDirArgs; + params.arguments << "--import" << "config." + format; - QbsRunParameters params("resolve"); - params.arguments = arguments; QCOMPARE(runQbs(params), 0); - QVERIFY2(m_qbsStdout.contains(("p1.qbsmetatestmodule.prop: " + firstProp).toUtf8()), - m_qbsStdout); - QVERIFY2(m_qbsStdout.contains(("p1.qbsothermodule.prop: " + secondProp).toUtf8()), - m_qbsStdout); - QVERIFY2(m_qbsStdout.contains(("p2.qbsmetatestmodule.prop: " + firstProp).toUtf8()), - m_qbsStdout); - QVERIFY2(m_qbsStdout.contains(("p2.qbsothermodule.prop: " + secondProp).toUtf8()), - m_qbsStdout); + + params.arguments = settingsDirArgs; + params.arguments << "--list"; + QCOMPARE(runQbs(params), 0); + const QByteArray output = m_qbsStdout; + const auto lines = output.split('\n'); + QCOMPARE(lines.count(), 5); + QCOMPARE(lines[0], "group.key1: \"value1\""); + QCOMPARE(lines[1], "group.key2: \"value2\""); + QCOMPARE(lines[2], "key: \"value\""); + QCOMPARE(lines[3], "listKey: [\"valueOne\", \"valueTwo\"]"); + QCOMPARE(lines[4], ""); } -void TestBlackbox::qbsModuleProviders_data() -{ - QTest::addColumn<QStringList>("arguments"); - QTest::addColumn<QString>("firstProp"); - QTest::addColumn<QString>("secondProp"); - - QTest::newRow("default") << QStringList() << "from_provider_a" << "undefined"; - QTest::newRow("override") - << QStringList("projects.project.qbsModuleProviders:provider_b") - << "from_provider_b" - << "from_provider_b"; - QTest::newRow("override list a") - << QStringList("projects.project.qbsModuleProviders:provider_a,provider_b") - << "from_provider_a" - << "from_provider_b"; - QTest::newRow("override list b") - << QStringList("projects.project.qbsModuleProviders:provider_b,provider_a") - << "from_provider_b" - << "from_provider_b"; -} - -// Tests possible use-cases how to override providers from command-line -void TestBlackbox::qbsModuleProvidersCliOverride() +void TestBlackbox::qbsConfigImport_data() { - QFETCH(QStringList, arguments); - QFETCH(QString, propertyValue); - - QDir::setCurrent(testDataDir + "/qbs-module-providers-cli-override"); + QTest::addColumn<QString>("format"); - QbsRunParameters params("resolve"); - params.arguments = arguments; - QCOMPARE(runQbs(params), 0); - QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()), - m_qbsStdout); + QTest::newRow("text") << QStringLiteral("txt"); + QTest::newRow("json") << QStringLiteral("json"); } -void TestBlackbox::qbsModuleProvidersCliOverride_data() +void TestBlackbox::qbsConfigExport() { - QTest::addColumn<QStringList>("arguments"); - QTest::addColumn<QString>("propertyValue"); - - QTest::newRow("default") << QStringList() << "undefined"; - QTest::newRow("project-wide") - << QStringList("project.qbsModuleProviders:provider_a") - << "from_provider_a"; - QTest::newRow("concrete project") - << QStringList("projects.innerProject.qbsModuleProviders:provider_a") - << "from_provider_a"; - QTest::newRow("concrete product") - << QStringList("products.product.qbsModuleProviders:provider_a") - << "from_provider_a"; - QTest::newRow("concrete project override project-wide") - << QStringList({ - "project.qbsModuleProviders:provider_a", - "projects.innerProject.qbsModuleProviders:provider_b"}) - << "from_provider_b"; - QTest::newRow("concrete product override project-wide") - << QStringList({ - "project.qbsModuleProviders:provider_a", - "products.product.qbsModuleProviders:provider_b"}) - << "from_provider_b"; -} - -// Tests whether scoped providers can be used as named, i.e. new provider machinery -// is compatible with the old one -void TestBlackbox::qbsModuleProvidersCompatibility() -{ - QFETCH(QStringList, arguments); - QFETCH(QString, propertyValue); + QFETCH(QString, format); - QDir::setCurrent(testDataDir + "/qbs-module-providers-compatibility"); + QDir::setCurrent(testDataDir + "/qbs-config-import-export"); + + QbsRunParameters params("config"); + QTemporaryDir settingsDir; + const QString fileName = "config." + format; + const QString filePath = settingsDir.path() + "/" + fileName; + QVERIFY(settingsDir.isValid()); + const QStringList commonArgs = QStringList{"--settings-dir", settingsDir.path(), "--user"}; + + std::pair<QString, QString> values[] = { + {"key", "value"}, + {"listKey", "[\"valueOne\",\"valueTwo\"]"}, + {"group.key1", "value1"}, + {"group.key2", "value2"} + }; + + for (const auto &value: values) { + params.arguments = commonArgs; + params.arguments << value.first << value.second; + QCOMPARE(runQbs(params), 0); + } + + params.arguments = commonArgs; + params.arguments << "--export" << filePath; - QbsRunParameters params("resolve"); - params.arguments = arguments; QCOMPARE(runQbs(params), 0); - QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()), - m_qbsStdout); -} -void TestBlackbox::qbsModuleProvidersCompatibility_data() -{ - QTest::addColumn<QStringList>("arguments"); - QTest::addColumn<QString>("propertyValue"); + QVERIFY(QFileInfo(filePath).canonicalPath() != QFileInfo(fileName).canonicalPath()); - QTest::newRow("default") << QStringList() << "from_scoped_provider"; - QTest::newRow("scoped by name") << QStringList("project.qbsModuleProviders:qbsmetatestmodule") << "from_scoped_provider"; - QTest::newRow("named") << QStringList("project.qbsModuleProviders:named_provider") << "from_named_provider"; -} + QFile exported(filePath); + QFile expected(fileName); -void TestBlackbox::qbspkgconfigModuleProvider() -{ - QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider/libs"); + QVERIFY(exported.open(QIODevice::ReadOnly | QIODevice::Text)); + QVERIFY(expected.open(QIODevice::ReadOnly | QIODevice::Text)); - const auto commonParams = QbsRunParameters(QStringLiteral("install"), { - QStringLiteral("qbs.installPrefix:/usr/local"), - QStringLiteral("--install-root"), - QStringLiteral("install-root") - }); - auto dynamicParams = commonParams; - dynamicParams.arguments << "config:library" << "projects.libs.isBundle:false"; - QCOMPARE(runQbs(dynamicParams), 0); + QCOMPARE(exported.readAll(), expected.readAll()); +} - QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider"); +void TestBlackbox::qbsConfigExport_data() +{ + QTest::addColumn<QString>("format"); - QbsRunParameters params; - params.arguments - << "moduleProviders.qbspkgconfig.libDirs:libdir" - << "moduleProviders.qbspkgconfig.sysroot:" + QDir::currentPath() + "/libs/install-root"; - QCOMPARE(runQbs(params), 0); + QTest::newRow("text") << QStringLiteral("txt"); + QTest::newRow("json") << QStringLiteral("json"); } static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data) @@ -6281,6 +6486,137 @@ static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data) return QJsonDocument::fromJson(QByteArray::fromBase64(msg)).object(); } +static void sendSessionPacket(QProcess &sessionProc, const QJsonObject &message) +{ + const QByteArray data = QJsonDocument(message).toJson().toBase64(); + sessionProc.write("qbsmsg:"); + sessionProc.write(QByteArray::number(data.length())); + sessionProc.write("\n"); + sessionProc.write(data); +} + +void TestBlackbox::qbsLanguageServer_data() +{ + QTest::addColumn<QString>("request"); + QTest::addColumn<QString>("location"); + QTest::addColumn<QString>("insertLocation"); + QTest::addColumn<QString>("insertString"); + QTest::addColumn<QString>("expectedReply"); + + QTest::addRow("follow to module") << "--goto-def" + << (testDataDir + "/lsp/lsp.qbs:4:9") + << QString() << QString() + << (testDataDir + "/lsp/modules/m/m.qbs:1:1"); + QTest::addRow("follow to submodules") + << "--goto-def" + << (testDataDir + "/lsp/lsp.qbs:5:35") + << QString() << QString() + << ((testDataDir + "/lsp/modules/Prefix/m1/m1.qbs:1:1\n") + + (testDataDir + "/lsp/modules/Prefix/m2/m2.qbs:1:1\n") + + (testDataDir + "/lsp/modules/Prefix/m3/m3.qbs:1:1")); + QTest::addRow("follow to product") + << "--goto-def" << (testDataDir + "/lsp/lsp.qbs:9:19") << QString() << QString() + << (testDataDir + "/lsp/lsp.qbs:2:5"); + QTest::addRow("follow to module, non-invalidating insert") + << "--goto-def" << (testDataDir + "/lsp/lsp.qbs:4:9") << "5:9" + << QString("property bool dummy\n") << (testDataDir + "/lsp/modules/m/m.qbs:1:1"); + QTest::addRow("follow to module, invalidating insert") + << "--goto-def" << (testDataDir + "/lsp/lsp.qbs:4:9") << QString() + << QString("property bool dummy\n") << QString(); + QTest::addRow("completion: LHS, module prefix") + << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() << QString("P") + << QString("Prefix.m1\nPrefix.m2\nPrefix.m3"); + QTest::addRow("completion: LHS, module name") + << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() << QString("Prefix.m") + << QString("m1\nm2\nm3"); + QTest::addRow("completion: LHS, module property right after dot") + << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() + << QString("Prefix.m1.") << QString("p1 bool\np2 string\nx bool"); + QTest::addRow("completion: LHS, module property with identifier prefix") + << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() + << QString("Prefix.m1.p") << QString("p1 bool\np2 string"); + QTest::addRow("completion: simple RHS, module property") + << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() + << QString("property bool dummy: Prefix.m1.p") << QString("p1 bool\np2 string"); + QTest::addRow("completion: complex RHS, module property") + << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() + << QString("property bool dummy: { return Prefix.m1.p") << QString("p1 bool\np2 string"); +} + +void TestBlackbox::qbsLanguageServer() +{ + QFETCH(QString, request); + QFETCH(QString, location); + QFETCH(QString, insertLocation); + QFETCH(QString, insertString); + QFETCH(QString, expectedReply); + + QDir::setCurrent(testDataDir + "/lsp"); + QProcess sessionProc; + sessionProc.start(qbsExecutableFilePath, QStringList("session")); + + QVERIFY(sessionProc.waitForStarted()); + + QByteArray incomingData; + + // Wait for and verify hello packet. + QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData); + const QString socketPath = receivedMessage.value("lsp-socket").toString(); + QVERIFY(!socketPath.isEmpty()); + + // Resolve project. + QJsonObject resolveMessage; + resolveMessage.insert("type", "resolve-project"); + resolveMessage.insert("top-level-profile", profileName()); + resolveMessage.insert("project-file-path", QDir::currentPath() + "/lsp.qbs"); + resolveMessage.insert("build-root", QDir::currentPath()); + resolveMessage.insert("settings-directory", settings()->baseDirectory()); + sendSessionPacket(sessionProc, resolveMessage); + bool receivedReply = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-resolved") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + } + } + QVERIFY(receivedReply); + + // Employ client app to send request. + QProcess lspClient; + const QFileInfo qbsFileInfo(qbsExecutableFilePath); + const QString clientFilePath = HostOsInfo::appendExecutableSuffix( + qbsFileInfo.absolutePath() + "/qbs_lspclient"); + QStringList args{"--socket", socketPath, request, location}; + if (!insertString.isEmpty()) + args << "--insert-code" << insertString; + if (!insertLocation.isEmpty()) + args << "--insert-location" << insertLocation; + lspClient.start(clientFilePath, args); + QVERIFY2(lspClient.waitForStarted(), qPrintable(lspClient.errorString())); + QVERIFY2(lspClient.waitForFinished(), qPrintable(lspClient.errorString())); + QString errMsg; + if (lspClient.exitStatus() != QProcess::NormalExit) + errMsg = lspClient.errorString(); + if (errMsg.isEmpty()) + errMsg = QString::fromLocal8Bit(lspClient.readAllStandardError()); + QVERIFY2(lspClient.exitCode() == 0, qPrintable(errMsg)); + QVERIFY2(errMsg.isEmpty(), qPrintable(errMsg)); + QString output = QString::fromUtf8(lspClient.readAllStandardOutput().trimmed()); + output.replace("\r\n", "\n"); + QCOMPARE(output, expectedReply); + + QJsonObject quitRequest; + quitRequest.insert("type", "quit"); + sendSessionPacket(sessionProc, quitRequest); + QVERIFY(sessionProc.waitForFinished(3000)); +} + void TestBlackbox::qbsSession() { QDir::setCurrent(testDataDir + "/qbs-session"); @@ -6297,11 +6633,7 @@ void TestBlackbox::qbsSession() QVERIFY(sessionProc.waitForStarted()); const auto sendPacket = [&sessionProc](const QJsonObject &message) { - const QByteArray data = QJsonDocument(message).toJson().toBase64(); - sessionProc.write("qbsmsg:"); - sessionProc.write(QByteArray::number(data.length())); - sessionProc.write("\n"); - sessionProc.write(data); + sendSessionPacket(sessionProc, message); }; static const auto envToJson = [](const QProcessEnvironment &env) { @@ -6325,7 +6657,7 @@ void TestBlackbox::qbsSession() // Wait for and verify hello packet. QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData); QCOMPARE(receivedMessage.value("type"), "hello"); - QCOMPARE(receivedMessage.value("api-level").toInt(), 2); + QCOMPARE(receivedMessage.value("api-level").toInt(), 5); QCOMPARE(receivedMessage.value("api-compat-level").toInt(), 2); // Resolve & verify structure @@ -6341,6 +6673,7 @@ void TestBlackbox::qbsSession() resolveMessage.insert("overridden-properties", overriddenValues); resolveMessage.insert("environment", envToJson(QbsRunParameters::defaultEnvironment())); resolveMessage.insert("data-mode", "only-if-changed"); + resolveMessage.insert("max-job-count", 2); resolveMessage.insert("log-time", true); resolveMessage.insert("module-properties", QJsonArray::fromStringList({"cpp.cxxLanguageVersion"})); @@ -7414,9 +7747,9 @@ static bool haveMakeNsis() << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS"); QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH") - .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts); - for (const QString &key : qAsConst(regKeys)) { + for (const QString &key : std::as_const(regKeys)) { QSettings settings(key, QSettings::NativeFormat); QString str = settings.value(QStringLiteral(".")).toString(); if (!str.isEmpty()) @@ -7424,7 +7757,7 @@ static bool haveMakeNsis() } bool haveMakeNsis = false; - for (const QString &path : qAsConst(paths)) { + for (const QString &path : std::as_const(paths)) { if (regularFileExists(QDir::fromNativeSeparators(path) + HostOsInfo::appendExecutableSuffix(QStringLiteral("/makensis")))) { haveMakeNsis = true; @@ -7652,7 +7985,7 @@ void TestBlackbox::generator_data() void TestBlackbox::nodejs() { const SettingsPtr s = settings(); - Profile p(profileName(), s.get()); + qbs::Profile p(profileName(), s.get()); int status; findNodejs(&status); @@ -7671,6 +8004,11 @@ void TestBlackbox::nodejs() QSKIP("nodejs.packageManagerFilePath not set and automatic detection failed"); } + if (p.value("nodejs.interpreterFilePath").toString().isEmpty() + && status != 0 && m_qbsStderr.contains("interpreterPath")) { + QSKIP("nodejs.interpreterFilePath not set and automatic detection failed"); + } + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) QSKIP("Cannot run binaries in cross-compiled build"); QCOMPARE(status, 0); @@ -7688,7 +8026,7 @@ void TestBlackbox::typescript() QSKIP("Skip this test when running on GitHub"); const SettingsPtr s = settings(); - Profile p(profileName(), s.get()); + qbs::Profile p(profileName(), s.get()); int status; findTypeScript(&status); @@ -7855,6 +8193,35 @@ void TestBlackbox::wildCardsAndRules() QVERIFY(!m_qbsStdout.contains("creating output artifact")); } +void TestBlackbox::wildCardsAndChangeTracking_data() +{ + QTest::addColumn<QString>("dirToModify"); + QTest::addColumn<bool>("expectReResolve"); + + QTest::newRow("root path") << QString(".") << false; + QTest::newRow("dir with recursive match") << QString("recursive1") << false; + QTest::newRow("non-recursive base dir") << QString("nonrecursive") << true; + QTest::newRow("empty base dir with file patterns") << QString("nonrecursive/empty") << true; +} + +void TestBlackbox::wildCardsAndChangeTracking() +{ + QFETCH(QString, dirToModify); + QFETCH(bool, expectReResolve); + + const QString srcDir = testDataDir + "/wildcards-and-change-tracking"; + QDir::setCurrent(srcDir); + rmDirR("default"); + QDir::current().mkdir("nonrecursive/empty"); + + QCOMPARE(runQbs({"resolve"}), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch(dirToModify + "/blubb.txt"); + QCOMPARE(runQbs({"resolve"}), 0); + QCOMPARE(m_qbsStdout.contains("Resolving"), expectReResolve); +} + void TestBlackbox::loadableModule() { QDir::setCurrent(testDataDir + QLatin1String("/loadablemodule")); @@ -7945,112 +8312,6 @@ void TestBlackbox::maximumCxxLanguageVersion() m_qbsStdout.constData()); } -void TestBlackbox::moduleProviders() -{ - QDir::setCurrent(testDataDir + "/module-providers"); - - // Resolving in dry-run mode must not leave any data behind. - QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); - if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) - QSKIP("Cannot run binaries in cross-compiled build"); - QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2); - QVERIFY(!QFile::exists(relativeBuildDir())); - - // Initial build. - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); - QVERIFY(QFile::exists(relativeBuildDir())); - QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2); - QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); - QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app1"), m_qbsStdout.constData()); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData()); - QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app2"), m_qbsStdout.constData()); - - // Rebuild with overridden module provider config. The output for product 2 must change, - // but no setup script must be re-run, because both config values have already been - // handled in the first run. - const QStringList resolveArgs("moduleProviders.mygenerator.chooseLettersFrom:beginning"); - QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0); - QVERIFY2(!m_qbsStdout.contains("Running setup script"), m_qbsStdout.constData()); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); - - // Forcing Probe execution triggers a re-run of the setup script. But only once, - // because the module provider config is the same now. - QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(resolveArgs) - << "--force-probe-execution")), 0); - QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); - - // Now re-run without the module provider config override. Again, the setup script must - // run once, for the config value that was not present in the last run. - QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); - QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); - QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); - QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData()); -} - -void TestBlackbox::fallbackModuleProvider_data() -{ - QTest::addColumn<bool>("fallbacksEnabledGlobally"); - QTest::addColumn<bool>("fallbacksEnabledInProduct"); - QTest::addColumn<QStringList>("pkgConfigLibDirs"); - QTest::addColumn<bool>("successExpected"); - QTest::newRow("without custom lib dir, fallbacks disabled globally and in product") - << false << false << QStringList() << false; - QTest::newRow("without custom lib dir, fallbacks disabled globally, enabled in product") - << false << true << QStringList() << false; - QTest::newRow("without custom lib dir, fallbacks enabled globally, disabled in product") - << true << false << QStringList() << false; - QTest::newRow("without custom lib dir, fallbacks enabled globally and in product") - << true << true << QStringList() << false; - QTest::newRow("with custom lib dir, fallbacks disabled globally and in product") - << false << false << QStringList(testDataDir + "/fallback-module-provider/libdir") - << false; - QTest::newRow("with custom lib dir, fallbacks disabled globally, enabled in product") - << false << true << QStringList(testDataDir + "/fallback-module-provider/libdir") - << false; - QTest::newRow("with custom lib dir, fallbacks enabled globally, disabled in product") - << true << false << QStringList(testDataDir + "/fallback-module-provider/libdir") - << false; - QTest::newRow("with custom lib dir, fallbacks enabled globally and in product") - << true << true << QStringList(testDataDir + "/fallback-module-provider/libdir") - << true; -} - -void TestBlackbox::fallbackModuleProvider() -{ - QFETCH(bool, fallbacksEnabledInProduct); - QFETCH(bool, fallbacksEnabledGlobally); - QFETCH(QStringList, pkgConfigLibDirs); - QFETCH(bool, successExpected); - - QDir::setCurrent(testDataDir + "/fallback-module-provider"); - static const auto b2s = [](bool b) { return QString(b ? "true" : "false"); }; - QbsRunParameters resolveParams("resolve", - QStringList{"modules.pkgconfig.libDirs:" + pkgConfigLibDirs.join(','), - "products.p.fallbacksEnabled:" + b2s(fallbacksEnabledInProduct), - "--force-probe-execution"}); - if (!fallbacksEnabledGlobally) - resolveParams.arguments << "--no-fallback-module-provider"; - QCOMPARE(runQbs(resolveParams), 0); - const bool pkgConfigPresent = m_qbsStdout.contains("pkg-config present: true"); - const bool pkgConfigNotPresent = m_qbsStdout.contains("pkg-config present: false"); - QVERIFY(pkgConfigPresent != pkgConfigNotPresent); - if (pkgConfigNotPresent) - successExpected = false; - QbsRunParameters buildParams; - buildParams.expectFailure = !successExpected; - QCOMPARE(runQbs(buildParams) == 0, successExpected); -} - void TestBlackbox::minimumSystemVersion() { rmDirR(relativeBuildDir()); @@ -8143,7 +8404,7 @@ void TestBlackbox::minimumSystemVersion_data() return "__MAC_OS_X_VERSION_MIN_REQUIRED=1070\nversion 10.7\n"; if (HostOsInfo::isWindowsHost()) - return "WINVER=1536\n6.00 operating system version\n6.00 subsystem version\n"; + return "WINVER=1538\n6.02 operating system version\n6.02 subsystem version\n"; return ""; }(); @@ -8171,7 +8432,7 @@ void TestBlackbox::missingBuildGraph() QbsRunParameters params; params.expectFailure = true; params.arguments << QLatin1String("config:") + actualConfigName; - for (const QString &command : qAsConst(commands)) { + for (const QString &command : std::as_const(commands)) { params.command = command; QVERIFY2(runQbs(params) != 0, qPrintable(command)); const QString expectedErrorMessage = QString("Build graph not found for " @@ -8281,6 +8542,12 @@ void TestBlackbox::movedFileDependency() QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); } +void TestBlackbox::msvcAsmLinkerFlags() +{ + QDir::setCurrent(testDataDir + "/msvc-asm-flags"); + QCOMPARE(runQbs(), 0); +} + void TestBlackbox::badInterpreter() { if (!HostOsInfo::isAnyUnixHost()) @@ -8402,6 +8669,7 @@ void TestBlackbox::groupsInModules() QVERIFY(m_qbsStdout.contains("compiling helper4.c")); QVERIFY(m_qbsStdout.contains("compiling helper5.c")); QVERIFY(!m_qbsStdout.contains("compiling helper6.c")); + QVERIFY(m_qbsStdout.contains("compiling helper7.c")); QCOMPARE(runQbs(params), 0); QVERIFY(!m_qbsStdout.contains("compiling rock.coal to rock.diamond")); @@ -8427,9 +8695,7 @@ void TestBlackbox::grpc_data() QTest::addColumn<QStringList>("arguments"); QTest::addColumn<bool>("hasModules"); - QTest::newRow("cpp") << QString("grpc_cpp.qbs") << QStringList() << false; - - QStringList pkgConfigArgs("project.qbsModuleProviders:qbspkgconfig"); + QStringList pkgConfigArgs({"project.qbsModuleProviders:qbspkgconfig"}); // on macOS, openSSL is hidden from pkg-config by default if (qbs::Internal::HostOsInfo::isMacosHost()) { pkgConfigArgs @@ -8447,6 +8713,13 @@ void TestBlackbox::grpc() rmDirR(relativeBuildDir()); QbsRunParameters resolveParams("resolve", QStringList{"-f", projectFile}); + if (QTest::currentDataTag() == QLatin1String("cpp")) { + if (const QString extraLibs = qEnvironmentVariable("QBS_EXTRA_GRPC_LIBS"); + !extraLibs.isEmpty()) { + resolveParams.arguments << (QLatin1String("modules.protobuf.cpp._extraGrpcLibs:") + + extraLibs); + } + } resolveParams.arguments << arguments; QCOMPARE(runQbs(resolveParams), 0); const bool withGrpc = m_qbsStdout.contains("has grpc: true"); |