1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qbs.
8 **
9 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29
30 #include "tst_blackbox.h"
31
32 #include "../shared.h"
33
34 #include <api/languageinfo.h>
35 #include <tools/hostosinfo.h>
36 #include <tools/installoptions.h>
37 #include <tools/preferences.h>
38 #include <tools/profile.h>
39 #include <tools/qttools.h>
40 #include <tools/settings.h>
41 #include <tools/shellutils.h>
42 #include <tools/stlutils.h>
43 #include <tools/version.h>
44
45 #include <QtCore/qdebug.h>
46 #include <QtCore/qelapsedtimer.h>
47 #include <QtCore/qjsonarray.h>
48 #include <QtCore/qjsondocument.h>
49 #include <QtCore/qjsonobject.h>
50 #include <QtCore/qjsonvalue.h>
51 #include <QtCore/qlocale.h>
52 #include <QtCore/qregularexpression.h>
53 #include <QtCore/qsettings.h>
54 #include <QtCore/qtemporarydir.h>
55 #include <QtCore/qtemporaryfile.h>
56
57 #include <algorithm>
58 #include <functional>
59 #include <regex>
60 #include <utility>
61
62 #define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir)
63
64 using qbs::Internal::HostOsInfo;
65 using qbs::Profile;
66
67 class MacosTarHealer {
68 public:
MacosTarHealer()69 MacosTarHealer() {
70 if (HostOsInfo::hostOs() == HostOsInfo::HostOsMacos) {
71 // work around absurd tar behavior on macOS
72 qputenv("COPY_EXTENDED_ATTRIBUTES_DISABLE", "true");
73 qputenv("COPYFILE_DISABLE", "true");
74 }
75 }
76
~MacosTarHealer()77 ~MacosTarHealer() {
78 if (HostOsInfo::hostOs() == HostOsInfo::HostOsMacos) {
79 qunsetenv("COPY_EXTENDED_ATTRIBUTES_DISABLE");
80 qunsetenv("COPYFILE_DISABLE");
81 }
82 }
83 };
84
findCli(int * status)85 QMap<QString, QString> TestBlackbox::findCli(int *status)
86 {
87 QTemporaryDir temp;
88 QDir::setCurrent(testDataDir + "/find");
89 QbsRunParameters params = QStringList() << "-f" << "find-cli.qbs";
90 params.buildDirectory = temp.path();
91 const int res = runQbs(params);
92 if (status)
93 *status = res;
94 QFile file(temp.path() + "/" + relativeProductBuildDir("find-cli") + "/cli.json");
95 if (!file.open(QIODevice::ReadOnly))
96 return {};
97 const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
98 return {{"path", QDir::fromNativeSeparators(tools["path"].toString())}};
99 }
100
findNodejs(int * status)101 QMap<QString, QString> TestBlackbox::findNodejs(int *status)
102 {
103 QTemporaryDir temp;
104 QDir::setCurrent(testDataDir + "/find");
105 QbsRunParameters params = QStringList() << "-f" << "find-nodejs.qbs";
106 params.buildDirectory = temp.path();
107 const int res = runQbs(params);
108 if (status)
109 *status = res;
110 QFile file(temp.path() + "/" + relativeProductBuildDir("find-nodejs") + "/nodejs.json");
111 if (!file.open(QIODevice::ReadOnly))
112 return {};
113 const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
114 return {{"node", QDir::fromNativeSeparators(tools["node"].toString())}};
115 }
116
findTypeScript(int * status)117 QMap<QString, QString> TestBlackbox::findTypeScript(int *status)
118 {
119 QTemporaryDir temp;
120 QDir::setCurrent(testDataDir + "/find");
121 QbsRunParameters params = QStringList() << "-f" << "find-typescript.qbs";
122 params.buildDirectory = temp.path();
123 const int res = runQbs(params);
124 if (status)
125 *status = res;
126 QFile file(temp.path() + "/" + relativeProductBuildDir("find-typescript") + "/typescript.json");
127 if (!file.open(QIODevice::ReadOnly))
128 return {};
129 const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
130 return {{"tsc", QDir::fromNativeSeparators(tools["tsc"].toString())}};
131 }
132
findArchiver(const QString & fileName,int * status)133 QString TestBlackbox::findArchiver(const QString &fileName, int *status)
134 {
135 if (fileName == "jar")
136 return findJdkTools(status).value(fileName);
137
138 QString binary = findExecutable(QStringList(fileName));
139 if (binary.isEmpty()) {
140 const SettingsPtr s = settings();
141 Profile p(profileName(), s.get());
142 binary = findExecutable(p.value("archiver.command").toStringList());
143 }
144 return binary;
145 }
146
lexYaccExist()147 bool TestBlackbox::lexYaccExist()
148 {
149 return !findExecutable(QStringList("lex")).isEmpty()
150 && !findExecutable(QStringList("yacc")).isEmpty();
151 }
152
bisonVersion()153 qbs::Version TestBlackbox::bisonVersion()
154 {
155 const auto yaccBinary = findExecutable(QStringList("yacc"));
156 QProcess process;
157 process.start(yaccBinary, QStringList() << "--version");
158 if (!process.waitForStarted())
159 return qbs::Version();
160 if (!process.waitForFinished())
161 return qbs::Version();
162 const auto processStdOut = process.readAllStandardOutput();
163 if (processStdOut.isEmpty())
164 return qbs::Version();
165 const auto line = processStdOut.split('\n')[0];
166 const auto words = line.split(' ');
167 if (words.empty())
168 return qbs::Version();
169 return qbs::Version::fromString(words.last());
170 }
171
sevenZip()172 void TestBlackbox::sevenZip()
173 {
174 QDir::setCurrent(testDataDir + "/archiver");
175 QString binary = findArchiver("7z");
176 if (binary.isEmpty())
177 QSKIP("7zip not found");
178 QCOMPARE(runQbs(QbsRunParameters(QStringList() << "modules.archiver.type:7zip")), 0);
179 const QString outputFile = relativeProductBuildDir("archivable") + "/archivable.7z";
180 QVERIFY2(regularFileExists(outputFile), qPrintable(outputFile));
181 QProcess listContents;
182 listContents.start(binary, QStringList() << "l" << outputFile);
183 QVERIFY2(listContents.waitForStarted(), qPrintable(listContents.errorString()));
184 QVERIFY2(listContents.waitForFinished(), qPrintable(listContents.errorString()));
185 QVERIFY2(listContents.exitCode() == 0, listContents.readAllStandardError().constData());
186 const QByteArray output = listContents.readAllStandardOutput();
187 QVERIFY2(output.contains("2 files"), output.constData());
188 QVERIFY2(output.contains("test.txt"), output.constData());
189 QVERIFY2(output.contains("archivable.qbs"), output.constData());
190 }
191
sourceArtifactInInputsFromDependencies()192 void TestBlackbox::sourceArtifactInInputsFromDependencies()
193 {
194 QDir::setCurrent(testDataDir + "/source-artifact-in-inputs-from-dependencies");
195 QCOMPARE(runQbs(), 0);
196 QFile outFile(relativeProductBuildDir("p") + "/output.txt");
197 QVERIFY2(outFile.exists(), qPrintable(outFile.fileName()));
198 QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString()));
199 const QByteArrayList output = outFile.readAll().trimmed().split('\n');
200 QCOMPARE(output.size(), 2);
201 bool header1Found = false;
202 bool header2Found = false;
203 for (const QByteArray &line : output) {
204 const QByteArray &path = line.trimmed();
205 if (path == "include1/header.h")
206 header1Found = true;
207 else if (path == "include2/header.h")
208 header2Found = true;
209 }
210 QVERIFY(header1Found);
211 QVERIFY(header2Found);
212 }
213
staticLibWithoutSources()214 void TestBlackbox::staticLibWithoutSources()
215 {
216 QDir::setCurrent(testDataDir + "/static-lib-without-sources");
217 QCOMPARE(runQbs(), 0);
218 }
219
suspiciousCalls()220 void TestBlackbox::suspiciousCalls()
221 {
222 const QString projectDir = testDataDir + "/suspicious-calls";
223 QDir::setCurrent(projectDir);
224 rmDirR(relativeBuildDir());
225 QFETCH(QString, projectFile);
226 QbsRunParameters params(QStringList() << "-f" << projectFile);
227 QCOMPARE(runQbs(params), 0);
228 QFETCH(QByteArray, expectedWarning);
229 QVERIFY2(m_qbsStderr.contains(expectedWarning), m_qbsStderr.constData());
230 }
231
suspiciousCalls_data()232 void TestBlackbox::suspiciousCalls_data()
233 {
234 QTest::addColumn<QString>("projectFile");
235 QTest::addColumn<QByteArray>("expectedWarning");
236 QTest::newRow("File.copy() in Probe") << "copy-probe.qbs" << QByteArray();
237 QTest::newRow("File.copy() during evaluation") << "copy-eval.qbs" << QByteArray("File.copy()");
238 QTest::newRow("File.copy() in prepare script")
239 << "copy-prepare.qbs" << QByteArray("File.copy()");
240 QTest::newRow("File.copy() in command") << "copy-command.qbs" << QByteArray();
241 QTest::newRow("File.directoryEntries() in Probe") << "direntries-probe.qbs" << QByteArray();
242 QTest::newRow("File.directoryEntries() during evaluation")
243 << "direntries-eval.qbs" << QByteArray("File.directoryEntries()");
244 QTest::newRow("File.directoryEntries() in prepare script")
245 << "direntries-prepare.qbs" << QByteArray();
246 QTest::newRow("File.directoryEntries() in command") << "direntries-command.qbs" << QByteArray();
247 }
248
systemIncludePaths()249 void TestBlackbox::systemIncludePaths()
250 {
251 const QString projectDir = testDataDir + "/system-include-paths";
252 QDir::setCurrent(projectDir);
253 QCOMPARE(runQbs(), 0);
254 }
255
distributionIncludePaths()256 void TestBlackbox::distributionIncludePaths()
257 {
258 const QString projectDir = testDataDir + "/distribution-include-paths";
259 QDir::setCurrent(projectDir);
260 QCOMPARE(runQbs(), 0);
261 }
262
tar()263 void TestBlackbox::tar()
264 {
265 if (HostOsInfo::hostOs() == HostOsInfo::HostOsWindows)
266 QSKIP("Beware of the msys tar");
267 MacosTarHealer tarHealer;
268 QDir::setCurrent(testDataDir + "/archiver");
269 rmDirR(relativeBuildDir());
270 QString binary = findArchiver("tar");
271 if (binary.isEmpty())
272 QSKIP("tar not found");
273 QCOMPARE(runQbs(QbsRunParameters(QStringList() << "modules.archiver.type:tar")), 0);
274 const QString outputFile = relativeProductBuildDir("archivable") + "/archivable.tar.gz";
275 QVERIFY2(regularFileExists(outputFile), qPrintable(outputFile));
276 QProcess listContents;
277 listContents.start(binary, QStringList() << "tf" << outputFile);
278 QVERIFY2(listContents.waitForStarted(), qPrintable(listContents.errorString()));
279 QVERIFY2(listContents.waitForFinished(), qPrintable(listContents.errorString()));
280 QVERIFY2(listContents.exitCode() == 0, listContents.readAllStandardError().constData());
281 QFile listFile("list.txt");
282 QVERIFY2(listFile.open(QIODevice::ReadOnly), qPrintable(listFile.errorString()));
283 QCOMPARE(listContents.readAllStandardOutput(), listFile.readAll());
284 }
285
textTemplate()286 void TestBlackbox::textTemplate()
287 {
288 QVERIFY(QDir::setCurrent(testDataDir + "/texttemplate"));
289 rmDirR(relativeBuildDir());
290 QCOMPARE(runQbs(), 0);
291 QString outputFilePath = relativeProductBuildDir("one") + "/output.txt";
292 QString expectedOutputFilePath = QFINDTESTDATA("expected/output.txt");
293 TEXT_FILE_COMPARE(outputFilePath, expectedOutputFilePath);
294 outputFilePath = relativeProductBuildDir("one") + "/lalala.txt";
295 expectedOutputFilePath = QFINDTESTDATA("expected/lalala.txt");
296 TEXT_FILE_COMPARE(outputFilePath, expectedOutputFilePath);
297 }
298
sortedFileList(const QByteArray & ba)299 static QStringList sortedFileList(const QByteArray &ba)
300 {
301 auto list = QString::fromUtf8(ba).split(QRegularExpression("[\r\n]"), QBS_SKIP_EMPTY_PARTS);
302 std::sort(list.begin(), list.end());
303 return list;
304 }
305
zip()306 void TestBlackbox::zip()
307 {
308 QFETCH(QString, binaryName);
309 int status = 0;
310 const QString binary = findArchiver(binaryName, &status);
311 QCOMPARE(status, 0);
312 if (binary.isEmpty())
313 QSKIP("zip tool not found");
314
315 QDir::setCurrent(testDataDir + "/archiver");
316 rmDirR(relativeBuildDir());
317 QbsRunParameters params(QStringList()
318 << "modules.archiver.type:zip" << "modules.archiver.command:" + binary);
319 QCOMPARE(runQbs(params), 0);
320 const QString outputFile = relativeProductBuildDir("archivable") + "/archivable.zip";
321 QVERIFY2(regularFileExists(outputFile), qPrintable(outputFile));
322 QProcess listContents;
323 if (binaryName == "zip") {
324 // zipinfo is part of Info-Zip
325 listContents.start("zipinfo", QStringList() << "-1" << outputFile);
326 } else {
327 listContents.start(binary, QStringList() << "tf" << outputFile);
328 }
329 QVERIFY2(listContents.waitForStarted(), qPrintable(listContents.errorString()));
330 QVERIFY2(listContents.waitForFinished(), qPrintable(listContents.errorString()));
331 QVERIFY2(listContents.exitCode() == 0, listContents.readAllStandardError().constData());
332 QFile listFile("list.txt");
333 QVERIFY2(listFile.open(QIODevice::ReadOnly), qPrintable(listFile.errorString()));
334 QCOMPARE(sortedFileList(listContents.readAllStandardOutput()),
335 sortedFileList(listFile.readAll()));
336
337 // Make sure the module is still loaded when the java/jar fallback is not available
338 params.command = "resolve";
339 params.arguments << "modules.java.jdkPath:/blubb";
340 QCOMPARE(runQbs(params), 0);
341 QCOMPARE(runQbs(), 0);
342 }
343
zip_data()344 void TestBlackbox::zip_data()
345 {
346 QTest::addColumn<QString>("binaryName");
347 QTest::newRow("zip") << "zip";
348 QTest::newRow("jar") << "jar";
349 }
350
zipInvalid()351 void TestBlackbox::zipInvalid()
352 {
353 QDir::setCurrent(testDataDir + "/archiver");
354 rmDirR(relativeBuildDir());
355 QbsRunParameters params(QStringList() << "modules.archiver.type:zip"
356 << "modules.archiver.command:/bin/something");
357 params.expectFailure = true;
358 QVERIFY(runQbs(params) != 0);
359 QVERIFY2(m_qbsStderr.contains("unrecognized archive tool: 'something'"), m_qbsStderr.constData());
360 }
361
TestBlackbox()362 TestBlackbox::TestBlackbox() : TestBlackboxBase (SRCDIR "/testdata", "blackbox")
363 {
364 }
365
allowedValues()366 void TestBlackbox::allowedValues()
367 {
368 QFETCH(QString, property);
369 QFETCH(QString, value);
370 QFETCH(QString, invalidValue);
371
372 QDir::setCurrent(testDataDir + "/allowed-values");
373 rmDirR(relativeBuildDir());
374
375 QbsRunParameters params;
376 if (!property.isEmpty() && !value.isEmpty()) {
377 params.arguments << QStringLiteral("%1:%2").arg(property, value);
378 }
379
380 params.expectFailure = !invalidValue.isEmpty();
381 QCOMPARE(runQbs(params) == 0, !params.expectFailure);
382 if (params.expectFailure) {
383 const auto errorString =
384 QStringLiteral("Value '%1' is not allowed for property").arg(invalidValue);
385 QVERIFY2(m_qbsStderr.contains(errorString.toUtf8()), m_qbsStderr.constData());
386 }
387 }
388
allowedValues_data()389 void TestBlackbox::allowedValues_data()
390 {
391 QTest::addColumn<QString>("property");
392 QTest::addColumn<QString>("value");
393 QTest::addColumn<QString>("invalidValue");
394
395 QTest::newRow("default") << QString() << QString() << QString();
396
397 QTest::newRow("allowed (product, CLI)") << "products.p.prop" << "foo" << QString();
398 QTest::newRow("not allowed (product, CLI)") << "products.p.prop" << "bar" << "bar";
399 QTest::newRow("allowed (product, JS)") << "products.p.prop2" << "foo" << QString();
400 QTest::newRow("not allowed (product, JS)") << "products.p.prop2" << "bar" << "bar";
401
402 QTest::newRow("allowed single (module, CLI)") << "modules.a.prop" << "foo" << QString();
403 QTest::newRow("not allowed single (module, CLI)") << "modules.a.prop" << "baz" << "baz";
404 QTest::newRow("allowed mult (module, CLI)") << "modules.a.prop" << "foo,bar" << QString();
405 QTest::newRow("not allowed mult (module, CLI)") << "modules.a.prop" << "foo,baz" << "baz";
406
407 QTest::newRow("allowed single (module, JS)") << "modules.a.prop2" << "foo" << QString();
408 QTest::newRow("not allowed single (module, JS)") << "modules.a.prop2" << "baz" << "baz";
409 QTest::newRow("allowed mult (module, JS)") << "modules.a.prop2" << "foo,bar" << QString();
410 QTest::newRow("not allowed mult (module, JS)") << "modules.a.prop2" << "foo,baz" << "baz";
411
412 // undefined should always be allowed
413 QTest::newRow("undefined (product)") << "products.p.prop" << "undefined" << QString();
414 QTest::newRow("undefined (module)") << "modules.a.prop" << "undefined" << QString();
415 }
416
addFileTagToGeneratedArtifact()417 void TestBlackbox::addFileTagToGeneratedArtifact()
418 {
419 QDir::setCurrent(testDataDir + "/add-filetag-to-generated-artifact");
420 QCOMPARE(runQbs(QStringList("project.enableTagging:true")), 0);
421 QVERIFY2(m_qbsStdout.contains("compressing my_app"), m_qbsStdout.constData());
422 const QString compressedAppFilePath
423 = relativeProductBuildDir("my_compressed_app") + '/'
424 + qbs::Internal::HostOsInfo::appendExecutableSuffix("compressed-my_app");
425 QVERIFY(regularFileExists(compressedAppFilePath));
426 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("project.enableTagging:false"))), 0);
427 QCOMPARE(runQbs(), 0);
428 QVERIFY(!regularFileExists(compressedAppFilePath));
429 }
430
alwaysRun()431 void TestBlackbox::alwaysRun()
432 {
433 QFETCH(QString, projectFile);
434
435 QDir::setCurrent(testDataDir + "/always-run");
436 rmDirR(relativeBuildDir());
437 QbsRunParameters params("build", QStringList() << "-f" << projectFile);
438 if (projectFile.contains("transformer")) {
439 params.expectFailure = true;
440 QVERIFY(runQbs(params) != 0);
441 QVERIFY2(m_qbsStderr.contains("removed"), m_qbsStderr.constData());
442 return;
443 }
444 QCOMPARE(runQbs(params), 0);
445 QVERIFY(m_qbsStdout.contains("yo"));
446 QCOMPARE(runQbs(params), 0);
447 QVERIFY(!m_qbsStdout.contains("yo"));
448 WAIT_FOR_NEW_TIMESTAMP();
449 REPLACE_IN_FILE(projectFile, "alwaysRun: false", "alwaysRun: true");
450
451 QCOMPARE(runQbs(params), 0);
452 QVERIFY(m_qbsStdout.contains("yo"));
453 QCOMPARE(runQbs(params), 0);
454 QVERIFY(m_qbsStdout.contains("yo"));
455 }
456
alwaysRun_data()457 void TestBlackbox::alwaysRun_data()
458 {
459 QTest::addColumn<QString>("projectFile");
460 QTest::newRow("Transformer") << "transformer.qbs";
461 QTest::newRow("Rule") << "rule.qbs";
462 }
463
artifactsMapChangeTracking()464 void TestBlackbox::artifactsMapChangeTracking()
465 {
466 QDir::setCurrent(testDataDir + "/artifacts-map-change-tracking");
467 QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0);
468 QVERIFY2(m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData());
469 QVERIFY2(m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData());
470 QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData());
471 QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0);
472 QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData());
473 QVERIFY2(!m_qbsStdout.contains("test.txt"), m_qbsStdout.constData());
474 QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData());
475 QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData());
476 QVERIFY2(m_qbsStdout.contains("TheBinary"), m_qbsStdout.constData());
477 QVERIFY2(m_qbsStdout.contains("dummy1"), m_qbsStdout.constData());
478 QVERIFY2(m_qbsStdout.contains("dummy2"), m_qbsStdout.constData());
479
480 // Change name of target binary. Command must be re-run, because the file name of an
481 // artifact changed.
482 WAIT_FOR_NEW_TIMESTAMP();
483 const QString projectFile("artifacts-map-change-tracking.qbs");
484 REPLACE_IN_FILE(projectFile, "TheBinary", "TheNewBinary");
485 QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0);
486
487 QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData());
488 QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData());
489 QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData());
490 QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0);
491 QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData());
492 QVERIFY2(!m_qbsStdout.contains("test.txt"), m_qbsStdout.constData());
493 QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData());
494 QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData());
495 QVERIFY2(m_qbsStdout.contains("TheNewBinary"), m_qbsStdout.constData());
496 QVERIFY2(!m_qbsStdout.contains("TheBinary"), m_qbsStdout.constData());
497 QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData());
498 QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData());
499
500 // Add file tag to generated artifact. Command must be re-run, because it enumerates the keys
501 // of the artifacts map.
502 WAIT_FOR_NEW_TIMESTAMP();
503 REPLACE_IN_FILE(projectFile, "fileTags: 'cpp'", "fileTags: ['cpp', 'blubb']");
504 QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0);
505 QVERIFY2(m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData());
506 QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData());
507 QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData());
508 QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0);
509 QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData());
510 QVERIFY2(!m_qbsStdout.contains("test.txt"), m_qbsStdout.constData());
511 QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData());
512 QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData());
513 QVERIFY2(m_qbsStdout.contains("TheNewBinary"), m_qbsStdout.constData());
514 QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData());
515 QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData());
516
517 // Add redundant file tag to generated artifact. Command must not be re-run, because
518 // the artifacts map has not changed.
519 WAIT_FOR_NEW_TIMESTAMP();
520 REPLACE_IN_FILE(projectFile, "fileTags: ['cpp', 'blubb']",
521 "fileTags: ['cpp', 'blubb', 'blubb']");
522 QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0);
523 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
524 QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData());
525 QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData());
526 QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData());
527 QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0);
528 QVERIFY2(!m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData());
529 QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData());
530 QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData());
531
532 // Rebuild the app. Command must not be re-run, because the artifacts map has not changed.
533 WAIT_FOR_NEW_TIMESTAMP();
534 touch("main.cpp");
535 QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0);
536 QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData());
537 QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData());
538 QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData());
539 QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0);
540 QVERIFY2(!m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData());
541 QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData());
542 QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData());
543
544 // Add source file to app. Command must be re-run, because the artifacts map has changed.
545 WAIT_FOR_NEW_TIMESTAMP();
546 REPLACE_IN_FILE(projectFile, "/* 'test.txt' */", "'test.txt'");
547 QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0);
548 QEXPECT_FAIL("", "change tracking could become even more fine-grained", Continue);
549 QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData());
550 QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData());
551 QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData());
552 QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0);
553 QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData());
554 QVERIFY2(m_qbsStdout.contains("test.txt"), m_qbsStdout.constData());
555 QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData());
556 QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData());
557 QVERIFY2(m_qbsStdout.contains("TheNewBinary"), m_qbsStdout.constData());
558 QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData());
559 QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData());
560 }
561
artifactsMapInvalidation()562 void TestBlackbox::artifactsMapInvalidation()
563 {
564 const QString projectDir = testDataDir + "/artifacts-map-invalidation";
565 QDir::setCurrent(projectDir);
566 QCOMPARE(runQbs(), 0);
567 TEXT_FILE_COMPARE(relativeProductBuildDir("p") + "/myfile.out", "file.in");
568 }
569
artifactsMapRaceCondition()570 void TestBlackbox::artifactsMapRaceCondition()
571 {
572 QDir::setCurrent(testDataDir + "/artifacts-map-race-condition");
573 QCOMPARE(runQbs(), 0);
574 }
575
artifactScanning()576 void TestBlackbox::artifactScanning()
577 {
578 const QString projectDir = testDataDir + "/artifact-scanning";
579 QDir::setCurrent(projectDir);
580 QbsRunParameters params(QStringList("-vv"));
581
582 QCOMPARE(runQbs(params), 0);
583 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 1);
584 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 1);
585 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 1);
586 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 1);
587 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 1);
588 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 1);
589 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 1);
590 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
591
592 WAIT_FOR_NEW_TIMESTAMP();
593 touch("p1.cpp");
594 QCOMPARE(runQbs(params), 0);
595 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 1);
596 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
597 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
598 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
599 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
600 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
601 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
602 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
603
604 WAIT_FOR_NEW_TIMESTAMP();
605 touch("p2.cpp");
606 QCOMPARE(runQbs(params), 0);
607 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
608 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 1);
609 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
610 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
611 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
612 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
613 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
614 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
615
616 WAIT_FOR_NEW_TIMESTAMP();
617 touch("p3.cpp");
618 QCOMPARE(runQbs(params), 0);
619 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
620 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
621 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 1);
622 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
623 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
624 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
625 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
626 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
627
628 WAIT_FOR_NEW_TIMESTAMP();
629 touch("shared.h");
630 QCOMPARE(runQbs(params), 0);
631 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
632 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
633 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
634 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 1);
635 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
636 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
637 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
638 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
639
640 WAIT_FOR_NEW_TIMESTAMP();
641 touch("external.h");
642 QCOMPARE(runQbs(params), 0);
643 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
644 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
645 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
646 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
647 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 1);
648 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
649 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
650 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
651
652 WAIT_FOR_NEW_TIMESTAMP();
653 touch("subdir/external2.h");
654 QCOMPARE(runQbs(params), 0);
655 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
656 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
657 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
658 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
659 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
660 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 1);
661 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
662 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
663
664 WAIT_FOR_NEW_TIMESTAMP();
665 touch("external-indirect.h");
666 QCOMPARE(runQbs(params), 0);
667 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 0);
668 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
669 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
670 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
671 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
672 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
673 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 1);
674 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 0);
675
676 WAIT_FOR_NEW_TIMESTAMP();
677 touch("p1.cpp");
678 params.command = "resolve";
679 params.arguments << "modules.cpp.treatSystemHeadersAsDependencies:true";
680 QCOMPARE(runQbs(params), 0);
681 params.command = "build";
682 QCOMPARE(runQbs(params), 0);
683 QCOMPARE(m_qbsStderr.count("scanning \"p1.cpp\""), 1);
684 QCOMPARE(m_qbsStderr.count("scanning \"p2.cpp\""), 0);
685 QCOMPARE(m_qbsStderr.count("scanning \"p3.cpp\""), 0);
686 QCOMPARE(m_qbsStderr.count("scanning \"shared.h\""), 0);
687 QCOMPARE(m_qbsStderr.count("scanning \"external.h\""), 0);
688 QCOMPARE(m_qbsStderr.count("scanning \"external2.h\""), 0);
689 QCOMPARE(m_qbsStderr.count("scanning \"external-indirect.h\""), 0);
690 QCOMPARE(m_qbsStderr.count("scanning \"iostream\""), 1);
691 }
692
buildDirectories()693 void TestBlackbox::buildDirectories()
694 {
695 const QString projectDir
696 = QDir::cleanPath(testDataDir + QLatin1String("/build-directories"));
697 const QString projectBuildDir = projectDir + '/' + relativeBuildDir();
698 QDir::setCurrent(projectDir);
699 QCOMPARE(runQbs(), 0);
700 const QStringList outputLines
701 = QString::fromLocal8Bit(m_qbsStdout.trimmed()).split('\n', QBS_SKIP_EMPTY_PARTS);
702 QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p1")),
703 m_qbsStdout.constData());
704 QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p2")),
705 m_qbsStdout.constData());
706 QVERIFY2(outputLines.contains(projectBuildDir), m_qbsStdout.constData());
707 QVERIFY2(outputLines.contains(projectDir), m_qbsStdout.constData());
708 }
709
buildEnvChange()710 void TestBlackbox::buildEnvChange()
711 {
712 QDir::setCurrent(testDataDir + "/buildenv-change");
713 QbsRunParameters params;
714 params.expectFailure = true;
715 params.arguments << "-k";
716 QVERIFY(runQbs(params) != 0);
717 const bool isMsvc = m_qbsStdout.contains("msvc");
718 QVERIFY2(m_qbsStdout.contains("compiling file.c"), m_qbsStdout.constData());
719 QString includePath = QDir::currentPath() + "/subdir";
720 params.environment.insert("CPLUS_INCLUDE_PATH", includePath);
721 params.environment.insert("CL", "/I" + includePath);
722 QVERIFY(runQbs(params) != 0);
723 params.command = "resolve";
724 params.expectFailure = false;
725 params.arguments.clear();
726 QCOMPARE(runQbs(params), 0);
727 QCOMPARE(runQbs(), 0);
728 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
729 QCOMPARE(m_qbsStdout.contains("compiling file.c"), isMsvc);
730 includePath = QDir::currentPath() + "/subdir2";
731 params.environment.insert("CPLUS_INCLUDE_PATH", includePath);
732 params.environment.insert("CL", "/I" + includePath);
733 QCOMPARE(runQbs(params), 0);
734 QCOMPARE(runQbs(), 0);
735 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
736 QCOMPARE(m_qbsStdout.contains("compiling file.c"), isMsvc);
737 params.environment = QProcessEnvironment::systemEnvironment();
738 QCOMPARE(runQbs(params), 0);
739 params.command = "build";
740 params.expectFailure = true;
741 QVERIFY(runQbs(params) != 0);
742 }
743
buildGraphVersions()744 void TestBlackbox::buildGraphVersions()
745 {
746 QDir::setCurrent(testDataDir + "/build-graph-versions");
747 QCOMPARE(runQbs(), 0);
748 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
749 QFile bgFile(relativeBuildGraphFilePath());
750 QVERIFY2(bgFile.open(QIODevice::ReadWrite), qPrintable(bgFile.errorString()));
751 bgFile.write("blubb");
752 bgFile.close();
753
754 // The first attempt at simple rebuilding as well as subsequent ones must fail.
755 QbsRunParameters params;
756 params.expectFailure = true;
757 QVERIFY(runQbs(params) != 0);
758 QVERIFY2(m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData());
759 QVERIFY2(m_qbsStderr.contains("Use the 'resolve' command"), m_qbsStderr.constData());
760 QVERIFY(runQbs(params) != 0);
761 QVERIFY2(m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData());
762 QVERIFY2(m_qbsStderr.contains("Use the 'resolve' command"), m_qbsStderr.constData());
763
764 // On re-resolving, the error turns into a warning and a new build graph is created.
765 QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
766 QVERIFY2(m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData());
767 QVERIFY2(!m_qbsStderr.contains("Use the 'resolve' command"), m_qbsStderr.constData());
768
769 QCOMPARE(runQbs(), 0);
770 QVERIFY2(!m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData());
771 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
772 }
773
buildVariantDefaults_data()774 void TestBlackbox::buildVariantDefaults_data()
775 {
776 QTest::addColumn<QString>("buildVariant");
777 QTest::newRow("default") << QString();
778 QTest::newRow("debug") << QStringLiteral("debug");
779 QTest::newRow("release") << QStringLiteral("release");
780 QTest::newRow("profiling") << QStringLiteral("profiling");
781 }
782
buildVariantDefaults()783 void TestBlackbox::buildVariantDefaults()
784 {
785 QFETCH(QString, buildVariant);
786 QDir::setCurrent(testDataDir + "/build-variant-defaults");
787 QbsRunParameters params{QStringLiteral("resolve")};
788 if (!buildVariant.isEmpty())
789 params.arguments << ("modules.qbs.buildVariant:" + buildVariant);
790 QCOMPARE(runQbs(params), 0);
791 }
792
capnproto()793 void TestBlackbox::capnproto()
794 {
795 QFETCH(QString, projectFile);
796 QDir::setCurrent(testDataDir + "/capnproto");
797 rmDirR(relativeBuildDir());
798
799 QbsRunParameters params{QStringLiteral("resolve"), {QStringLiteral("-f"), projectFile}};
800 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
801 QSKIP("Cannot run binaries in cross-compiled build");
802 if (m_qbsStdout.contains("capnproto is not present"))
803 QSKIP("capnproto is not present");
804
805 params.command.clear();
806 QCOMPARE(runQbs(params), 0);
807 }
808
capnproto_data()809 void TestBlackbox::capnproto_data()
810 {
811 QTest::addColumn<QString>("projectFile");
812
813 QTest::newRow("cpp") << QStringLiteral("capnproto_cpp.qbs");
814 QTest::newRow("cpp-pkgconfig") << QStringLiteral("capnproto_cpp_pkgconfig.qbs");
815 QTest::newRow("greeter cpp (grpc)") << QStringLiteral("greeter_cpp.qbs");
816 QTest::newRow("relative import") << QStringLiteral("capnproto_relative_import.qbs");
817 QTest::newRow("absolute import") << QStringLiteral("capnproto_absolute_import.qbs");
818 }
819
changedFiles_data()820 void TestBlackbox::changedFiles_data()
821 {
822 QTest::addColumn<bool>("useChangedFilesForInitialBuild");
823 QTest::newRow("initial build with changed files") << true;
824 QTest::newRow("initial build without changed files") << false;
825 }
826
changedFiles()827 void TestBlackbox::changedFiles()
828 {
829 QFETCH(bool, useChangedFilesForInitialBuild);
830
831 QDir::setCurrent(testDataDir + "/changed-files");
832 rmDirR(relativeBuildDir());
833 const QString changedFile = QDir::cleanPath(QDir::currentPath() + "/file1.cpp");
834 QbsRunParameters params1;
835 if (useChangedFilesForInitialBuild)
836 params1 = QbsRunParameters(QStringList("--changed-files") << changedFile);
837
838 // Initial run: Build all files, even though only one of them was marked as changed
839 // (if --changed-files was used).
840 QCOMPARE(runQbs(params1), 0);
841 QCOMPARE(m_qbsStdout.count("compiling"), 3);
842 QCOMPARE(m_qbsStdout.count("creating"), 3);
843
844 WAIT_FOR_NEW_TIMESTAMP();
845 touch(QDir::currentPath() + "/main.cpp");
846
847 // Now only the file marked as changed must be compiled, even though it hasn't really
848 // changed and another one has.
849 QbsRunParameters params2(QStringList("--changed-files") << changedFile);
850 QCOMPARE(runQbs(params2), 0);
851 QCOMPARE(m_qbsStdout.count("compiling"), 1);
852 QCOMPARE(m_qbsStdout.count("creating"), 1);
853 QVERIFY2(m_qbsStdout.contains("file1.cpp"), m_qbsStdout.constData());
854 }
855
changedInputsFromDependencies()856 void TestBlackbox::changedInputsFromDependencies()
857 {
858 QDir::setCurrent(testDataDir + "/changed-inputs-from-dependencies");
859 QCOMPARE(runQbs(), 0);
860 QVERIFY2(m_qbsStdout.contains("final prepare script"), m_qbsStdout.constData());
861 QCOMPARE(runQbs(), 0);
862 QVERIFY2(!m_qbsStdout.contains("final prepare script"), m_qbsStdout.constData());
863 WAIT_FOR_NEW_TIMESTAMP();
864 touch("input.txt");
865 QCOMPARE(runQbs(), 0);
866 QVERIFY2(m_qbsStdout.contains("final prepare script"), m_qbsStdout.constData());
867 }
868
changedRuleInputs()869 void TestBlackbox::changedRuleInputs()
870 {
871 QDir::setCurrent(testDataDir + "/changed-rule-inputs");
872
873 // Initial build.
874 QCOMPARE(runQbs(), 0);
875 QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData());
876 QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData());
877
878 // Re-build: p1 is always regenerated, and p2 has a dependency on it.
879 QCOMPARE(runQbs(), 0);
880 QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData());
881 QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData());
882
883 // Remove the dependency. p2 gets re-generated one last time, because its set of
884 // inputs changed.
885 WAIT_FOR_NEW_TIMESTAMP();
886 REPLACE_IN_FILE("changed-rule-inputs.qbs", "inputsFromDependencies: \"p1\"",
887 "inputsFromDependencies: \"p3\"");
888 QCOMPARE(runQbs(), 0);
889 QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData());
890 QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData());
891
892 // Now the artifacts are no longer connected, and p2 must not get rebuilt anymore.
893 QCOMPARE(runQbs(), 0);
894 QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData());
895 QVERIFY2(!m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData());
896 }
897
changeInDisabledProduct()898 void TestBlackbox::changeInDisabledProduct()
899 {
900 QDir::setCurrent(testDataDir + "/change-in-disabled-product");
901 QCOMPARE(runQbs(), 0);
902 WAIT_FOR_NEW_TIMESTAMP();
903 REPLACE_IN_FILE("change-in-disabled-product.qbs", "// 'test2.txt'", "'test2.txt'");
904 QCOMPARE(runQbs(), 0);
905 }
906
changeInImportedFile()907 void TestBlackbox::changeInImportedFile()
908 {
909 QDir::setCurrent(testDataDir + "/change-in-imported-file");
910 QCOMPARE(runQbs(), 0);
911 QVERIFY2(m_qbsStdout.contains("old output"), m_qbsStdout.constData());
912
913 WAIT_FOR_NEW_TIMESTAMP();
914 REPLACE_IN_FILE("prepare.js", "old output", "new output");
915 QCOMPARE(runQbs(), 0);
916 QVERIFY2(m_qbsStdout.contains("new output"), m_qbsStdout.constData());
917
918 WAIT_FOR_NEW_TIMESTAMP();
919 touch("prepare.js");
920 QCOMPARE(runQbs(), 0);
921 QVERIFY2(!m_qbsStdout.contains("output"), m_qbsStdout.constData());
922 }
923
changeTrackingAndMultiplexing()924 void TestBlackbox::changeTrackingAndMultiplexing()
925 {
926 QDir::setCurrent(testDataDir + "/change-tracking-and-multiplexing");
927 QCOMPARE(runQbs(QStringList("modules.cpp.staticLibraryPrefix:prefix1")), 0);
928 QCOMPARE(m_qbsStdout.count("compiling lib.cpp"), 2);
929 QCOMPARE(m_qbsStdout.count("creating prefix1l"), 2);
930 QCOMPARE(runQbs(QbsRunParameters("resolve",
931 QStringList("modules.cpp.staticLibraryPrefix:prefix2"))), 0);
932 QCOMPARE(runQbs(), 0);
933 QCOMPARE(m_qbsStdout.count("compiling lib.cpp"), 0);
934 QCOMPARE(m_qbsStdout.count("creating prefix2l"), 2);
935 }
936
findByName(const QJsonArray & objects,const QString & name)937 static QJsonObject findByName(const QJsonArray &objects, const QString &name)
938 {
939 for (const QJsonValue &v : objects) {
940 if (!v.isObject())
941 continue;
942 QJsonObject obj = v.toObject();
943 const QString objName = obj.value(QStringLiteral("name")).toString();
944 if (objName == name)
945 return obj;
946 }
947 return {};
948 }
949
readDepsOutput(const QString & depsFilePath,QJsonDocument & jsonDocument)950 static void readDepsOutput(const QString &depsFilePath, QJsonDocument &jsonDocument)
951 {
952 jsonDocument = QJsonDocument();
953 QFile depsFile(depsFilePath);
954 QVERIFY2(depsFile.open(QFile::ReadOnly), qPrintable(depsFile.errorString()));
955 QJsonParseError jsonerror;
956 jsonDocument = QJsonDocument::fromJson(depsFile.readAll(), &jsonerror);
957 if (jsonerror.error != QJsonParseError::NoError) {
958 qDebug() << jsonerror.errorString();
959 QFAIL("JSON parsing failed.");
960 }
961 }
962
dependenciesProperty()963 void TestBlackbox::dependenciesProperty()
964 {
965 QDir::setCurrent(testDataDir + QLatin1String("/dependenciesProperty"));
966 QCOMPARE(runQbs(), 0);
967 const QString depsFile(relativeProductBuildDir("product1") + "/product1.deps");
968 QJsonDocument jsondoc;
969 readDepsOutput(depsFile, jsondoc);
970 QVERIFY(jsondoc.isArray());
971 QJsonArray dependencies = jsondoc.array();
972 QCOMPARE(dependencies.size(), 2);
973 QJsonObject product2 = findByName(dependencies, QStringLiteral("product2"));
974 QJsonArray product2_type = product2.value(QStringLiteral("type")).toArray();
975 QCOMPARE(product2_type.size(), 1);
976 QCOMPARE(product2_type.first().toString(), QLatin1String("application"));
977 QCOMPARE(product2.value(QLatin1String("narf")).toString(), QLatin1String("zort"));
978 QJsonArray product2_cppArtifacts
979 = product2.value("artifacts").toObject().value("cpp").toArray();
980 QCOMPARE(product2_cppArtifacts.size(), 1);
981 QJsonArray product2_deps = product2.value(QStringLiteral("dependencies")).toArray();
982 QVERIFY(!product2_deps.empty());
983 QJsonObject product2_qbs = findByName(product2_deps, QStringLiteral("qbs"));
984 QVERIFY(!product2_qbs.empty());
985 QJsonObject product2_cpp = findByName(product2_deps, QStringLiteral("cpp"));
986 QJsonArray product2_cpp_defines = product2_cpp.value(QLatin1String("defines")).toArray();
987 QCOMPARE(product2_cpp_defines.size(), 1);
988 QCOMPARE(product2_cpp_defines.first().toString(), QLatin1String("SMURF"));
989 QJsonArray cpp_dependencies = product2_cpp.value("dependencies").toArray();
990 QVERIFY(!cpp_dependencies.isEmpty());
991 int qbsCount = 0;
992 for (const auto dep : cpp_dependencies) {
993 if (dep.toObject().value("name").toString() == "qbs")
994 ++qbsCount;
995 }
996 QCOMPARE(qbsCount, 1);
997
998 // Add new dependency, check that command is re-run.
999 const QString projectFile("dependenciesProperty.qbs");
1000 WAIT_FOR_NEW_TIMESTAMP();
1001 REPLACE_IN_FILE(projectFile, "// Depends { name: 'newDependency' }",
1002 "Depends { name: 'newDependency' }");
1003 QCOMPARE(runQbs(), 0);
1004 QVERIFY2(m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData());
1005 readDepsOutput(depsFile, jsondoc);
1006 dependencies = jsondoc.array();
1007 QCOMPARE(dependencies.size(), 3);
1008
1009 // Add new Depends item that does not actually introduce a new dependency, check
1010 // that command is not re-run.
1011 WAIT_FOR_NEW_TIMESTAMP();
1012 REPLACE_IN_FILE(projectFile, "// Depends { name: 'product2' }", "Depends { name: 'product2' }");
1013 QCOMPARE(runQbs(), 0);
1014 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
1015 QVERIFY2(!m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData());
1016 readDepsOutput(depsFile, jsondoc);
1017 dependencies = jsondoc.array();
1018 QCOMPARE(dependencies.size(), 3);
1019
1020 // Change property of dependency, check that command is re-run.
1021 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"products.product2.narf:zortofsky"})),
1022 0);
1023 QCOMPARE(runQbs(), 0);
1024 QVERIFY2(!m_qbsStdout.contains("compiling product2.cpp"), m_qbsStdout.constData());
1025 QVERIFY2(m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData());
1026 readDepsOutput(depsFile, jsondoc);
1027 dependencies = jsondoc.array();
1028 QCOMPARE(dependencies.size(), 3);
1029 product2 = findByName(dependencies, QStringLiteral("product2"));
1030 QCOMPARE(product2.value(QLatin1String("narf")).toString(), QLatin1String("zortofsky"));
1031
1032 // Change module property of dependency, check that command is re-run.
1033 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"products.product2.narf:zortofsky",
1034 "products.product2.cpp.defines:DIGEDAG"})), 0);
1035 QCOMPARE(runQbs(), 0);
1036 QVERIFY2(m_qbsStdout.contains("compiling product2.cpp"), m_qbsStdout.constData());
1037 QVERIFY2(m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData());
1038 readDepsOutput(depsFile, jsondoc);
1039 dependencies = jsondoc.array();
1040 QCOMPARE(dependencies.size(), 3);
1041 product2 = findByName(dependencies, QStringLiteral("product2"));
1042 product2_deps = product2.value(QStringLiteral("dependencies")).toArray();
1043 product2_cpp = findByName(product2_deps, QStringLiteral("cpp"));
1044 product2_cpp_defines = product2_cpp.value(QStringLiteral("defines")).toArray();
1045 QCOMPARE(product2_cpp_defines.size(), 1);
1046 QCOMPARE(product2_cpp_defines.first().toString(), QLatin1String("DIGEDAG"));
1047 }
1048
dependencyScanningLoop()1049 void TestBlackbox::dependencyScanningLoop()
1050 {
1051 QDir::setCurrent(testDataDir + "/dependency-scanning-loop");
1052 QCOMPARE(runQbs(), 0);
1053 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
1054 }
1055
deprecatedProperty()1056 void TestBlackbox::deprecatedProperty()
1057 {
1058 QDir::setCurrent(testDataDir + "/deprecated-property");
1059 QbsRunParameters params(QStringList("-q"));
1060 params.expectFailure = true;
1061 QVERIFY(runQbs(params) != 0);
1062 m_qbsStderr = QDir::fromNativeSeparators(QString::fromLocal8Bit(m_qbsStderr)).toLocal8Bit();
1063 QVERIFY2(m_qbsStderr.contains("deprecated-property.qbs:6:24 The property 'oldProp' is "
1064 "deprecated and will be removed in Qbs 99.9.0."), m_qbsStderr.constData());
1065 QVERIFY2(m_qbsStderr.contains("deprecated-property.qbs:7:28 The property 'veryOldProp' can no "
1066 "longer be used. It was removed in Qbs 1.3.0."), m_qbsStderr.constData());
1067 QVERIFY2(m_qbsStderr.contains("Property 'forgottenProp' was scheduled for removal in version "
1068 "1.8.0, but is still present."), m_qbsStderr.constData());
1069 QVERIFY2(m_qbsStderr.contains("themodule/m.qbs:22:5 Removal version for 'forgottenProp' "
1070 "specified here."), m_qbsStderr.constData());
1071 QVERIFY2(m_qbsStderr.count("Use newProp instead.") == 2, m_qbsStderr.constData());
1072 QVERIFY2(m_qbsStderr.count("is deprecated") == 1, m_qbsStderr.constData());
1073 QVERIFY2(m_qbsStderr.count("was removed") == 1, m_qbsStderr.constData());
1074 }
1075
disappearedProfile()1076 void TestBlackbox::disappearedProfile()
1077 {
1078 QDir::setCurrent(testDataDir + "/disappeared-profile");
1079 QbsRunParameters resolveParams;
1080
1081 // First, we need to fail, because we don't tell qbs where the module is.
1082 resolveParams.expectFailure = true;
1083 QVERIFY(runQbs(resolveParams) != 0);
1084
1085 // Now we set up a profile with all the necessary information, and qbs succeeds.
1086 qbs::Settings settings(QDir::currentPath() + "/settings-dir");
1087 qbs::Profile profile("p", &settings);
1088 profile.setValue("m.p1", "p1 from profile");
1089 profile.setValue("m.p2", "p2 from profile");
1090 profile.setValue("preferences.qbsSearchPaths",
1091 QStringList({QDir::currentPath() + "/modules-dir"}));
1092 settings.sync();
1093 resolveParams.command = "resolve";
1094 resolveParams.expectFailure = false;
1095 resolveParams.settingsDir = settings.baseDirectory();
1096 resolveParams.profile = profile.name();
1097 QCOMPARE(runQbs(resolveParams), 0);
1098
1099 // Now we change a property in the profile, but because we don't use the "resolve" command,
1100 // the old profile contents stored in the build graph are used.
1101 profile.setValue("m.p2", "p2 new from profile");
1102 settings.sync();
1103 QbsRunParameters buildParams;
1104 buildParams.profile.clear();
1105 QCOMPARE(runQbs(buildParams), 0);
1106 QVERIFY2(m_qbsStdout.contains("Creating dummy1.txt with p1 from profile"),
1107 m_qbsStdout.constData());
1108 QVERIFY2(m_qbsStdout.contains("Creating dummy2.txt with p2 from profile"),
1109 m_qbsStdout.constData());
1110
1111 // Now we do use the "resolve" command, so the new property value is taken into account.
1112 QCOMPARE(runQbs(resolveParams), 0);
1113 QCOMPARE(runQbs(buildParams), 0);
1114 QVERIFY2(!m_qbsStdout.contains("Creating dummy1.txt"), m_qbsStdout.constData());
1115 QVERIFY2(m_qbsStdout.contains("Creating dummy2.txt with p2 new from profile"),
1116 m_qbsStdout.constData());
1117
1118 // Now we change the profile again without a "resolve" command. However, this time we
1119 // force re-resolving indirectly by changing a project file. The updated property value
1120 // must still not be taken into account.
1121 profile.setValue("m.p1", "p1 new from profile");
1122 settings.sync();
1123 WAIT_FOR_NEW_TIMESTAMP();
1124 REPLACE_IN_FILE("modules-dir/modules/m/m.qbs", "property string p1",
1125 "property string p1: 'p1 from module'");
1126 QCOMPARE(runQbs(buildParams), 0);
1127 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
1128 QVERIFY2(!m_qbsStdout.contains("Creating dummy1.txt"), m_qbsStdout.constData());
1129 QVERIFY2(!m_qbsStdout.contains("Creating dummy2.txt"), m_qbsStdout.constData());
1130
1131 // Now we run the "resolve" command without giving the necessary settings path to find
1132 // the profile.
1133 resolveParams.expectFailure = true;
1134 resolveParams.settingsDir.clear();
1135 resolveParams.profile.clear();
1136 QVERIFY(runQbs(resolveParams) != 0);
1137 QVERIFY2(m_qbsStderr.contains("profile"), m_qbsStderr.constData());
1138 }
1139
discardUnusedData()1140 void TestBlackbox::discardUnusedData()
1141 {
1142 QDir::setCurrent(testDataDir + "/discard-unused-data");
1143 rmDirR(relativeBuildDir());
1144 QFETCH(QString, discardString);
1145 QFETCH(bool, symbolPresent);
1146 QbsRunParameters params;
1147 if (!discardString.isEmpty())
1148 params.arguments << ("modules.cpp.discardUnusedData:" + discardString);
1149 QCOMPARE(runQbs(params), 0);
1150 QVERIFY2(m_qbsStdout.contains("is Darwin"), m_qbsStdout.constData());
1151 const bool isDarwin = m_qbsStdout.contains("is Darwin: true");
1152 const QString output = QString::fromLocal8Bit(m_qbsStdout);
1153 const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"),
1154 QRegularExpression::DotMatchesEverythingOption);
1155 const QRegularExpressionMatch match = pattern.match(output);
1156 QVERIFY2(match.hasMatch(), qPrintable(output));
1157 QCOMPARE(match.lastCapturedIndex(), 1);
1158 const QString nmPath = match.captured(1);
1159 if (!QFile::exists(nmPath))
1160 QSKIP("Cannot check for symbol presence: No nm found.");
1161 QProcess nm;
1162 nm.start(nmPath, QStringList(QDir::currentPath() + '/' + relativeExecutableFilePath("app")));
1163 QVERIFY(nm.waitForStarted());
1164 QVERIFY(nm.waitForFinished());
1165 const QByteArray nmOutput = nm.readAllStandardOutput();
1166 QVERIFY2(nm.exitCode() == 0, nm.readAllStandardError().constData());
1167 if (!symbolPresent && !isDarwin)
1168 QSKIP("Unused symbol detection only supported on Darwin");
1169 QVERIFY2(nmOutput.contains("unusedFunc") == symbolPresent, nmOutput.constData());
1170 }
1171
discardUnusedData_data()1172 void TestBlackbox::discardUnusedData_data()
1173 {
1174 QTest::addColumn<QString>("discardString");
1175 QTest::addColumn<bool>("symbolPresent");
1176
1177 QTest::newRow("discard") << QString("true") << false;
1178 QTest::newRow("don't discard") << QString("false") << true;
1179 QTest::newRow("default") << QString() << true;
1180 }
1181
driverLinkerFlags()1182 void TestBlackbox::driverLinkerFlags()
1183 {
1184 QDir::setCurrent(testDataDir + QLatin1String("/driver-linker-flags"));
1185 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0);
1186 if (!m_qbsStdout.contains("toolchain is GCC-like"))
1187 QSKIP("Test applies on GCC-like toolchains only");
1188 QFETCH(QString, linkerMode);
1189 QFETCH(bool, expectDriverOption);
1190 const QString linkerModeArg = "modules.cpp.linkerMode:" + linkerMode;
1191 QCOMPARE(runQbs(QStringList({"-n", "--command-echo-mode", "command-line", linkerModeArg})), 0);
1192 const QByteArray driverArg = "-nostartfiles";
1193 const QByteArrayList output = m_qbsStdout.split('\n');
1194 QByteArray compileLine;
1195 QByteArray linkLine;
1196 for (const QByteArray &line : output) {
1197 if (line.contains(" -c "))
1198 compileLine = line;
1199 else if (line.contains("main.cpp.o"))
1200 linkLine = line;
1201 }
1202 QVERIFY(!compileLine.isEmpty());
1203 QVERIFY(!linkLine.isEmpty());
1204 QVERIFY2(!compileLine.contains(driverArg), compileLine.constData());
1205 QVERIFY2(linkLine.contains(driverArg) == expectDriverOption, linkLine.constData());
1206 }
1207
driverLinkerFlags_data()1208 void TestBlackbox::driverLinkerFlags_data()
1209 {
1210 QTest::addColumn<QString>("linkerMode");
1211 QTest::addColumn<bool>("expectDriverOption");
1212
1213 QTest::newRow("link using compiler driver") << "automatic" << true;
1214 QTest::newRow("link using linker") << "manual" << false;
1215 }
1216
dynamicLibraryInModule()1217 void TestBlackbox::dynamicLibraryInModule()
1218 {
1219 QDir::setCurrent(testDataDir + "/dynamic-library-in-module");
1220 const QString installRootSpec = QString("qbs.installRoot:") + QDir::currentPath();
1221 QbsRunParameters libParams(QStringList({"-f", "thelibs.qbs", installRootSpec}));
1222 libParams.buildDirectory = "libbuild";
1223 QCOMPARE(runQbs(libParams), 0);
1224 QbsRunParameters appParams("build", QStringList({"-f", "theapp.qbs", installRootSpec}));
1225 appParams.buildDirectory = "appbuild";
1226 QCOMPARE(runQbs(appParams), 0);
1227 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
1228 QSKIP("Cannot run binaries in cross-compiled build");
1229 appParams.command = "run";
1230 QCOMPARE(runQbs(appParams), 0);
1231 QVERIFY2(m_qbsStdout.contains("Hello from thelib"), m_qbsStdout.constData());
1232 QVERIFY2(m_qbsStdout.contains("Hello from theotherlib"), m_qbsStdout.constData());
1233 QVERIFY2(!m_qbsStdout.contains("thirdlib"), m_qbsStdout.constData());
1234 QVERIFY(!QFileInfo::exists(appParams.buildDirectory + '/'
1235 + qbs::InstallOptions::defaultInstallRoot()));
1236 }
1237
symlinkRemoval()1238 void TestBlackbox::symlinkRemoval()
1239 {
1240 if (HostOsInfo::isWindowsHost())
1241 QSKIP("No symlink support on Windows.");
1242 QDir::setCurrent(testDataDir + "/symlink-removal");
1243 QVERIFY(QDir::current().mkdir("dir1"));
1244 QVERIFY(QDir::current().mkdir("dir2"));
1245 QVERIFY(QFile::link("dir2", "dir1/broken-link"));
1246 QVERIFY(QFile::link(QFileInfo("dir2").absoluteFilePath(), "dir1/valid-link-to-dir"));
1247 QVERIFY(QFile::link(QFileInfo("symlink-removal.qbs").absoluteFilePath(),
1248 "dir1/valid-link-to-file"));
1249 QCOMPARE(runQbs(), 0);
1250 QVERIFY(!QFile::exists("dir1"));
1251 QVERIFY(QFile::exists("dir2"));
1252 QVERIFY(QFile::exists("symlink-removal.qbs"));
1253 }
1254
usingsAsSoleInputsNonMultiplexed()1255 void TestBlackbox::usingsAsSoleInputsNonMultiplexed()
1256 {
1257 QDir::setCurrent(testDataDir + QLatin1String("/usings-as-sole-inputs-non-multiplexed"));
1258 QCOMPARE(runQbs(), 0);
1259 const QString p3BuildDir = relativeProductBuildDir("p3");
1260 QVERIFY(regularFileExists(p3BuildDir + "/custom1.out.plus"));
1261 QVERIFY(regularFileExists(p3BuildDir + "/custom2.out.plus"));
1262 }
1263
variantSuffix()1264 void TestBlackbox::variantSuffix()
1265 {
1266 QDir::setCurrent(testDataDir + "/variant-suffix");
1267 QFETCH(bool, multiplex);
1268 QFETCH(bool, expectFailure);
1269 QFETCH(QString, variantSuffix);
1270 QFETCH(QString, buildVariant);
1271 QFETCH(QVariantMap, fileNames);
1272 QbsRunParameters params;
1273 params.command = "resolve";
1274 params.arguments << "--force-probe-execution";
1275 if (multiplex)
1276 params.arguments << "products.l.multiplex:true";
1277 else
1278 params.arguments << ("modules.qbs.buildVariant:" + buildVariant);
1279 if (!variantSuffix.isEmpty())
1280 params.arguments << ("modules.cpp.variantSuffix:" + variantSuffix);
1281 QCOMPARE(runQbs(params), 0);
1282 const QString fileNameMapKey = m_qbsStdout.contains("is Windows: true")
1283 ? "windows" : m_qbsStdout.contains("is Apple: true") ? "apple" : "unix";
1284 if (variantSuffix.isEmpty() && multiplex && fileNameMapKey == "unix")
1285 expectFailure = true;
1286 params.command = "build";
1287 params.expectFailure = expectFailure;
1288 params.arguments = QStringList("--clean-install-root");
1289 QCOMPARE(runQbs(params) == 0, !expectFailure);
1290 if (expectFailure)
1291 return;
1292 const QStringList fileNameList = fileNames.value(fileNameMapKey).toStringList();
1293 for (const QString &fileName : fileNameList) {
1294 QFile libFile("default/install-root/lib/" + fileName);
1295 QVERIFY2(libFile.exists(), qPrintable(libFile.fileName()));
1296 }
1297 }
1298
variantSuffix_data()1299 void TestBlackbox::variantSuffix_data()
1300 {
1301 QTest::addColumn<bool>("multiplex");
1302 QTest::addColumn<bool>("expectFailure");
1303 QTest::addColumn<QString>("variantSuffix");
1304 QTest::addColumn<QString>("buildVariant");
1305 QTest::addColumn<QVariantMap>("fileNames");
1306
1307 QTest::newRow("default suffix, debug") << false << false << QString() << QString("debug")
1308 << QVariantMap({std::make_pair(QString("windows"), QStringList("libl.ext")),
1309 std::make_pair(QString("apple"), QStringList("libl.ext")),
1310 std::make_pair(QString("unix"), QStringList("libl.ext"))});
1311 QTest::newRow("default suffix, release") << false << false << QString() << QString("release")
1312 << QVariantMap({std::make_pair(QString("windows"), QStringList("libl.ext")),
1313 std::make_pair(QString("apple"), QStringList("libl.ext")),
1314 std::make_pair(QString("unix"), QStringList("libl.ext"))});
1315 QTest::newRow("custom suffix, debug") << false << false << QString("blubb") << QString("debug")
1316 << QVariantMap({std::make_pair(QString("windows"), QStringList("liblblubb.ext")),
1317 std::make_pair(QString("apple"), QStringList("liblblubb.ext")),
1318 std::make_pair(QString("unix"), QStringList("liblblubb.ext"))});
1319 QTest::newRow("custom suffix, release") << false << false << QString("blubb")
1320 << QString("release")
1321 << QVariantMap({std::make_pair(QString("windows"), QStringList("liblblubb.ext")),
1322 std::make_pair(QString("apple"), QStringList("liblblubb.ext")),
1323 std::make_pair(QString("unix"), QStringList("liblblubb.ext"))});
1324 QTest::newRow("default suffix, multiplex") << true << false << QString() << QString()
1325 << QVariantMap({std::make_pair(QString("windows"),
1326 QStringList({"libl.ext", "libld.ext"})),
1327 std::make_pair(QString("apple"),
1328 QStringList({"libl.ext", "libl_debug.ext"})),
1329 std::make_pair(QString("unix"), QStringList())});
1330 QTest::newRow("custom suffix, multiplex") << true << true << QString("blubb") << QString()
1331 << QVariantMap({std::make_pair(QString("windows"), QStringList()),
1332 std::make_pair(QString("apple"), QStringList()),
1333 std::make_pair(QString("unix"), QStringList())});
1334 }
1335
waitForProcessSuccess(QProcess & p)1336 static bool waitForProcessSuccess(QProcess &p)
1337 {
1338 if (!p.waitForStarted() || !p.waitForFinished()) {
1339 qDebug() << p.errorString();
1340 return false;
1341 }
1342 if (p.exitCode() != 0) {
1343 qDebug() << p.readAllStandardError();
1344 return false;
1345 }
1346 return true;
1347 }
1348
vcsGit()1349 void TestBlackbox::vcsGit()
1350 {
1351 const QString gitFilePath = findExecutable(QStringList("git"));
1352 if (gitFilePath.isEmpty())
1353 QSKIP("git not found");
1354
1355 // Set up repo.
1356 QTemporaryDir repoDir;
1357 QVERIFY(repoDir.isValid());
1358 ccp(testDataDir + "/vcs", repoDir.path());
1359 QDir::setCurrent(repoDir.path());
1360
1361 QProcess git;
1362 git.start(gitFilePath, QStringList("init"));
1363 QVERIFY(waitForProcessSuccess(git));
1364 git.start(gitFilePath, QStringList({"config", "user.name", "My Name"}));
1365 QVERIFY(waitForProcessSuccess(git));
1366 git.start(gitFilePath, QStringList({"config", "user.email", "me@example.com"}));
1367 QVERIFY(waitForProcessSuccess(git));
1368
1369 const auto getRepoStateFromApp = [this] {
1370 const int startIndex = m_qbsStdout.indexOf("__");
1371 if (startIndex == -1)
1372 return QByteArray();
1373 const int endIndex = m_qbsStdout.indexOf("__", startIndex + 2);
1374 if (endIndex == -1)
1375 return QByteArray();
1376 return m_qbsStdout.mid(startIndex + 2, endIndex - startIndex - 2);
1377 };
1378
1379 QCOMPARE(runQbs({"resolve", {"-f", repoDir.path()}}), 0);
1380 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
1381 QSKIP("Cannot run binaries in cross-compiled build");
1382 // Run without git metadata.
1383 QbsRunParameters params("run", QStringList{"-f", repoDir.path()});
1384 params.workingDir = repoDir.path() + "/..";
1385 params.buildDirectory = repoDir.path();
1386 QCOMPARE(runQbs(params), 0);
1387 QVERIFY2(m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1388 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1389 QByteArray newRepoState = getRepoStateFromApp();
1390 QCOMPARE(newRepoState, QByteArray("none"));
1391 QByteArray oldRepoState = newRepoState;
1392
1393 // Initial commit
1394 git.start(gitFilePath, QStringList({"add", "main.cpp"}));
1395 QVERIFY(waitForProcessSuccess(git));
1396 git.start(gitFilePath, QStringList({"commit", "-m", "initial commit"}));
1397 QVERIFY(waitForProcessSuccess(git));
1398
1399 // Run with git metadata.
1400 WAIT_FOR_NEW_TIMESTAMP();
1401 QCOMPARE(runQbs(params), 0);
1402 QVERIFY2(m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1403 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1404 newRepoState = getRepoStateFromApp();
1405 QVERIFY(oldRepoState != newRepoState);
1406 oldRepoState = newRepoState;
1407
1408 // Run with no changes.
1409 QCOMPARE(runQbs(params), 0);
1410 QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1411 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1412 newRepoState = getRepoStateFromApp();
1413 QCOMPARE(oldRepoState, newRepoState);
1414
1415 // Run with changed source file.
1416 WAIT_FOR_NEW_TIMESTAMP();
1417 touch("main.cpp");
1418 QCOMPARE(runQbs(params), 0);
1419 QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1420 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1421 newRepoState = getRepoStateFromApp();
1422 QCOMPARE(oldRepoState, newRepoState);
1423
1424 // Add new file to repo.
1425 WAIT_FOR_NEW_TIMESTAMP();
1426 touch("blubb.txt");
1427 git.start(gitFilePath, QStringList({"add", "blubb.txt"}));
1428 QVERIFY(waitForProcessSuccess(git));
1429 git.start(gitFilePath, QStringList({"commit", "-m", "blubb!"}));
1430 QVERIFY(waitForProcessSuccess(git));
1431 QCOMPARE(runQbs(params), 0);
1432 QVERIFY2(m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1433 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1434 newRepoState = getRepoStateFromApp();
1435 QVERIFY(oldRepoState != newRepoState);
1436 }
1437
vcsSubversion()1438 void TestBlackbox::vcsSubversion()
1439 {
1440 const QString svnadminFilePath = findExecutable(QStringList("svnadmin"));
1441 if (svnadminFilePath.isEmpty())
1442 QSKIP("svnadmin not found");
1443 const QString svnFilePath = findExecutable(QStringList("svn"));
1444 if (svnFilePath.isEmpty())
1445 QSKIP("svn not found");
1446
1447 if (HostOsInfo::isWindowsHost() && qEnvironmentVariableIsSet("GITHUB_ACTIONS"))
1448 QSKIP("Skip this test when running on GitHub");
1449
1450 // Set up repo.
1451 QTemporaryDir repoDir;
1452 QVERIFY(repoDir.isValid());
1453 QProcess proc;
1454 proc.setWorkingDirectory(repoDir.path());
1455 proc.start(svnadminFilePath, QStringList({"create", "vcstest"}));
1456 QVERIFY(waitForProcessSuccess(proc));
1457 const QString projectUrl = "file://" + repoDir.path() + "/vcstest/trunk";
1458 proc.start(svnFilePath, QStringList({"import", testDataDir + "/vcs", projectUrl, "-m",
1459 "initial import"}));
1460 QVERIFY(waitForProcessSuccess(proc));
1461 QTemporaryDir checkoutDir;
1462 QVERIFY(checkoutDir.isValid());
1463 proc.setWorkingDirectory(checkoutDir.path());
1464 proc.start(svnFilePath, QStringList({"co", projectUrl, "."}));
1465 QVERIFY(waitForProcessSuccess(proc));
1466
1467 // Initial runs
1468 QDir::setCurrent(checkoutDir.path());
1469 QbsRunParameters failParams;
1470 failParams.command = "run";
1471 failParams.expectFailure = true;
1472 const int retval = runQbs(failParams);
1473 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
1474 QSKIP("Cannot run binaries in cross-compiled build");
1475 if (m_qbsStderr.contains("svn too old"))
1476 QSKIP("svn too old");
1477 QCOMPARE(retval, 0);
1478 QVERIFY2(m_qbsStdout.contains("__1__"), m_qbsStdout.constData());
1479 QCOMPARE(runQbs(QbsRunParameters("run")), 0);
1480 QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1481 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1482 QVERIFY2(m_qbsStdout.contains("__1__"), m_qbsStdout.constData());
1483
1484 // Run with changed source file.
1485 WAIT_FOR_NEW_TIMESTAMP();
1486 touch("main.cpp");
1487 QCOMPARE(runQbs(QbsRunParameters("run")), 0);
1488 QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData());
1489 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData());
1490 QVERIFY2(m_qbsStdout.contains("__1__"), m_qbsStdout.constData());
1491
1492 // Add new file to repo.
1493 WAIT_FOR_NEW_TIMESTAMP();
1494 touch("blubb.txt");
1495 proc.start(svnFilePath, QStringList({"add", "blubb.txt"}));
1496 QVERIFY(waitForProcessSuccess(proc));
1497 proc.start(svnFilePath, QStringList({"commit", "-m", "blubb!"}));
1498 QVERIFY(waitForProcessSuccess(proc));
1499 QCOMPARE(runQbs(QbsRunParameters("run")), 0);
1500 QVERIFY2(m_qbsStdout.contains("__2__"), m_qbsStdout.constData());
1501 }
1502
versionCheck()1503 void TestBlackbox::versionCheck()
1504 {
1505 QDir::setCurrent(testDataDir + "/versioncheck");
1506 QFETCH(QString, requestedMinVersion);
1507 QFETCH(QString, requestedMaxVersion);
1508 QFETCH(QString, actualVersion);
1509 QFETCH(QString, errorMessage);
1510 QbsRunParameters params;
1511 params.expectFailure = !errorMessage.isEmpty();
1512 params.arguments << "-n"
1513 << ("products.versioncheck.requestedMinVersion:'" + requestedMinVersion + "'")
1514 << ("products.versioncheck.requestedMaxVersion:'" + requestedMaxVersion + "'")
1515 << ("modules.lower.version:'" + actualVersion + "'");
1516 QCOMPARE(runQbs(params) == 0, errorMessage.isEmpty());
1517 if (params.expectFailure)
1518 QVERIFY2(QString(m_qbsStderr).contains(errorMessage), m_qbsStderr.constData());
1519 }
1520
versionCheck_data()1521 void TestBlackbox::versionCheck_data()
1522 {
1523 QTest::addColumn<QString>("requestedMinVersion");
1524 QTest::addColumn<QString>("requestedMaxVersion");
1525 QTest::addColumn<QString>("actualVersion");
1526 QTest::addColumn<QString>("errorMessage");
1527
1528 QTest::newRow("ok1") << "1.0" << "1.1" << "1.0" << QString();
1529 QTest::newRow("ok2") << "1.0" << "2.0.1" << "2.0" << QString();
1530 QTest::newRow("ok3") << "1.0" << "2.5" << "1.5" << QString();
1531 QTest::newRow("ok3") << "1.0" << "2.0" << "1.99" << QString();
1532 QTest::newRow("bad1") << "2.0" << "2.1" << "1.5" << "needs to be at least";
1533 QTest::newRow("bad2") << "2.0" << "3.0" << "1.5" << "needs to be at least";
1534 QTest::newRow("bad3") << "2.0" << "3.0" << "3.5" << "needs to be lower than";
1535 QTest::newRow("bad4") << "2.0" << "3.0" << "3.0" << "needs to be lower than";
1536
1537 // "bad" because the "higer" module has stronger requirements.
1538 QTest::newRow("bad5") << "0.1" << "0.9" << "0.5" << "Impossible version constraint";
1539 }
1540
versionScript()1541 void TestBlackbox::versionScript()
1542 {
1543 const SettingsPtr s = settings();
1544 Profile buildProfile(profileName(), s.get());
1545 QStringList toolchain = profileToolchain(buildProfile);
1546 if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux)
1547 QSKIP("version script test only applies to Linux");
1548 QDir::setCurrent(testDataDir + "/versionscript");
1549 QCOMPARE(runQbs(QbsRunParameters(QStringList("-q")
1550 << ("qbs.installRoot:" + QDir::currentPath()))), 0);
1551 const QString output = QString::fromLocal8Bit(m_qbsStderr);
1552 const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"),
1553 QRegularExpression::DotMatchesEverythingOption);
1554 const QRegularExpressionMatch match = pattern.match(output);
1555 QVERIFY2(match.hasMatch(), qPrintable(output));
1556 QCOMPARE(pattern.captureCount(), 1);
1557 const QString nmPath = match.captured(1);
1558 if (!QFile::exists(nmPath))
1559 QSKIP("Cannot check for symbol presence: No nm found.");
1560 QProcess nm;
1561 nm.start(nmPath, QStringList(QDir::currentPath() + "/libversionscript.so"));
1562 QVERIFY(nm.waitForStarted());
1563 QVERIFY(nm.waitForFinished());
1564 const QByteArray allSymbols = nm.readAllStandardOutput();
1565 QCOMPARE(nm.exitCode(), 0);
1566 QVERIFY2(allSymbols.contains("dummyLocal"), allSymbols.constData());
1567 QVERIFY2(allSymbols.contains("dummyGlobal"), allSymbols.constData());
1568 nm.start(nmPath, QStringList() << "-g" << QDir::currentPath() + "/libversionscript.so");
1569 QVERIFY(nm.waitForStarted());
1570 QVERIFY(nm.waitForFinished());
1571 const QByteArray globalSymbols = nm.readAllStandardOutput();
1572 QCOMPARE(nm.exitCode(), 0);
1573 QVERIFY2(!globalSymbols.contains("dummyLocal"), allSymbols.constData());
1574 QVERIFY2(globalSymbols.contains("dummyGlobal"), allSymbols.constData());
1575 }
1576
wholeArchive()1577 void TestBlackbox::wholeArchive()
1578 {
1579 QDir::setCurrent(testDataDir + "/whole-archive");
1580 QFETCH(QString, wholeArchiveString);
1581 QFETCH(bool, ruleInvalidationExpected);
1582 QFETCH(bool, dllLinkingExpected);
1583 const QbsRunParameters resolveParams("resolve",
1584 QStringList("products.dynamiclib.linkWholeArchive:" + wholeArchiveString));
1585 QCOMPARE(runQbs(QbsRunParameters(resolveParams)), 0);
1586 const bool linkerSupportsWholeArchive = m_qbsStdout.contains("can link whole archives");
1587 const bool linkerDoesNotSupportWholeArchive
1588 = m_qbsStdout.contains("cannot link whole archives");
1589 QVERIFY(linkerSupportsWholeArchive != linkerDoesNotSupportWholeArchive);
1590
1591 QCOMPARE(runQbs(QbsRunParameters(QStringList({ "-vvp", "dynamiclib" }))), 0);
1592 const bool wholeArchive = !wholeArchiveString.isEmpty();
1593 const bool outdatedVisualStudio = wholeArchive && linkerDoesNotSupportWholeArchive;
1594 const QByteArray invalidationOutput
1595 = "Value for property 'staticlib 1:cpp.linkWholeArchive' has changed.";
1596 if (!outdatedVisualStudio)
1597 QCOMPARE(m_qbsStderr.contains(invalidationOutput), ruleInvalidationExpected);
1598 QCOMPARE(m_qbsStdout.contains("linking"), dllLinkingExpected && !outdatedVisualStudio);
1599 QbsRunParameters buildParams(QStringList("-p"));
1600 buildParams.expectFailure = !wholeArchive || outdatedVisualStudio;
1601 buildParams.arguments << "app1";
1602 QCOMPARE(runQbs(QbsRunParameters(buildParams)) == 0, wholeArchive && !outdatedVisualStudio);
1603 buildParams.arguments.last() = "app2";
1604 QCOMPARE(runQbs(QbsRunParameters(buildParams)) == 0, wholeArchive && !outdatedVisualStudio);
1605 buildParams.arguments.last() = "app4";
1606 QCOMPARE(runQbs(QbsRunParameters(buildParams)) == 0, wholeArchive && !outdatedVisualStudio);
1607 buildParams.arguments.last() = "app3";
1608 buildParams.expectFailure = true;
1609 QVERIFY(runQbs(QbsRunParameters(buildParams)) != 0);
1610 }
1611
wholeArchive_data()1612 void TestBlackbox::wholeArchive_data()
1613 {
1614 QTest::addColumn<QString>("wholeArchiveString");
1615 QTest::addColumn<bool>("ruleInvalidationExpected");
1616 QTest::addColumn<bool>("dllLinkingExpected");
1617 QTest::newRow("link normally") << QString() << false << true;
1618 QTest::newRow("link whole archive") << "true" << true << true;
1619 QTest::newRow("link whole archive again") << "notfalse" << false << false;
1620 }
1621
symlinkExists(const QString & linkFilePath)1622 static bool symlinkExists(const QString &linkFilePath)
1623 {
1624 return QFileInfo(linkFilePath).isSymLink();
1625 }
1626
clean()1627 void TestBlackbox::clean()
1628 {
1629 const QString appObjectFilePath = relativeProductBuildDir("app") + '/' + inputDirHash(".")
1630 + objectFileName("/main.cpp", profileName());
1631 const QString appExeFilePath = relativeExecutableFilePath("app");
1632 const QString depObjectFilePath = relativeProductBuildDir("dep") + '/' + inputDirHash(".")
1633 + objectFileName("/dep.cpp", profileName());
1634 const QString depLibBase = relativeProductBuildDir("dep")
1635 + '/' + QBS_HOST_DYNAMICLIB_PREFIX + "dep";
1636 QString depLibFilePath;
1637 QStringList symlinks;
1638 if (qbs::Internal::HostOsInfo::isMacosHost()) {
1639 depLibFilePath = depLibBase + ".1.1.0" + QBS_HOST_DYNAMICLIB_SUFFIX;
1640 symlinks << depLibBase + ".1.1" + QBS_HOST_DYNAMICLIB_SUFFIX
1641 << depLibBase + ".1" + QBS_HOST_DYNAMICLIB_SUFFIX
1642 << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX;
1643 } else if (qbs::Internal::HostOsInfo::isAnyUnixHost()) {
1644 depLibFilePath = depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX + ".1.1.0";
1645 symlinks << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX + ".1.1"
1646 << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX + ".1"
1647 << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX;
1648 } else {
1649 depLibFilePath = depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX;
1650 }
1651
1652 QDir::setCurrent(testDataDir + "/clean");
1653
1654 // Can't clean without a build graph.
1655 QbsRunParameters failParams("clean");
1656 failParams.expectFailure = true;
1657 QVERIFY(runQbs(failParams) != 0);
1658
1659 // Default behavior: Remove all.
1660 QCOMPARE(runQbs(), 0);
1661 QVERIFY(regularFileExists(appObjectFilePath));
1662 QVERIFY(regularFileExists(appExeFilePath));
1663 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"))), 0);
1664 QVERIFY(!QFile(appObjectFilePath).exists());
1665 QVERIFY(!QFile(appExeFilePath).exists());
1666 QVERIFY(!QFile(depObjectFilePath).exists());
1667 QVERIFY(!QFile(depLibFilePath).exists());
1668 for (const QString &symLink : qAsConst(symlinks))
1669 QVERIFY2(!symlinkExists(symLink), qPrintable(symLink));
1670
1671 // Remove all, with a forced re-resolve in between.
1672 // This checks that rescuable artifacts are also removed.
1673 QCOMPARE(runQbs(QbsRunParameters("resolve",
1674 QStringList() << "modules.cpp.optimization:none")), 0);
1675 QCOMPARE(runQbs(), 0);
1676 QVERIFY(regularFileExists(appObjectFilePath));
1677 QVERIFY(regularFileExists(appExeFilePath));
1678 QCOMPARE(runQbs(QbsRunParameters("resolve",
1679 QStringList() << "modules.cpp.optimization:fast")), 0);
1680 QVERIFY(regularFileExists(appObjectFilePath));
1681 QVERIFY(regularFileExists(appExeFilePath));
1682 QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
1683 QVERIFY(!QFile(appObjectFilePath).exists());
1684 QVERIFY(!QFile(appExeFilePath).exists());
1685 QVERIFY(!QFile(depObjectFilePath).exists());
1686 QVERIFY(!QFile(depLibFilePath).exists());
1687 for (const QString &symLink : qAsConst(symlinks))
1688 QVERIFY2(!symlinkExists(symLink), qPrintable(symLink));
1689
1690 // Dry run.
1691 QCOMPARE(runQbs(), 0);
1692 QVERIFY(regularFileExists(appObjectFilePath));
1693 QVERIFY(regularFileExists(appExeFilePath));
1694 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"), QStringList("-n"))), 0);
1695 QVERIFY(regularFileExists(appObjectFilePath));
1696 QVERIFY(regularFileExists(appExeFilePath));
1697 QVERIFY(regularFileExists(depObjectFilePath));
1698 QVERIFY(regularFileExists(depLibFilePath));
1699 for (const QString &symLink : qAsConst(symlinks))
1700 QVERIFY2(symlinkExists(symLink), qPrintable(symLink));
1701
1702 // Product-wise, dependency only.
1703 QCOMPARE(runQbs(), 0);
1704 QVERIFY(regularFileExists(appObjectFilePath));
1705 QVERIFY(regularFileExists(appExeFilePath));
1706 QVERIFY(regularFileExists(depObjectFilePath));
1707 QVERIFY(regularFileExists(depLibFilePath));
1708 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"), QStringList("-p") << "dep")), 0);
1709 QVERIFY(regularFileExists(appObjectFilePath));
1710 QVERIFY(regularFileExists(appExeFilePath));
1711 QVERIFY(!QFile(depObjectFilePath).exists());
1712 QVERIFY(!QFile(depLibFilePath).exists());
1713 for (const QString &symLink : qAsConst(symlinks))
1714 QVERIFY2(!symlinkExists(symLink), qPrintable(symLink));
1715
1716 // Product-wise, dependent product only.
1717 QCOMPARE(runQbs(), 0);
1718 QVERIFY(regularFileExists(appObjectFilePath));
1719 QVERIFY(regularFileExists(appExeFilePath));
1720 QVERIFY(regularFileExists(depObjectFilePath));
1721 QVERIFY(regularFileExists(depLibFilePath));
1722 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"), QStringList("-p") << "app")), 0);
1723 QVERIFY(!QFile(appObjectFilePath).exists());
1724 QVERIFY(!QFile(appExeFilePath).exists());
1725 QVERIFY(regularFileExists(depObjectFilePath));
1726 QVERIFY(regularFileExists(depLibFilePath));
1727 for (const QString &symLink : qAsConst(symlinks))
1728 QVERIFY2(symlinkExists(symLink), qPrintable(symLink));
1729 }
1730
concurrentExecutor()1731 void TestBlackbox::concurrentExecutor()
1732 {
1733 QDir::setCurrent(testDataDir + "/concurrent-executor");
1734 QCOMPARE(runQbs(QStringList() << "-j" << "2"), 0);
1735 QVERIFY2(!m_qbsStderr.contains("ASSERT"), m_qbsStderr.constData());
1736 }
1737
conditionalExport()1738 void TestBlackbox::conditionalExport()
1739 {
1740 QDir::setCurrent(testDataDir + "/conditional-export");
1741 QbsRunParameters params;
1742 params.expectFailure = true;
1743 QVERIFY(runQbs(params) != 0);
1744 QVERIFY2(m_qbsStderr.contains("missing define"), m_qbsStderr.constData());
1745
1746 params.expectFailure = false;
1747 params.arguments << "project.enableExport:true";
1748 params.command = "resolve";
1749 QCOMPARE(runQbs(params), 0);
1750 params.command = "build";
1751 QCOMPARE(runQbs(params), 0);
1752 }
1753
conditionalFileTagger()1754 void TestBlackbox::conditionalFileTagger()
1755 {
1756 QDir::setCurrent(testDataDir + "/conditional-filetagger");
1757 QbsRunParameters params(QStringList("products.theApp.enableTagger:false"));
1758 QCOMPARE(runQbs(params), 0);
1759 QVERIFY(!m_qbsStdout.contains("compiling"));
1760 params.arguments = QStringList("products.theApp.enableTagger:true");
1761 params.command = "resolve";
1762 QCOMPARE(runQbs(params), 0);
1763 params.command = "build";
1764 QCOMPARE(runQbs(params), 0);
1765 QVERIFY(m_qbsStdout.contains("compiling"));
1766 }
1767
configure()1768 void TestBlackbox::configure()
1769 {
1770 QDir::setCurrent(testDataDir + "/configure");
1771 QCOMPARE(runQbs({"resolve"}), 0);
1772 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
1773 QSKIP("Cannot run binaries in cross-compiled build");
1774 QbsRunParameters params;
1775 params.command = "run";
1776 QCOMPARE(runQbs(params), 0);
1777 QVERIFY2(m_qbsStdout.contains("Configured at"), m_qbsStdout.constData());
1778 }
1779
conflictingArtifacts()1780 void TestBlackbox::conflictingArtifacts()
1781 {
1782 QDir::setCurrent(testDataDir + "/conflicting-artifacts");
1783 QbsRunParameters params(QStringList() << "-n");
1784 params.expectFailure = true;
1785 QVERIFY(runQbs(params) != 0);
1786 QVERIFY2(m_qbsStderr.contains("Conflicting artifacts"), m_qbsStderr.constData());
1787 }
1788
cxxLanguageVersion()1789 void TestBlackbox::cxxLanguageVersion()
1790 {
1791 QDir::setCurrent(testDataDir + "/cxx-language-version");
1792 rmDirR(relativeBuildDir());
1793 QFETCH(QString, version);
1794 QFETCH(QVariantMap, requiredFlags);
1795 QFETCH(QVariantMap, forbiddenFlags);
1796 QbsRunParameters resolveParams;
1797 resolveParams.command = "resolve";
1798 resolveParams.arguments << "--force-probe-execution";
1799 resolveParams.arguments << "modules.cpp.useLanguageVersionFallback:true";
1800 if (!version.isEmpty())
1801 resolveParams.arguments << ("modules.cpp.cxxLanguageVersion:" + version);
1802 QCOMPARE(runQbs(resolveParams), 0);
1803 QString mapKey;
1804 if (version == "c++17" && m_qbsStdout.contains("is even newer MSVC: true"))
1805 mapKey = "msvc-brandnew";
1806 else if (m_qbsStdout.contains("is newer MSVC: true"))
1807 mapKey = "msvc-new";
1808 else if (m_qbsStdout.contains("is older MSVC: true"))
1809 mapKey = "msvc_old";
1810 else if (m_qbsStdout.contains("is GCC: true"))
1811 mapKey = "gcc";
1812 QVERIFY2(!mapKey.isEmpty(), m_qbsStdout.constData());
1813 QbsRunParameters buildParams;
1814 buildParams.expectFailure = mapKey == "gcc" && (version == "c++17" || version == "c++21");
1815 buildParams.arguments = QStringList({"--command-echo-mode", "command-line"});
1816 const int retVal = runQbs(buildParams);
1817 if (!buildParams.expectFailure)
1818 QCOMPARE(retVal, 0);
1819 const QString requiredFlag = requiredFlags.value(mapKey).toString();
1820 if (!requiredFlag.isEmpty())
1821 QVERIFY2(m_qbsStdout.contains(requiredFlag.toLocal8Bit()), m_qbsStdout.constData());
1822 const QString forbiddenFlag = forbiddenFlags.value(mapKey).toString();
1823 if (!forbiddenFlag.isEmpty())
1824 QVERIFY2(!m_qbsStdout.contains(forbiddenFlag.toLocal8Bit()), m_qbsStdout.constData());
1825 }
1826
cxxLanguageVersion_data()1827 void TestBlackbox::cxxLanguageVersion_data()
1828 {
1829 QTest::addColumn<QString>("version");
1830 QTest::addColumn<QVariantMap>("requiredFlags");
1831 QTest::addColumn<QVariantMap>("forbiddenFlags");
1832
1833 QTest::newRow("C++98")
1834 << QString("c++98")
1835 << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++98"))})
1836 << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:")),
1837 std::make_pair(QString("msvc-new"), QString("/std:"))});
1838 QTest::newRow("C++11")
1839 << QString("c++11")
1840 << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++0x"))})
1841 << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:")),
1842 std::make_pair(QString("msvc-new"), QString("/std:"))});
1843 QTest::newRow("C++14")
1844 << QString("c++14")
1845 << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++1y")),
1846 std::make_pair(QString("msvc-new"), QString("/std:c++14"))
1847 })
1848 << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:"))});
1849 QTest::newRow("C++17")
1850 << QString("c++17")
1851 << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++1z")),
1852 std::make_pair(QString("msvc-new"), QString("/std:c++latest")),
1853 std::make_pair(QString("msvc-brandnew"), QString("/std:c++17"))
1854 })
1855 << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:"))});
1856 QTest::newRow("C++21")
1857 << QString("c++21")
1858 << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++21")),
1859 std::make_pair(QString("msvc-new"), QString("/std:c++latest"))
1860 })
1861 << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:"))});
1862 QTest::newRow("default")
1863 << QString()
1864 << QVariantMap()
1865 << QVariantMap({std::make_pair(QString("gcc"), QString("-std=")),
1866 std::make_pair(QString("msvc-old"), QString("/std:")),
1867 std::make_pair(QString("msvc-new"), QString("/std:"))});
1868 }
1869
conanfileProbe_data()1870 void TestBlackbox::conanfileProbe_data()
1871 {
1872 QTest::addColumn<bool>("forceFailure");
1873
1874 QTest::newRow("success") << false;
1875 QTest::newRow("failure") << true;
1876 }
1877
conanfileProbe()1878 void TestBlackbox::conanfileProbe()
1879 {
1880 QFETCH(bool, forceFailure);
1881
1882 QString executable = findExecutable({"conan"});
1883 if (executable.isEmpty())
1884 QSKIP("conan is not installed or not available in PATH.");
1885
1886 // We first build a dummy package testlib and use that as dependency
1887 // in the testapp package.
1888 QDir::setCurrent(testDataDir + "/conanfile-probe/testlib");
1889 QStringList arguments { "create", "-o", "opt=True", "-s", "os=AIX", ".",
1890 "testlib/1.2.3@qbs/testing" };
1891 QProcess conan;
1892 conan.start(executable, arguments);
1893 QVERIFY(waitForProcessSuccess(conan));
1894
1895 QDir::setCurrent(testDataDir + "/conanfile-probe/testapp");
1896 QCOMPARE(runQbs(QbsRunParameters("resolve",
1897 {"--force-probe-execution",
1898 QStringLiteral("projects.conanfile-probe-project.forceFailure:") +
1899 (forceFailure ? "true" : "false")})), forceFailure ? 1 : 0);
1900
1901 QFile file(relativeBuildDir() + "/results.json");
1902 QVERIFY(file.open(QIODevice::ReadOnly));
1903 QVariantMap actualResults = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
1904 const auto generatedFilesPath = actualResults.take("generatedFilesPath").toString();
1905 // We want to make sure that generatedFilesPath is under the project directory,
1906 // but we don't care about the actual name.
1907 QVERIFY(directoryExists(relativeBuildDir() + "/genconan/"
1908 + QFileInfo(generatedFilesPath).baseName()));
1909
1910 const QVariantMap expectedResults = {
1911 { "json", "TESTLIB_ENV_VAL" },
1912 { "dependencies", QVariantList{"testlib1", "testlib2"} },
1913 };
1914 QCOMPARE(actualResults, expectedResults);
1915 }
1916
cpuFeatures()1917 void TestBlackbox::cpuFeatures()
1918 {
1919 QDir::setCurrent(testDataDir + "/cpu-features");
1920 QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
1921 const bool isX86 = m_qbsStdout.contains("is x86: true");
1922 const bool isX64 = m_qbsStdout.contains("is x64: true");
1923 if (!isX86 && !isX64) {
1924 QVERIFY2(m_qbsStdout.contains("is x86: false") && m_qbsStdout.contains("is x64: false"),
1925 m_qbsStdout.constData());
1926 QSKIP("Not an x86 host");
1927 }
1928 const bool isGcc = m_qbsStdout.contains("is gcc: true");
1929 const bool isMsvc = m_qbsStdout.contains("is msvc: true");
1930 if (!isGcc && !isMsvc) {
1931 QVERIFY2(m_qbsStdout.contains("is gcc: false") && m_qbsStdout.contains("is msvc: false"),
1932 m_qbsStdout.constData());
1933 QSKIP("Neither GCC nor MSVC");
1934 }
1935 QbsRunParameters params(QStringList{"--command-echo-mode", "command-line"});
1936 params.expectFailure = true;
1937 runQbs(params);
1938 if (isGcc) {
1939 QVERIFY2(m_qbsStdout.contains("-msse2") && m_qbsStdout.contains("-mavx")
1940 && m_qbsStdout.contains("-mno-avx512f"), m_qbsStdout.constData());
1941 } else {
1942 QVERIFY2(m_qbsStdout.contains("/arch:AVX"), m_qbsStdout.constData());
1943 QVERIFY2(!m_qbsStdout.contains("/arch:AVX2"), m_qbsStdout.constData());
1944 QVERIFY2(m_qbsStdout.contains("/arch:SSE2") == isX86, m_qbsStdout.constData());
1945 }
1946 }
1947
renameDependency()1948 void TestBlackbox::renameDependency()
1949 {
1950 QDir::setCurrent(testDataDir + "/renameDependency");
1951 if (QFile::exists("work"))
1952 rmDirR("work");
1953 QDir().mkdir("work");
1954 ccp("before", "work");
1955 QDir::setCurrent(testDataDir + "/renameDependency/work");
1956 QCOMPARE(runQbs(), 0);
1957
1958 WAIT_FOR_NEW_TIMESTAMP();
1959 QFile::remove("lib.h");
1960 QFile::remove("lib.cpp");
1961 ccp("../after", ".");
1962 QbsRunParameters params;
1963 params.expectFailure = true;
1964 QVERIFY(runQbs(params) != 0);
1965 QVERIFY(m_qbsStdout.contains("compiling main.cpp"));
1966 }
1967
separateDebugInfo()1968 void TestBlackbox::separateDebugInfo()
1969 {
1970 QDir::setCurrent(testDataDir + "/separate-debug-info");
1971 QCOMPARE(runQbs(QbsRunParameters(QStringList("qbs.debugInformation:true"))), 0);
1972 const bool isWindows = m_qbsStdout.contains("is windows: yes");
1973 const bool isNotWindows = m_qbsStdout.contains("is windows: no");
1974 QVERIFY(isWindows != isNotWindows);
1975 const bool isMacos = m_qbsStdout.contains("is macos: yes");
1976 const bool isNotMacos = m_qbsStdout.contains("is macos: no");
1977 QVERIFY(isMacos != isNotMacos);
1978 const bool isDarwin = m_qbsStdout.contains("is darwin: yes");
1979 const bool isNotDarwin = m_qbsStdout.contains("is darwin: no");
1980 QVERIFY(isDarwin != isNotDarwin);
1981
1982 const SettingsPtr s = settings();
1983 Profile buildProfile(profileName(), s.get());
1984 QStringList toolchain = profileToolchain(buildProfile);
1985 if (isDarwin) {
1986 QVERIFY(directoryExists(relativeProductBuildDir("app1") + "/app1.app.dSYM"));
1987 QVERIFY(regularFileExists(relativeProductBuildDir("app1")
1988 + "/app1.app.dSYM/Contents/Info.plist"));
1989 QVERIFY(regularFileExists(relativeProductBuildDir("app1")
1990 + "/app1.app.dSYM/Contents/Resources/DWARF/app1"));
1991 QCOMPARE(QDir(relativeProductBuildDir("app1")
1992 + "/app1.app.dSYM/Contents/Resources/DWARF")
1993 .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
1994 QVERIFY(!QFile::exists(relativeProductBuildDir("app2") + "/app2.app.dSYM"));
1995 QVERIFY(!QFile::exists(relativeProductBuildDir("app3") + "/app3.app.dSYM"));
1996 if (isMacos) {
1997 QVERIFY(regularFileExists(relativeProductBuildDir("app3")
1998 + "/app3.app/Contents/MacOS/app3.dwarf"));
1999 } else {
2000 QVERIFY(regularFileExists(relativeProductBuildDir("app3")
2001 + "/app3.app/app3.dwarf"));
2002 }
2003 QVERIFY(directoryExists(relativeProductBuildDir("app4") + "/app4.dSYM"));
2004 QVERIFY(regularFileExists(relativeProductBuildDir("app4")
2005 + "/app4.dSYM/Contents/Info.plist"));
2006 QVERIFY(regularFileExists(relativeProductBuildDir("app4")
2007 + "/app4.dSYM/Contents/Resources/DWARF/app4"));
2008 QCOMPARE(QDir(relativeProductBuildDir("app4")
2009 + "/app4.dSYM/Contents/Resources/DWARF")
2010 .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
2011 QVERIFY(regularFileExists(relativeProductBuildDir("app5") + "/app5.dwarf"));
2012 QVERIFY(directoryExists(relativeProductBuildDir("foo1") + "/foo1.framework.dSYM"));
2013 QVERIFY(regularFileExists(relativeProductBuildDir("foo1")
2014 + "/foo1.framework.dSYM/Contents/Info.plist"));
2015 QVERIFY(regularFileExists(relativeProductBuildDir("foo1")
2016 + "/foo1.framework.dSYM/Contents/Resources/DWARF/foo1"));
2017 QCOMPARE(QDir(relativeProductBuildDir("foo1")
2018 + "/foo1.framework.dSYM/Contents/Resources/DWARF")
2019 .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
2020 QVERIFY(!QFile::exists(relativeProductBuildDir("foo2") + "/foo2.framework.dSYM"));
2021 QVERIFY(!QFile::exists(relativeProductBuildDir("foo3") + "/foo3.framework.dSYM"));
2022 if (isMacos) {
2023 QVERIFY(regularFileExists(relativeProductBuildDir("foo3")
2024 + "/foo3.framework/Versions/A/foo3.dwarf"));
2025 } else {
2026 QVERIFY(regularFileExists(relativeProductBuildDir("foo3")
2027 + "/foo3.framework/foo3.dwarf"));
2028 }
2029 QVERIFY(directoryExists(relativeProductBuildDir("foo4") + "/libfoo4.dylib.dSYM"));
2030 QVERIFY(regularFileExists(relativeProductBuildDir("foo4")
2031 + "/libfoo4.dylib.dSYM/Contents/Info.plist"));
2032 QVERIFY(regularFileExists(relativeProductBuildDir("foo4")
2033 + "/libfoo4.dylib.dSYM/Contents/Resources/DWARF/libfoo4.dylib"));
2034 QCOMPARE(QDir(relativeProductBuildDir("foo4")
2035 + "/libfoo4.dylib.dSYM/Contents/Resources/DWARF")
2036 .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
2037 QVERIFY(regularFileExists(relativeProductBuildDir("foo5") + "/libfoo5.dylib.dwarf"));
2038 QVERIFY(directoryExists(relativeProductBuildDir("bar1") + "/bar1.bundle.dSYM"));
2039 QVERIFY(regularFileExists(relativeProductBuildDir("bar1")
2040 + "/bar1.bundle.dSYM/Contents/Info.plist"));
2041 QVERIFY(regularFileExists(relativeProductBuildDir("bar1")
2042 + "/bar1.bundle.dSYM/Contents/Resources/DWARF/bar1"));
2043 QCOMPARE(QDir(relativeProductBuildDir("bar1")
2044 + "/bar1.bundle.dSYM/Contents/Resources/DWARF")
2045 .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
2046 QVERIFY(!QFile::exists(relativeProductBuildDir("bar2") + "/bar2.bundle.dSYM"));
2047 QVERIFY(!QFile::exists(relativeProductBuildDir("bar3") + "/bar3.bundle.dSYM"));
2048 if (isMacos) {
2049 QVERIFY(regularFileExists(relativeProductBuildDir("bar3")
2050 + "/bar3.bundle/Contents/MacOS/bar3.dwarf"));
2051 } else {
2052 QVERIFY(regularFileExists(relativeProductBuildDir("bar3")
2053 + "/bar3.bundle/bar3.dwarf"));
2054 }
2055 QVERIFY(directoryExists(relativeProductBuildDir("bar4") + "/bar4.bundle.dSYM"));
2056 QVERIFY(regularFileExists(relativeProductBuildDir("bar4")
2057 + "/bar4.bundle.dSYM/Contents/Info.plist"));
2058 QVERIFY(regularFileExists(relativeProductBuildDir("bar4")
2059 + "/bar4.bundle.dSYM/Contents/Resources/DWARF/bar4.bundle"));
2060 QCOMPARE(QDir(relativeProductBuildDir("bar4")
2061 + "/bar4.bundle.dSYM/Contents/Resources/DWARF")
2062 .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
2063 QVERIFY(regularFileExists(relativeProductBuildDir("bar5") + "/bar5.bundle.dwarf"));
2064 } else if (toolchain.contains("gcc")) {
2065 const QString exeSuffix = isWindows ? ".exe" : "";
2066 const QString dllPrefix = isWindows ? "" : "lib";
2067 const QString dllSuffix = isWindows ? ".dll" : ".so";
2068 QVERIFY(QFile::exists(relativeProductBuildDir("app1") + "/app1" + exeSuffix + ".debug"));
2069 QVERIFY(!QFile::exists(relativeProductBuildDir("app2") + "/app2" + exeSuffix + ".debug"));
2070 QVERIFY(QFile::exists(relativeProductBuildDir("foo1")
2071 + '/' + dllPrefix + "foo1" + dllSuffix + ".debug"));
2072 QVERIFY(!QFile::exists(relativeProductBuildDir("foo2")
2073 + '/' + "foo2" + dllSuffix + ".debug"));
2074 QVERIFY(QFile::exists(relativeProductBuildDir("bar1")
2075 + '/' + dllPrefix + "bar1" + dllSuffix + ".debug"));
2076 QVERIFY(!QFile::exists(relativeProductBuildDir("bar2")
2077 + '/' + dllPrefix + "bar2" + dllSuffix + ".debug"));
2078 } else if (toolchain.contains("msvc")) {
2079 QVERIFY(QFile::exists(relativeProductBuildDir("app1") + "/app1.pdb"));
2080 QVERIFY(QFile::exists(relativeProductBuildDir("foo1") + "/foo1.pdb"));
2081 QVERIFY(QFile::exists(relativeProductBuildDir("bar1") + "/bar1.pdb"));
2082 // MSVC's linker even creates a pdb file if /Z7 is passed to the compiler.
2083 } else {
2084 QSKIP("Unsupported toolchain. Skipping.");
2085 }
2086 }
2087
trackAddFile()2088 void TestBlackbox::trackAddFile()
2089 {
2090 QList<QByteArray> output;
2091 QDir::setCurrent(testDataDir + "/trackAddFile");
2092 if (QFile::exists("work"))
2093 rmDirR("work");
2094 QDir().mkdir("work");
2095 ccp("before", "work");
2096 QDir::setCurrent(testDataDir + "/trackAddFile/work");
2097 QCOMPARE(runQbs({"resolve"}), 0);
2098 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
2099 QSKIP("Cannot run binaries in cross-compiled build");
2100 const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"});
2101 QCOMPARE(runQbs(runParams), 0);
2102
2103 output = m_qbsStdout.split('\n');
2104 QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!");
2105 QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!");
2106 QString unchangedObjectFile = relativeBuildDir()
2107 + objectFileName("/someapp/narf.cpp", profileName());
2108 QDateTime unchangedObjectFileTime1 = QFileInfo(unchangedObjectFile).lastModified();
2109
2110 WAIT_FOR_NEW_TIMESTAMP();
2111 ccp("../after", ".");
2112 touch("trackAddFile.qbs");
2113 touch("main.cpp");
2114 QCOMPARE(runQbs(runParams), 0);
2115
2116 output = m_qbsStdout.split('\n');
2117 QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!");
2118 QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!");
2119 QCOMPARE(output.takeFirst().trimmed().constData(), "ZORT!");
2120
2121 // the object file of the untouched source should not have changed
2122 QDateTime unchangedObjectFileTime2 = QFileInfo(unchangedObjectFile).lastModified();
2123 QCOMPARE(unchangedObjectFileTime1, unchangedObjectFileTime2);
2124 }
2125
trackExternalProductChanges()2126 void TestBlackbox::trackExternalProductChanges()
2127 {
2128 QDir::setCurrent(testDataDir + "/trackExternalProductChanges");
2129 QCOMPARE(runQbs(), 0);
2130 QVERIFY(m_qbsStdout.contains("compiling main.cpp"));
2131 QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp"));
2132 QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
2133 QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
2134
2135 QbsRunParameters params;
2136 params.environment.insert("QBS_TEST_PULL_IN_FILE_VIA_ENV", "1");
2137 QCOMPARE(runQbs(params), 0);
2138 QVERIFY(!m_qbsStdout.contains("compiling main.cpp"));
2139 QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp"));
2140 QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
2141 QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
2142 params.command = "resolve";
2143 QCOMPARE(runQbs(params), 0);
2144 params.command = "build";
2145 QCOMPARE(runQbs(params), 0);
2146 QVERIFY(!m_qbsStdout.contains("compiling main.cpp"));
2147 QVERIFY(m_qbsStdout.contains("compiling environmentChange.cpp"));
2148 QVERIFY2(!m_qbsStdout.contains("compiling jsFileChange.cpp"), m_qbsStdout.constData());
2149 QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
2150
2151 rmDirR(relativeBuildDir());
2152 QCOMPARE(runQbs(), 0);
2153 QVERIFY(m_qbsStdout.contains("compiling main.cpp"));
2154 QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp"));
2155 QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
2156 QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
2157
2158 WAIT_FOR_NEW_TIMESTAMP();
2159 REPLACE_IN_FILE("fileList.js", "return []", "return ['jsFileChange.cpp']");
2160 QCOMPARE(runQbs(), 0);
2161 QVERIFY(!m_qbsStdout.contains("compiling main.cpp"));
2162 QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp"));
2163 QVERIFY(m_qbsStdout.contains("compiling jsFileChange.cpp"));
2164 QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
2165
2166 rmDirR(relativeBuildDir());
2167 REPLACE_IN_FILE("fileList.js", "['jsFileChange.cpp']", "[]");
2168 QCOMPARE(runQbs(), 0);
2169 QVERIFY(m_qbsStdout.contains("compiling main.cpp"));
2170 QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp"));
2171 QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
2172 QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
2173
2174 QFile cppFile("fileExists.cpp");
2175 QVERIFY(cppFile.open(QIODevice::WriteOnly));
2176 cppFile.write("void fileExists() { }\n");
2177 cppFile.close();
2178 QCOMPARE(runQbs(), 0);
2179 QVERIFY(!m_qbsStdout.contains("compiling main.cpp"));
2180 QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp"));
2181 QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
2182 QVERIFY(m_qbsStdout.contains("compiling fileExists.cpp"));
2183
2184 rmDirR(relativeBuildDir());
2185 const SettingsPtr s = settings();
2186 const Profile profile(profileName(), s.get());
2187 const QStringList toolchainTypes = profileToolchain(profile);
2188 if (!toolchainTypes.contains("gcc"))
2189 QSKIP("Need GCC-like compiler to run this test");
2190 params.environment = QbsRunParameters::defaultEnvironment();
2191 params.environment.insert("INCLUDE_PATH_TEST", "1");
2192 params.expectFailure = true;
2193 QVERIFY(runQbs(params) != 0);
2194 QVERIFY2(m_qbsStderr.contains("hiddenheaderqbs.h"), m_qbsStderr.constData());
2195 params.command = "resolve";
2196 params.environment.insert("CPLUS_INCLUDE_PATH",
2197 QDir::toNativeSeparators(QDir::currentPath() + "/hidden"));
2198 params.expectFailure = false;
2199 QCOMPARE(runQbs(params), 0);
2200 params.command = "build";
2201 QCOMPARE(runQbs(params), 0);
2202 }
2203
trackGroupConditionChange()2204 void TestBlackbox::trackGroupConditionChange()
2205 {
2206 QbsRunParameters params;
2207 params.expectFailure = true;
2208 QDir::setCurrent(testDataDir + "/group-condition-change");
2209 QVERIFY(runQbs(params) != 0);
2210 QVERIFY(m_qbsStderr.contains("jibbetnich"));
2211
2212 params.command = "resolve";
2213 params.arguments = QStringList("project.kaputt:false");
2214 params.expectFailure = false;
2215 QCOMPARE(runQbs(params), 0);
2216 QCOMPARE(runQbs(), 0);
2217 }
2218
trackRemoveFile()2219 void TestBlackbox::trackRemoveFile()
2220 {
2221 QList<QByteArray> output;
2222 QDir::setCurrent(testDataDir + "/trackAddFile");
2223 if (QFile::exists("work"))
2224 rmDirR("work");
2225 QDir().mkdir("work");
2226 ccp("before", "work");
2227 ccp("after", "work");
2228 QDir::setCurrent(testDataDir + "/trackAddFile/work");
2229 QCOMPARE(runQbs({"resolve"}), 0);
2230 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
2231 QSKIP("Cannot run binaries in cross-compiled build");
2232 const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"});
2233 QCOMPARE(runQbs(runParams), 0);
2234 output = m_qbsStdout.split('\n');
2235 QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!");
2236 QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!");
2237 QCOMPARE(output.takeFirst().trimmed().constData(), "ZORT!");
2238 QString unchangedObjectFile = relativeBuildDir()
2239 + objectFileName("/someapp/narf.cpp", profileName());
2240 QDateTime unchangedObjectFileTime1 = QFileInfo(unchangedObjectFile).lastModified();
2241
2242 WAIT_FOR_NEW_TIMESTAMP();
2243 QFile::remove("trackAddFile.qbs");
2244 QFile::remove("main.cpp");
2245 QFile::copy("../before/trackAddFile.qbs", "trackAddFile.qbs");
2246 QFile::copy("../before/main.cpp", "main.cpp");
2247 QVERIFY(QFile::remove("zort.h"));
2248 QVERIFY(QFile::remove("zort.cpp"));
2249 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("resolve"))), 0);
2250
2251 touch("main.cpp");
2252 touch("trackAddFile.qbs");
2253 QCOMPARE(runQbs(runParams), 0);
2254 output = m_qbsStdout.split('\n');
2255 QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!");
2256 QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!");
2257
2258 // the object file of the untouched source should not have changed
2259 QDateTime unchangedObjectFileTime2 = QFileInfo(unchangedObjectFile).lastModified();
2260 QCOMPARE(unchangedObjectFileTime1, unchangedObjectFileTime2);
2261
2262 // the object file for the removed cpp file should have vanished too
2263 QVERIFY(!regularFileExists(relativeBuildDir()
2264 + objectFileName("/someapp/zort.cpp", profileName())));
2265 }
2266
trackAddFileTag()2267 void TestBlackbox::trackAddFileTag()
2268 {
2269 QList<QByteArray> output;
2270 QDir::setCurrent(testDataDir + "/trackFileTags");
2271 if (QFile::exists("work"))
2272 rmDirR("work");
2273 QDir().mkdir("work");
2274 ccp("before", "work");
2275 QDir::setCurrent(testDataDir + "/trackFileTags/work");
2276 QCOMPARE(runQbs({"resolve"}), 0);
2277 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
2278 QSKIP("Cannot run binaries in cross-compiled build");
2279 const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"});
2280 QCOMPARE(runQbs(runParams), 0);
2281 output = m_qbsStdout.split('\n');
2282 QCOMPARE(output.takeFirst().trimmed().constData(), "there's no foo here");
2283
2284 WAIT_FOR_NEW_TIMESTAMP();
2285 ccp("../after", ".");
2286 touch("main.cpp");
2287 touch("trackFileTags.qbs");
2288 waitForFileUnlock();
2289 QCOMPARE(runQbs(runParams), 0);
2290 output = m_qbsStdout.split('\n');
2291 QCOMPARE(output.takeFirst().trimmed().constData(), "there's 15 foo here");
2292 }
2293
trackRemoveFileTag()2294 void TestBlackbox::trackRemoveFileTag()
2295 {
2296 QList<QByteArray> output;
2297 QDir::setCurrent(testDataDir + "/trackFileTags");
2298 if (QFile::exists("work"))
2299 rmDirR("work");
2300 QDir().mkdir("work");
2301 ccp("after", "work");
2302 QDir::setCurrent(testDataDir + "/trackFileTags/work");
2303 QCOMPARE(runQbs({"resolve"}), 0);
2304 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
2305 QSKIP("Cannot run binaries in cross-compiled build");
2306 const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"});
2307 QCOMPARE(runQbs(runParams), 0);
2308
2309 // check if the artifacts are here that will become stale in the 2nd step
2310 QVERIFY(regularFileExists(relativeProductBuildDir("someapp") + '/' + inputDirHash(".")
2311 + objectFileName("/main_foo.cpp", profileName())));
2312 QVERIFY(regularFileExists(relativeProductBuildDir("someapp") + "/main_foo.cpp"));
2313 QVERIFY(regularFileExists(relativeProductBuildDir("someapp") + "/main.foo"));
2314 output = m_qbsStdout.split('\n');
2315 QCOMPARE(output.takeFirst().trimmed().constData(), "there's 15 foo here");
2316
2317 WAIT_FOR_NEW_TIMESTAMP();
2318 ccp("../before", ".");
2319 touch("main.cpp");
2320 touch("trackFileTags.qbs");
2321 QCOMPARE(runQbs(runParams), 0);
2322 output = m_qbsStdout.split('\n');
2323 QCOMPARE(output.takeFirst().trimmed().constData(), "there's no foo here");
2324
2325 // check if stale artifacts have been removed
2326 QCOMPARE(regularFileExists(relativeProductBuildDir("someapp") + '/' + inputDirHash(".")
2327 + objectFileName("/main_foo.cpp", profileName())), false);
2328 QCOMPARE(regularFileExists(relativeProductBuildDir("someapp") + "/main_foo.cpp"), false);
2329 QCOMPARE(regularFileExists(relativeProductBuildDir("someapp") + "/main.foo"), false);
2330 }
2331
trackAddProduct()2332 void TestBlackbox::trackAddProduct()
2333 {
2334 QDir::setCurrent(testDataDir + "/trackProducts");
2335 if (QFile::exists("work"))
2336 rmDirR("work");
2337 QDir().mkdir("work");
2338 ccp("before", "work");
2339 QDir::setCurrent(testDataDir + "/trackProducts/work");
2340 QbsRunParameters params(QStringList() << "-f" << "trackProducts.qbs");
2341 QCOMPARE(runQbs(params), 0);
2342 QVERIFY(m_qbsStdout.contains("compiling foo.cpp"));
2343 QVERIFY(m_qbsStdout.contains("compiling bar.cpp"));
2344 QVERIFY(m_qbsStdout.contains("linking product1"));
2345 QVERIFY(m_qbsStdout.contains("linking product2"));
2346
2347 WAIT_FOR_NEW_TIMESTAMP();
2348 ccp("../after", ".");
2349 touch("trackProducts.qbs");
2350 QCOMPARE(runQbs(params), 0);
2351 QVERIFY(m_qbsStdout.contains("compiling zoo.cpp"));
2352 QVERIFY(m_qbsStdout.contains("linking product3"));
2353 QVERIFY(!m_qbsStdout.contains("compiling foo.cpp"));
2354 QVERIFY(!m_qbsStdout.contains("compiling bar.cpp"));
2355 QVERIFY(!m_qbsStdout.contains("linking product1"));
2356 QVERIFY(!m_qbsStdout.contains("linking product2"));
2357 }
2358
trackRemoveProduct()2359 void TestBlackbox::trackRemoveProduct()
2360 {
2361 QDir::setCurrent(testDataDir + "/trackProducts");
2362 if (QFile::exists("work"))
2363 rmDirR("work");
2364 QDir().mkdir("work");
2365 ccp("before", "work");
2366 ccp("after", "work");
2367 QDir::setCurrent(testDataDir + "/trackProducts/work");
2368 QbsRunParameters params(QStringList() << "-f" << "trackProducts.qbs");
2369 QCOMPARE(runQbs(params), 0);
2370 QVERIFY(m_qbsStdout.contains("compiling foo.cpp"));
2371 QVERIFY(m_qbsStdout.contains("compiling bar.cpp"));
2372 QVERIFY(m_qbsStdout.contains("compiling zoo.cpp"));
2373 QVERIFY(m_qbsStdout.contains("linking product1"));
2374 QVERIFY(m_qbsStdout.contains("linking product2"));
2375 QVERIFY(m_qbsStdout.contains("linking product3"));
2376
2377 WAIT_FOR_NEW_TIMESTAMP();
2378 QFile::remove("zoo.cpp");
2379 QFile::remove("product3.qbs");
2380 copyFileAndUpdateTimestamp("../before/trackProducts.qbs", "trackProducts.qbs");
2381 QCOMPARE(runQbs(params), 0);
2382 QVERIFY(!m_qbsStdout.contains("compiling foo.cpp"));
2383 QVERIFY(!m_qbsStdout.contains("compiling bar.cpp"));
2384 QVERIFY(!m_qbsStdout.contains("compiling zoo.cpp"));
2385 QVERIFY(!m_qbsStdout.contains("linking product1"));
2386 QVERIFY(!m_qbsStdout.contains("linking product2"));
2387 QVERIFY(!m_qbsStdout.contains("linking product3"));
2388 }
2389
wildcardRenaming()2390 void TestBlackbox::wildcardRenaming()
2391 {
2392 QDir::setCurrent(testDataDir + "/wildcard_renaming");
2393 QCOMPARE(runQbs(QbsRunParameters("install")), 0);
2394 QVERIFY(QFileInfo(defaultInstallRoot + "/pioniere.txt").exists());
2395 WAIT_FOR_NEW_TIMESTAMP();
2396 QFile::rename(QDir::currentPath() + "/pioniere.txt", QDir::currentPath() + "/fdj.txt");
2397 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("install"),
2398 QStringList("--clean-install-root"))), 0);
2399 QVERIFY(!QFileInfo(defaultInstallRoot + "/pioniere.txt").exists());
2400 QVERIFY(QFileInfo(defaultInstallRoot + "/fdj.txt").exists());
2401 }
2402
recursiveRenaming()2403 void TestBlackbox::recursiveRenaming()
2404 {
2405 QDir::setCurrent(testDataDir + "/recursive_renaming");
2406 QCOMPARE(runQbs(QbsRunParameters("install")), 0);
2407 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/wasser.txt").exists());
2408 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/subdir/blubb.txt").exists());
2409 WAIT_FOR_NEW_TIMESTAMP();
2410 QVERIFY(QFile::rename(QDir::currentPath() + "/dir/wasser.txt", QDir::currentPath() + "/dir/wein.txt"));
2411 QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("install"),
2412 QStringList("--clean-install-root"))), 0);
2413 QVERIFY(!QFileInfo(defaultInstallRoot + "/dir/wasser.txt").exists());
2414 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/wein.txt").exists());
2415 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/subdir/blubb.txt").exists());
2416 }
2417
recursiveWildcards()2418 void TestBlackbox::recursiveWildcards()
2419 {
2420 QDir::setCurrent(testDataDir + "/recursive_wildcards");
2421 QCOMPARE(runQbs(QbsRunParameters("install")), 0);
2422 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file1.txt").exists());
2423 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file2.txt").exists());
2424 QFile outputFile(defaultInstallRoot + "/output.txt");
2425 QVERIFY2(outputFile.open(QIODevice::ReadOnly), qPrintable(outputFile.errorString()));
2426 QCOMPARE(outputFile.readAll(), QByteArray("file1.txtfile2.txt"));
2427 outputFile.close();
2428 WAIT_FOR_NEW_TIMESTAMP();
2429 QFile newFile("dir/subdir/file3.txt");
2430 QVERIFY2(newFile.open(QIODevice::WriteOnly), qPrintable(newFile.errorString()));
2431 newFile.close();
2432 QCOMPARE(runQbs(QbsRunParameters("install")), 0);
2433 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file3.txt").exists());
2434 QVERIFY2(outputFile.open(QIODevice::ReadOnly), qPrintable(outputFile.errorString()));
2435 QCOMPARE(outputFile.readAll(), QByteArray("file1.txtfile2.txtfile3.txt"));
2436 outputFile.close();
2437 WAIT_FOR_NEW_TIMESTAMP();
2438 QVERIFY2(newFile.remove(), qPrintable(newFile.errorString()));
2439 QVERIFY2(!newFile.exists(), qPrintable(newFile.fileName()));
2440 QCOMPARE(runQbs(QbsRunParameters("install", QStringList{ "--clean-install-root"})), 0);
2441 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file1.txt").exists());
2442 QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file2.txt").exists());
2443 QVERIFY(!QFileInfo(defaultInstallRoot + "/dir/file3.txt").exists());
2444 QVERIFY2(outputFile.open(QIODevice::ReadOnly), qPrintable(outputFile.errorString()));
2445 QCOMPARE(outputFile.readAll(), QByteArray("file1.txtfile2.txt"));
2446 }
2447
referenceErrorInExport()2448 void TestBlackbox::referenceErrorInExport()
2449 {
2450 QDir::setCurrent(testDataDir + "/referenceErrorInExport");
2451 QbsRunParameters params;
2452 params.expectFailure = true;
2453 QVERIFY(runQbs(params) != 0);
2454 QVERIFY(m_qbsStderr.contains(
2455 "referenceErrorInExport.qbs:15:12 ReferenceError: Can't find variable: includePaths"));
2456 }
2457
removeDuplicateLibraries_data()2458 void TestBlackbox::removeDuplicateLibraries_data()
2459 {
2460 QTest::addColumn<bool>("removeDuplicates");
2461 QTest::newRow("remove duplicates") << true;
2462 QTest::newRow("don't remove duplicates") << false;
2463 }
2464
removeDuplicateLibraries()2465 void TestBlackbox::removeDuplicateLibraries()
2466 {
2467 QDir::setCurrent(testDataDir + "/remove-duplicate-libs");
2468 QFETCH(bool, removeDuplicates);
2469 const QbsRunParameters resolveParams("resolve", {"-f", "remove-duplicate-libs.qbs",
2470 "project.removeDuplicates:" + QString(removeDuplicates? "true" : "false")});
2471 QCOMPARE(runQbs(resolveParams), 0);
2472 const bool isBfd = m_qbsStdout.contains("is bfd linker: true");
2473 const bool isNotBfd = m_qbsStdout.contains("is bfd linker: false");
2474 QVERIFY2(isBfd != isNotBfd, m_qbsStdout.constData());
2475 QbsRunParameters buildParams("build");
2476 buildParams.expectFailure = removeDuplicates && isBfd;
2477 QCOMPARE(runQbs(buildParams) == 0, !buildParams.expectFailure);
2478 }
2479
reproducibleBuild()2480 void TestBlackbox::reproducibleBuild()
2481 {
2482 const SettingsPtr s = settings();
2483 const Profile profile(profileName(), s.get());
2484 const QStringList toolchains = profileToolchain(profile);
2485 if (!toolchains.contains("gcc"))
2486 QSKIP("reproducible builds only supported for gcc");
2487 if (toolchains.contains("clang"))
2488 QSKIP("reproducible builds are not supported for clang");
2489
2490 QFETCH(bool, reproducible);
2491
2492 QDir::setCurrent(testDataDir + "/reproducible-build");
2493 QbsRunParameters params;
2494 params.arguments << QString("modules.cpp.enableReproducibleBuilds:")
2495 + (reproducible ? "true" : "false");
2496 rmDirR(relativeBuildDir());
2497 QCOMPARE(runQbs(params), 0);
2498 QFile object(relativeProductBuildDir("the product") + '/' + inputDirHash(".") + '/'
2499 + objectFileName("file1.cpp", profileName()));
2500 QVERIFY2(object.open(QIODevice::ReadOnly), qPrintable(object.fileName()));
2501 const QByteArray oldContents = object.readAll();
2502 object.close();
2503 QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
2504 QVERIFY(!object.exists());
2505 QCOMPARE(runQbs(params), 0);
2506 if (reproducible) {
2507 QVERIFY(object.open(QIODevice::ReadOnly));
2508 const QByteArray newContents = object.readAll();
2509 QVERIFY(oldContents == newContents);
2510 object.close();
2511 }
2512 QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
2513 }
2514
reproducibleBuild_data()2515 void TestBlackbox::reproducibleBuild_data()
2516 {
2517 QTest::addColumn<bool>("reproducible");
2518 QTest::newRow("non-reproducible build") << false;
2519 QTest::newRow("reproducible build") << true;
2520 }
2521
responseFiles()2522 void TestBlackbox::responseFiles()
2523 {
2524 QDir::setCurrent(testDataDir + "/response-files");
2525 QCOMPARE(runQbs({"resolve"}), 0);
2526 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
2527 QSKIP("Cannot run binaries in cross-compiled build");
2528 QbsRunParameters params;
2529 params.command = "install";
2530 params.arguments << "--install-root" << "installed";
2531 QCOMPARE(runQbs(params), 0);
2532 QFile file("installed/response-file-content.txt");
2533 QVERIFY(file.open(QIODevice::ReadOnly));
2534 const QList<QByteArray> expected = QList<QByteArray>()
2535 << "foo" << qbs::Internal::shellQuote(QStringLiteral("with space")).toUtf8()
2536 << "bar" << "";
2537 QList<QByteArray> lines = file.readAll().split('\n');
2538 for (auto &line : lines)
2539 line = line.trimmed();
2540 QCOMPARE(lines, expected);
2541 }
2542
retaggedOutputArtifact()2543 void TestBlackbox::retaggedOutputArtifact()
2544 {
2545 QDir::setCurrent(testDataDir + "/retagged-output-artifact");
2546 QbsRunParameters resolveParams("resolve");
2547 resolveParams.arguments = QStringList("products.p.useTag1:true");
2548 QCOMPARE(runQbs(resolveParams), 0);
2549 QCOMPARE(runQbs(), 0);
2550 const QString a2 = relativeProductBuildDir("p") + "/a2.txt";
2551 const QString a3 = relativeProductBuildDir("p") + "/a3.txt";
2552 QVERIFY2(QFile::exists(a2), qPrintable(a2));
2553 QVERIFY2(!QFile::exists(a3), qPrintable(a3));
2554 resolveParams.arguments = QStringList("products.p.useTag1:false");
2555 QCOMPARE(runQbs(resolveParams), 0);
2556 QCOMPARE(runQbs(), 0);
2557 QVERIFY2(!QFile::exists(a2), qPrintable(a2));
2558 QVERIFY2(QFile::exists(a3), qPrintable(a3));
2559 resolveParams.arguments = QStringList("products.p.useTag1:true");
2560 QCOMPARE(runQbs(resolveParams), 0);
2561 QCOMPARE(runQbs(), 0);
2562 QVERIFY2(QFile::exists(a2), qPrintable(a2));
2563 QVERIFY2(!QFile::exists(a3), qPrintable(a3));
2564 }
2565
ruleConditions()2566 void TestBlackbox::ruleConditions()
2567 {
2568 QDir::setCurrent(testDataDir + "/ruleConditions");
2569 QCOMPARE(runQbs(), 0);
2570 QVERIFY(QFileInfo(relativeExecutableFilePath("zorted")).exists());
2571 QVERIFY(QFileInfo(relativeExecutableFilePath("unzorted")).exists());
2572 QVERIFY(QFileInfo(relativeProductBuildDir("zorted") + "/zorted.foo.narf.zort").exists());
2573 QVERIFY(!QFileInfo(relativeProductBuildDir("unzorted") + "/unzorted.foo.narf.zort").exists());
2574 }
2575
ruleConnectionWithExcludedInputs()2576 void TestBlackbox::ruleConnectionWithExcludedInputs()
2577 {
2578 QDir::setCurrent(testDataDir + "/rule-connection-with-excluded-inputs");
2579 QCOMPARE(runQbs(), 0);
2580 QVERIFY2(m_qbsStdout.contains("inputs.x: 2") && m_qbsStdout.contains("inputs.y: 0"),
2581 m_qbsStdout.constData());
2582 }
2583
ruleCycle()2584 void TestBlackbox::ruleCycle()
2585 {
2586 QDir::setCurrent(testDataDir + "/ruleCycle");
2587 QbsRunParameters params;
2588 params.expectFailure = true;
2589 QVERIFY(runQbs(params) != 0);
2590 QVERIFY(m_qbsStderr.contains("Cycle detected in rule dependencies"));
2591 }
2592
ruleWithNoInputs()2593 void TestBlackbox::ruleWithNoInputs()
2594 {
2595 QDir::setCurrent(testDataDir + "/rule-with-no-inputs");
2596 QVERIFY2(runQbs() == 0, m_qbsStderr.constData());
2597 QVERIFY2(m_qbsStdout.contains("running the rule"), m_qbsStdout.constData());
2598 QVERIFY2(m_qbsStdout.contains("creating output"), m_qbsStdout.constData());
2599 QVERIFY2(runQbs() == 0, m_qbsStderr.constData());
2600 QVERIFY2(!m_qbsStdout.contains("running the rule"), m_qbsStdout.constData());
2601 QVERIFY2(!m_qbsStdout.contains("creating output"), m_qbsStdout.constData());
2602 QbsRunParameters params("resolve", QStringList() << "products.theProduct.version:1");
2603 QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData());
2604 params.command = "build";
2605 QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData());
2606 QVERIFY2(!m_qbsStdout.contains("running the rule"), m_qbsStdout.constData());
2607 QVERIFY2(!m_qbsStdout.contains("creating output"), m_qbsStdout.constData());
2608 params.command = "resolve";
2609 params.arguments = QStringList() << "products.theProduct.version:2";
2610 QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData());
2611 params.command = "build";
2612 QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData());
2613 QVERIFY2(!m_qbsStdout.contains("running the rule"), m_qbsStdout.constData());
2614 QVERIFY2(m_qbsStdout.contains("creating output"), m_qbsStdout.constData());
2615 params.command = "resolve";
2616 params.arguments = QStringList() << "products.theProduct.version:2"
2617 << "products.theProduct.dummy:true";
2618 QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData());
2619 params.command = "build";
2620 QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData());
2621 QVERIFY2(m_qbsStdout.contains("running the rule"), m_qbsStdout.constData());
2622 QVERIFY2(!m_qbsStdout.contains("creating output"), m_qbsStdout.constData());
2623 }
2624
ruleWithNonRequiredInputs()2625 void TestBlackbox::ruleWithNonRequiredInputs()
2626 {
2627 QDir::setCurrent(testDataDir + "/rule-with-non-required-inputs");
2628 QbsRunParameters params("build", {"products.p.enableTagger:false"});
2629 QCOMPARE(runQbs(params), 0);
2630 QFile outFile(relativeProductBuildDir("p") + "/output.txt");
2631 QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString()));
2632 QByteArray output = outFile.readAll();
2633 QCOMPARE(output, QByteArray("()"));
2634 outFile.close();
2635 params.command = "resolve";
2636 params.arguments = QStringList({"products.p.enableTagger:true"});
2637 QCOMPARE(runQbs(params), 0);
2638 QCOMPARE(runQbs(), 0);
2639 QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString()));
2640 output = outFile.readAll();
2641 QCOMPARE(output, QByteArray("(a.inp,b.inp,c.inp,)"));
2642 QCOMPARE(runQbs(), 0);
2643 QVERIFY2(!m_qbsStdout.contains("Generating"), m_qbsStdout.constData());
2644 WAIT_FOR_NEW_TIMESTAMP();
2645 touch("a.inp");
2646 QCOMPARE(runQbs(), 0);
2647 QVERIFY2(m_qbsStdout.contains("Generating"), m_qbsStdout.constData());
2648 }
2649
sanitizer_data()2650 void TestBlackbox::sanitizer_data()
2651 {
2652 QTest::addColumn<QString>("sanitizer");
2653 QTest::newRow("none") << QString();
2654 QTest::newRow("address") << QStringLiteral("address");
2655 QTest::newRow("undefined") << QStringLiteral("undefined");
2656 QTest::newRow("thread") << QStringLiteral("thread");
2657 }
2658
sanitizer()2659 void TestBlackbox::sanitizer()
2660 {
2661 QFETCH(QString, sanitizer);
2662 QDir::setCurrent(testDataDir + "/sanitizer");
2663 rmDirR(relativeBuildDir());
2664 QbsRunParameters params("build", {"--command-echo-mode", "command-line"});
2665 if (!sanitizer.isEmpty()) {
2666 params.arguments.append(
2667 {QStringLiteral("products.sanitizer.sanitizer:\"") + sanitizer + "\""});
2668 }
2669 QCOMPARE(runQbs(params), 0);
2670 if (m_qbsStdout.contains(QByteArrayLiteral("Compiler does not support sanitizer")))
2671 QSKIP("Compiler does not support the specified sanitizer");
2672 if (!sanitizer.isEmpty()) {
2673 QVERIFY2(m_qbsStdout.contains(QByteArrayLiteral("-fsanitize=") + sanitizer.toLatin1()),
2674 qPrintable(m_qbsStdout));
2675 } else {
2676 QVERIFY2(!m_qbsStdout.contains(QByteArrayLiteral("-fsanitize=")), qPrintable(m_qbsStdout));
2677 }
2678 }
2679
scannerItem()2680 void TestBlackbox::scannerItem()
2681 {
2682 QDir::setCurrent(testDataDir + "/scanner-item");
2683 QCOMPARE(runQbs(), 0);
2684 QVERIFY2(m_qbsStdout.contains("handling file1.in"), m_qbsStdout.constData());
2685 QVERIFY2(m_qbsStdout.contains("handling file2.in"), m_qbsStdout.constData());
2686 WAIT_FOR_NEW_TIMESTAMP();
2687 touch("subdir1/file.inc");
2688 QCOMPARE(runQbs(), 0);
2689 QVERIFY2(m_qbsStdout.contains("handling file1.in"), m_qbsStdout.constData());
2690 QVERIFY2(!m_qbsStdout.contains("handling file2.in"), m_qbsStdout.constData());
2691 WAIT_FOR_NEW_TIMESTAMP();
2692 touch("subdir2/file.inc");
2693 QCOMPARE(runQbs(), 0);
2694 QVERIFY2(!m_qbsStdout.contains("handling file1.in"), m_qbsStdout.constData());
2695 QVERIFY2(m_qbsStdout.contains("handling file2.in"), m_qbsStdout.constData());
2696 }
2697
scanResultInOtherProduct()2698 void TestBlackbox::scanResultInOtherProduct()
2699 {
2700 QDir::setCurrent(testDataDir + "/scan-result-in-other-product");
2701 QCOMPARE(runQbs(QStringList("-vv")), 0);
2702 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
2703 QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData());
2704 QVERIFY2(!m_qbsStderr.contains("The file dependency might get lost during change tracking"),
2705 m_qbsStderr.constData());
2706 WAIT_FOR_NEW_TIMESTAMP();
2707 REPLACE_IN_FILE("other/other.qbs", "blubb", "blubb2");
2708 QCOMPARE(runQbs(), 0);
2709 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
2710 QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData());
2711 WAIT_FOR_NEW_TIMESTAMP();
2712 touch("lib/lib.h");
2713 QCOMPARE(runQbs(), 0);
2714 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
2715 QVERIFY2(!m_qbsStdout.contains("generating text file"), m_qbsStdout.constData());
2716 }
2717
scanResultInNonDependency()2718 void TestBlackbox::scanResultInNonDependency()
2719 {
2720 QDir::setCurrent(testDataDir + "/scan-result-in-non-dependency");
2721 QCOMPARE(runQbs(QStringList("-vv")), 0);
2722 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
2723 QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData());
2724 QVERIFY2(m_qbsStderr.contains("The file dependency might get lost during change tracking"),
2725 m_qbsStderr.constData());
2726 WAIT_FOR_NEW_TIMESTAMP();
2727 REPLACE_IN_FILE("other/other.qbs", "blubb", "blubb2");
2728 QCOMPARE(runQbs(), 0);
2729 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
2730 QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData());
2731 WAIT_FOR_NEW_TIMESTAMP();
2732 touch("lib/lib.h");
2733 QCOMPARE(runQbs(), 0);
2734 QEXPECT_FAIL("", "QBS-1532", Continue);
2735 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
2736 QVERIFY2(!m_qbsStdout.contains("generating text file"), m_qbsStdout.constData());
2737 }
2738
setupBuildEnvironment()2739 void TestBlackbox::setupBuildEnvironment()
2740 {
2741 QDir::setCurrent(testDataDir + "/setup-build-environment");
2742 QCOMPARE(runQbs(), 0);
2743 QFile f(relativeProductBuildDir("first_product") + QLatin1String("/m.output"));
2744 QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString()));
2745 QCOMPARE(f.readAll().trimmed(), QByteArray("1"));
2746 f.close();
2747 f.setFileName(relativeProductBuildDir("second_product") + QLatin1String("/m.output"));
2748 QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString()));
2749 QCOMPARE(f.readAll().trimmed(), QByteArray());
2750 }
2751
setupRunEnvironment()2752 void TestBlackbox::setupRunEnvironment()
2753 {
2754 QDir::setCurrent(testDataDir + "/setup-run-environment");
2755 QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
2756 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
2757 QSKIP("Cannot run binaries in cross-compiled build");
2758 QbsRunParameters failParams("run", QStringList({"--setup-run-env-config",
2759 "ignore-lib-dependencies"}));
2760 failParams.expectFailure = true;
2761 failParams.expectCrash = m_qbsStdout.contains("is windows");
2762 QVERIFY(runQbs(QbsRunParameters(failParams)) != 0);
2763 QVERIFY2(failParams.expectCrash || m_qbsStderr.contains("lib"), m_qbsStderr.constData());
2764 QCOMPARE(runQbs(QbsRunParameters("run")), 0);
2765 QbsRunParameters dryRunParams("run", QStringList("--dry-run"));
2766 dryRunParams.buildDirectory = "dryrun";
2767 QCOMPARE(runQbs(dryRunParams), 0);
2768 const QString appFilePath = QDir::currentPath() + "/dryrun/"
2769 + relativeExecutableFilePath("app");
2770 QVERIFY2(m_qbsStdout.contains("Would start target")
2771 && m_qbsStdout.contains(QDir::toNativeSeparators(appFilePath).toLocal8Bit()),
2772 m_qbsStdout.constData());
2773 }
2774
smartRelinking()2775 void TestBlackbox::smartRelinking()
2776 {
2777 QDir::setCurrent(testDataDir + "/smart-relinking");
2778 rmDirR(relativeBuildDir());
2779 QFETCH(bool, strictChecking);
2780 QbsRunParameters params(QStringList() << (QString("modules.cpp.exportedSymbolsCheckMode:%1")
2781 .arg(strictChecking ? "strict" : "ignore-undefined")));
2782 QCOMPARE(runQbs(params), 0);
2783 if (m_qbsStdout.contains("project disabled"))
2784 QSKIP("Test does not apply on this target");
2785 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2786 QVERIFY2(m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2787
2788 // Irrelevant change.
2789 WAIT_FOR_NEW_TIMESTAMP();
2790 touch("lib.cpp");
2791 QCOMPARE(runQbs(params), 0);
2792 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2793 QVERIFY2(!m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2794
2795 // Add new private symbol.
2796 WAIT_FOR_NEW_TIMESTAMP();
2797 params.command = "resolve";
2798 params.arguments << "products.lib.defines:PRIV2";
2799 QCOMPARE(runQbs(params), 0);
2800 QCOMPARE(runQbs(), 0);
2801 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2802 QVERIFY2(!m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2803
2804 // Remove private symbol.
2805 WAIT_FOR_NEW_TIMESTAMP();
2806 params.arguments.removeLast();
2807 QCOMPARE(runQbs(params), 0);
2808 QCOMPARE(runQbs(), 0);
2809 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2810 QVERIFY2(!m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2811
2812 // Add new public symbol.
2813 WAIT_FOR_NEW_TIMESTAMP();
2814 params.arguments << "products.lib.defines:PUB2";
2815 QCOMPARE(runQbs(params), 0);
2816 QCOMPARE(runQbs(), 0);
2817 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2818 QVERIFY2(m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2819
2820 // Remove public symbol.
2821 WAIT_FOR_NEW_TIMESTAMP();
2822 params.arguments.removeLast();
2823 QCOMPARE(runQbs(params), 0);
2824 QCOMPARE(runQbs(), 0);
2825 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2826 QVERIFY2(m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2827
2828 // Add new undefined symbol.
2829 WAIT_FOR_NEW_TIMESTAMP();
2830 params.arguments << "products.lib.defines:PRINTF";
2831 QCOMPARE(runQbs(params), 0);
2832 QCOMPARE(runQbs(), 0);
2833 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2834 QVERIFY2(strictChecking == m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2835
2836 // Remove undefined symbol.
2837 WAIT_FOR_NEW_TIMESTAMP();
2838 params.arguments.removeLast();
2839 QCOMPARE(runQbs(params), 0);
2840 QCOMPARE(runQbs(), 0);
2841 QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData());
2842 QVERIFY2(strictChecking == m_qbsStdout.contains("linking app"), m_qbsStdout.constData());
2843 }
2844
smartRelinking_data()2845 void TestBlackbox::smartRelinking_data()
2846 {
2847 QTest::addColumn<bool>("strictChecking");
2848 QTest::newRow("strict checking") << true;
2849 QTest::newRow("ignore undefined") << false;
2850 }
2851
2852
soName(const QString & readElfPath,const QString & libFilePath)2853 static QString soName(const QString &readElfPath, const QString &libFilePath)
2854 {
2855 QProcess readElf;
2856 auto env = QProcessEnvironment::systemEnvironment();
2857 env.insert(QStringLiteral("LC_ALL"), QStringLiteral("C")); // force readelf to use US encoding
2858 readElf.setProcessEnvironment(env);
2859 readElf.start(readElfPath, QStringList() << "-a" << libFilePath);
2860 if (!readElf.waitForStarted() || !readElf.waitForFinished() || readElf.exitCode() != 0) {
2861 qDebug() << readElf.errorString() << readElf.readAllStandardError();
2862 return {};
2863 }
2864 const QByteArray output = readElf.readAllStandardOutput();
2865 const QByteArray magicString = "Library soname: [";
2866 const int magicStringIndex = output.indexOf(magicString);
2867 if (magicStringIndex == -1)
2868 return {};
2869 const int endIndex = output.indexOf(']', magicStringIndex);
2870 if (endIndex == -1)
2871 return {};
2872 const int nameIndex = magicStringIndex + magicString.size();
2873 const QByteArray theName = output.mid(nameIndex, endIndex - nameIndex);
2874 return QString::fromLatin1(theName);
2875 }
2876
soVersion()2877 void TestBlackbox::soVersion()
2878 {
2879 const QString readElfPath = findExecutable(QStringList("readelf"));
2880 if (readElfPath.isEmpty() || readElfPath.endsWith("exe"))
2881 QSKIP("soversion test not applicable on this system");
2882 QDir::setCurrent(testDataDir + "/soversion");
2883
2884 QFETCH(QString, soVersion);
2885 QFETCH(bool, useVersion);
2886 QFETCH(QString, expectedSoName);
2887
2888 QbsRunParameters params;
2889 params.arguments << ("products.mylib.useVersion:" + QString((useVersion ? "true" : "false")));
2890 if (!soVersion.isNull())
2891 params.arguments << ("modules.cpp.soVersion:" + soVersion);
2892 const QString libFilePath = relativeProductBuildDir("mylib") + "/libmylib.so"
2893 + (useVersion ? ".1.2.3" : QString());
2894 rmDirR(relativeBuildDir());
2895 QCOMPARE(runQbs(params), 0);
2896 QVERIFY2(regularFileExists(libFilePath), qPrintable(libFilePath));
2897 QCOMPARE(soName(readElfPath, libFilePath), expectedSoName);
2898 }
2899
soVersion_data()2900 void TestBlackbox::soVersion_data()
2901 {
2902 QTest::addColumn<QString>("soVersion");
2903 QTest::addColumn<bool>("useVersion");
2904 QTest::addColumn<QString>("expectedSoName");
2905
2906 QTest::newRow("default") << QString() << true << QString("libmylib.so.1");
2907 QTest::newRow("explicit soVersion") << QString("1.2") << true << QString("libmylib.so.1.2");
2908 QTest::newRow("empty soVersion") << QString("") << true << QString("libmylib.so.1.2.3");
2909 QTest::newRow("no version, explicit soVersion") << QString("5") << false
2910 << QString("libmylib.so.5");
2911 QTest::newRow("no version, default soVersion") << QString() << false << QString("libmylib.so");
2912 QTest::newRow("no version, empty soVersion") << QString("") << false << QString("libmylib.so");
2913 }
2914
sourceArtifactChanges()2915 void TestBlackbox::sourceArtifactChanges()
2916 {
2917 QDir::setCurrent(testDataDir + "/source-artifact-changes");
2918 bool useCustomFileTags = false;
2919 bool overrideFileTags = true;
2920 bool filesAreTargets = false;
2921 bool useCxx11 = false;
2922 const QString appFilePath = QDir::currentPath() + '/' + relativeExecutableFilePath("app");
2923 static const auto b2s = [](bool b) { return QString(b ? "true" : "false"); };
2924 const auto resolveParams = [&useCustomFileTags, &overrideFileTags, &filesAreTargets, &useCxx11] {
2925 return QbsRunParameters("resolve", QStringList{
2926 "modules.module_with_files.overrideTags:" + b2s(overrideFileTags),
2927 "modules.module_with_files.filesAreTargets:" + b2s(filesAreTargets),
2928 "modules.module_with_files.fileTags:" + QString(useCustomFileTags ? "custom" : "cpp"),
2929 "modules.cpp.cxxLanguageVersion:" + QString(useCxx11 ? "c++11" : "c++98")
2930 });
2931 };
2932 #define VERIFY_COMPILATION(expected) \
2933 do { \
2934 QVERIFY2(m_qbsStdout.contains("compiling main.cpp") == expected, m_qbsStdout.constData()); \
2935 QVERIFY2(QFile::exists(appFilePath) == expected, qPrintable(appFilePath)); \
2936 if (expected) \
2937 QVERIFY2(m_qbsStdout.contains("cpp artifacts: 1"), m_qbsStdout.constData()); \
2938 else \
2939 QVERIFY2(m_qbsStdout.contains("cpp artifacts: 0"), m_qbsStdout.constData()); \
2940 } while (false)
2941
2942 // Initial build.
2943 QCOMPARE(runQbs(resolveParams()), 0);
2944 QVERIFY2(m_qbsStdout.contains("is gcc: "), m_qbsStdout.constData());
2945 const bool isGcc = m_qbsStdout.contains("is gcc: true");
2946 QCOMPARE(runQbs(), 0);
2947 VERIFY_COMPILATION(true);
2948
2949 // Overwrite the file tags. Now the source file is no longer tagged "cpp" and nothing
2950 // should get built.
2951 WAIT_FOR_NEW_TIMESTAMP();
2952 touch("modules/module_with_files/main.cpp");
2953 useCustomFileTags = true;
2954 QCOMPARE(runQbs(resolveParams()), 0);
2955 QCOMPARE(runQbs(), 0);
2956 VERIFY_COMPILATION(false);
2957
2958 // Now the custom file tag exists in addition to "cpp", and the app should get built again.
2959 overrideFileTags = false;
2960 QCOMPARE(runQbs(resolveParams()), 0);
2961 QCOMPARE(runQbs(), 0);
2962 VERIFY_COMPILATION(true);
2963
2964 // Mark the cpp file as a module target. Now it will no longer be considered an input
2965 // by the compiler rule, and nothing should get built.
2966 WAIT_FOR_NEW_TIMESTAMP();
2967 touch("modules/module_with_files/main.cpp");
2968 filesAreTargets = true;
2969 QCOMPARE(runQbs(resolveParams()), 0);
2970 QCOMPARE(runQbs(), 0);
2971 VERIFY_COMPILATION(false);
2972
2973 // Now just revert the last change.
2974 filesAreTargets = false;
2975 QCOMPARE(runQbs(resolveParams()), 0);
2976 QCOMPARE(runQbs(), 0);
2977 VERIFY_COMPILATION(true);
2978
2979 // Change a relevant cpp property. A rebuild is expected.
2980 useCxx11 = true;
2981 QCOMPARE(runQbs(resolveParams()), 0);
2982 QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line"})), 0);
2983 if (isGcc) {
2984 QVERIFY2(m_qbsStdout.contains("-std=c++11") || m_qbsStdout.contains("-std=c++0x"),
2985 m_qbsStdout.constData());
2986 }
2987
2988 #undef VERIFY_COMPILATION
2989 }
2990
overrideProjectProperties()2991 void TestBlackbox::overrideProjectProperties()
2992 {
2993 QDir::setCurrent(testDataDir + "/overrideProjectProperties");
2994 QCOMPARE(runQbs(QbsRunParameters(QStringList()
2995 << QStringLiteral("-f")
2996 << QStringLiteral("overrideProjectProperties.qbs")
2997 << QStringLiteral("project.nameSuffix:ForYou")
2998 << QStringLiteral("project.someBool:false")
2999 << QStringLiteral("project.someInt:156")
3000 << QStringLiteral("project.someStringList:one")
3001 << QStringLiteral("products.MyAppForYou.mainFile:main.cpp"))),
3002 0);
3003 QVERIFY(regularFileExists(relativeExecutableFilePath("MyAppForYou")));
3004 QVERIFY(QFile::remove(relativeBuildGraphFilePath()));
3005 QbsRunParameters params;
3006 params.arguments << QStringLiteral("-f") << QStringLiteral("project_using_helper_lib.qbs");
3007 params.expectFailure = true;
3008 QVERIFY(runQbs(params) != 0);
3009
3010 rmDirR(relativeBuildDir());
3011 params.arguments = QStringList() << QStringLiteral("-f")
3012 << QStringLiteral("project_using_helper_lib.qbs")
3013 << QStringLiteral("project.linkSuccessfully:true");
3014 params.expectFailure = false;
3015 QCOMPARE(runQbs(params), 0);
3016 }
3017
pathProbe_data()3018 void TestBlackbox::pathProbe_data()
3019 {
3020 QTest::addColumn<QString>("projectFile");
3021 QTest::addColumn<bool>("successExpected");
3022 QTest::newRow("non-existent") << QString("non-existent.qbs") << false;
3023 QTest::newRow("non-existent-selector.qbs") << QString("non-existent-selector.qbs") << false;
3024 QTest::newRow("single-file") << QString("single-file.qbs") << true;
3025 QTest::newRow("single-file-selector") << QString("single-file-selector.qbs") << true;
3026 QTest::newRow("single-file-selector-array") << QString("single-file-selector-array.qbs") << true;
3027 QTest::newRow("single-file-mult-variants") << QString("single-file-mult-variants.qbs") << true;
3028 QTest::newRow("mult-files") << QString("mult-files.qbs") << true;
3029 QTest::newRow("mult-files-mult-variants") << QString("mult-files-mult-variants.qbs") << true;
3030 QTest::newRow("single-file-suffixes") << QString("single-file-suffixes.qbs") << true;
3031 QTest::newRow("mult-files-suffixes") << QString("mult-files-suffixes.qbs") << true;
3032 QTest::newRow("mult-files-common-suffixes") << QString("mult-files-common-suffixes.qbs") << true;
3033 QTest::newRow("mult-files-mult-suffixes") << QString("mult-files-mult-suffixes.qbs") << true;
3034 QTest::newRow("name-filter") << QString("name-filter.qbs") << true;
3035 QTest::newRow("candidate-filter") << QString("candidate-filter.qbs") << true;
3036 QTest::newRow("environment-paths") << QString("environment-paths.qbs") << true;
3037 }
3038
pathProbe()3039 void TestBlackbox::pathProbe()
3040 {
3041 QDir::setCurrent(testDataDir + "/path-probe");
3042 QFETCH(QString, projectFile);
3043 QFETCH(bool, successExpected);
3044 rmDirR(relativeBuildDir());
3045
3046 QbsRunParameters buildParams("build", QStringList{"-f", projectFile});
3047 buildParams.expectFailure = !successExpected;
3048 buildParams.environment.insert("SEARCH_PATH", "usr/bin");
3049 QCOMPARE(runQbs(buildParams) == 0, successExpected);
3050 if (!successExpected)
3051 QVERIFY2(m_qbsStderr.contains("Probe failed to find files"), m_qbsStderr);
3052 }
3053
pchChangeTracking()3054 void TestBlackbox::pchChangeTracking()
3055 {
3056 QDir::setCurrent(testDataDir + "/pch-change-tracking");
3057 QCOMPARE(runQbs(), 0);
3058 QVERIFY(m_qbsStdout.contains("precompiling pch.h (cpp)"));
3059 WAIT_FOR_NEW_TIMESTAMP();
3060 touch("header1.h");
3061 QCOMPARE(runQbs(), 0);
3062 QVERIFY(m_qbsStdout.contains("precompiling pch.h (cpp)"));
3063 QVERIFY(m_qbsStdout.contains("compiling header2.cpp"));
3064 QVERIFY(m_qbsStdout.contains("compiling main.cpp"));
3065 WAIT_FOR_NEW_TIMESTAMP();
3066 touch("header2.h");
3067 QCOMPARE(runQbs(), 0);
3068 QVERIFY2(!m_qbsStdout.contains("precompiling pch.h (cpp)"), m_qbsStdout.constData());
3069 }
3070
perGroupDefineInExportItem()3071 void TestBlackbox::perGroupDefineInExportItem()
3072 {
3073 QDir::setCurrent(testDataDir + "/per-group-define-in-export-item");
3074 QCOMPARE(runQbs(), 0);
3075 }
3076
pkgConfigProbe()3077 void TestBlackbox::pkgConfigProbe()
3078 {
3079 const QString exe = findExecutable(QStringList() << "pkg-config");
3080 if (exe.isEmpty())
3081 QSKIP("This test requires the pkg-config tool");
3082
3083 QDir::setCurrent(testDataDir + "/pkg-config-probe");
3084
3085 QFETCH(QString, packageBaseName);
3086 QFETCH(QStringList, found);
3087 QFETCH(QStringList, libs);
3088 QFETCH(QStringList, cflags);
3089 QFETCH(QStringList, version);
3090
3091 rmDirR(relativeBuildDir());
3092 QbsRunParameters params(QStringList() << ("project.packageBaseName:" + packageBaseName));
3093 QCOMPARE(runQbs(params), 0);
3094 const QString stdOut = m_qbsStdout;
3095 QVERIFY2(stdOut.contains("theProduct1 found: " + found.at(0)), m_qbsStdout.constData());
3096 QVERIFY2(stdOut.contains("theProduct2 found: " + found.at(1)), m_qbsStdout.constData());
3097 QVERIFY2(stdOut.contains("theProduct1 libs: " + libs.at(0)), m_qbsStdout.constData());
3098 QVERIFY2(stdOut.contains("theProduct2 libs: " + libs.at(1)), m_qbsStdout.constData());
3099 QVERIFY2(stdOut.contains("theProduct1 cflags: " + cflags.at(0)), m_qbsStdout.constData());
3100 QVERIFY2(stdOut.contains("theProduct2 cflags: " + cflags.at(1)), m_qbsStdout.constData());
3101 QVERIFY2(stdOut.contains("theProduct1 version: " + version.at(0)), m_qbsStdout.constData());
3102 QVERIFY2(stdOut.contains("theProduct2 version: " + version.at(1)), m_qbsStdout.constData());
3103 }
3104
pkgConfigProbe_data()3105 void TestBlackbox::pkgConfigProbe_data()
3106 {
3107 QTest::addColumn<QString>("packageBaseName");
3108 QTest::addColumn<QStringList>("found");
3109 QTest::addColumn<QStringList>("libs");
3110 QTest::addColumn<QStringList>("cflags");
3111 QTest::addColumn<QStringList>("version");
3112
3113 QTest::newRow("existing package")
3114 << "dummy" << (QStringList() << "true" << "true")
3115 << (QStringList() << "[\"-Ldummydir1\",\"-ldummy1\"]"
3116 << "[\"-Ldummydir2\",\"-ldummy2\"]")
3117 << (QStringList() << "[]" << "[]") << (QStringList() << "0.0.1" << "0.0.2");
3118
3119 // Note: The array values should be "undefined", but we lose that information when
3120 // converting to QVariants in the ProjectResolver.
3121 QTest::newRow("non-existing package")
3122 << "blubb" << (QStringList() << "false" << "false") << (QStringList() << "[]" << "[]")
3123 << (QStringList() << "[]" << "[]") << (QStringList() << "undefined" << "undefined");
3124 }
3125
pkgConfigProbeSysroot()3126 void TestBlackbox::pkgConfigProbeSysroot()
3127 {
3128 const QString exe = findExecutable(QStringList() << "pkg-config");
3129 if (exe.isEmpty())
3130 QSKIP("This test requires the pkg-config tool");
3131
3132 QDir::setCurrent(testDataDir + "/pkg-config-probe-sysroot");
3133 QCOMPARE(runQbs(QStringList("-v")), 0);
3134 QCOMPARE(m_qbsStderr.count("PkgConfigProbe: found packages"), 2);
3135 const QString outputTemplate = "theProduct%1 libs: [\"-L%2/usr/dummy\",\"-ldummy1\"]";
3136 QVERIFY2(m_qbsStdout.contains(outputTemplate
3137 .arg("1", QDir::currentPath() + "/sysroot1").toLocal8Bit()),
3138 m_qbsStdout.constData());
3139 QVERIFY2(m_qbsStdout.contains(outputTemplate
3140 .arg("2", QDir::currentPath() + "/sysroot2").toLocal8Bit()),
3141 m_qbsStdout.constData());
3142 QVERIFY2(m_qbsStdout.contains(outputTemplate
3143 .arg("3", QDir::currentPath() + "/sysroot1").toLocal8Bit()),
3144 m_qbsStdout.constData());
3145 }
3146
pluginDependency()3147 void TestBlackbox::pluginDependency()
3148 {
3149 QDir::setCurrent(testDataDir + "/plugin-dependency");
3150
3151 // Build the plugins and the helper2 lib.
3152 QCOMPARE(runQbs(QStringList{"--products", "plugin1,plugin2,plugin3,plugin4,helper2"}), 0);
3153 QVERIFY(m_qbsStdout.contains("plugin1"));
3154 QVERIFY(m_qbsStdout.contains("plugin2"));
3155 QVERIFY(m_qbsStdout.contains("plugin3"));
3156 QVERIFY(m_qbsStdout.contains("plugin4"));
3157 QVERIFY(m_qbsStdout.contains("helper2"));
3158 QVERIFY(!m_qbsStderr.contains("SOFT ASSERT"));
3159
3160 // Build the app. Plugins 1 and 2 must not be linked. Plugin 3 must be linked.
3161 QCOMPARE(runQbs(QStringList{"--command-echo-mode", "command-line"}), 0);
3162 QByteArray output = m_qbsStdout + '\n' + m_qbsStderr;
3163 QVERIFY(!output.contains("plugin1"));
3164 QVERIFY(!output.contains("plugin2"));
3165 QVERIFY(!output.contains("helper2"));
3166
3167 // Test change tracking for parameter in Parameters item.
3168 WAIT_FOR_NEW_TIMESTAMP();
3169 REPLACE_IN_FILE("plugin-dependency.qbs", "false // marker 1", "true");
3170 QCOMPARE(runQbs(QStringList{"-p", "plugin2"}), 0);
3171 QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData());
3172 QCOMPARE(runQbs(QStringList{"--command-echo-mode", "command-line"}), 0);
3173 output = m_qbsStdout + '\n' + m_qbsStderr;
3174 QVERIFY2(!output.contains("plugin1"), output.constData());
3175 QVERIFY2(!output.contains("helper2"), output.constData());
3176 QVERIFY2(output.contains("plugin2"), output.constData());
3177
3178 // Test change tracking for parameter in Depends item.
3179 WAIT_FOR_NEW_TIMESTAMP();
3180 REPLACE_IN_FILE("plugin-dependency.qbs", "false /* marker 2 */", "true");
3181 QCOMPARE(runQbs(QStringList{"-p", "helper1", "--command-echo-mode", "command-line"}), 0);
3182 output = m_qbsStdout + '\n' + m_qbsStderr;
3183 QVERIFY2(output.contains("helper2"), output.constData());
3184
3185 // Check that the build dependency still works.
3186 QCOMPARE(runQbs(QStringLiteral("clean")), 0);
3187 QCOMPARE(runQbs(QStringList{"--products", "myapp", "--command-echo-mode", "command-line"}), 0);
3188 QVERIFY(m_qbsStdout.contains("plugin1"));
3189 QVERIFY(m_qbsStdout.contains("plugin2"));
3190 QVERIFY(m_qbsStdout.contains("plugin3"));
3191 QVERIFY(m_qbsStdout.contains("plugin4"));
3192 }
3193
precompiledAndPrefixHeaders()3194 void TestBlackbox::precompiledAndPrefixHeaders()
3195 {
3196 QDir::setCurrent(testDataDir + "/precompiled-and-prefix-headers");
3197 QCOMPARE(runQbs(), 0);
3198 }
3199
precompiledHeaderAndRedefine()3200 void TestBlackbox::precompiledHeaderAndRedefine()
3201 {
3202 QDir::setCurrent(testDataDir + "/precompiled-headers-and-redefine");
3203 QCOMPARE(runQbs(), 0);
3204 }
3205
preventFloatingPointValues()3206 void TestBlackbox::preventFloatingPointValues()
3207 {
3208 QDir::setCurrent(testDataDir + "/prevent-floating-point-values");
3209 QCOMPARE(runQbs(QStringList("products.p.version:1.50")), 0);
3210 QVERIFY2(m_qbsStdout.contains("version: 1.50"), m_qbsStdout.constData());
3211 }
3212
probeChangeTracking()3213 void TestBlackbox::probeChangeTracking()
3214 {
3215 QDir::setCurrent(testDataDir + "/probe-change-tracking");
3216
3217 // Product probe disabled, other probes enabled.
3218 QbsRunParameters params;
3219 params.command = "resolve";
3220 params.arguments = QStringList("products.theProduct.runProbe:false");
3221 QCOMPARE(runQbs(params), 0);
3222 QVERIFY(m_qbsStdout.contains("running tlpProbe"));
3223 QVERIFY(m_qbsStdout.contains("running subProbe"));
3224 QVERIFY(!m_qbsStdout.contains("running productProbe"));
3225
3226 // Product probe newly enabled.
3227 params.arguments = QStringList("products.theProduct.runProbe:true");
3228 QCOMPARE(runQbs(params), 0);
3229 QVERIFY(!m_qbsStdout.contains("running tlpProbe"));
3230 QVERIFY(!m_qbsStdout.contains("running subProbe"));
3231 QVERIFY(m_qbsStdout.contains("running productProbe: 12"));
3232
3233 // Re-resolving with unchanged probe.
3234 WAIT_FOR_NEW_TIMESTAMP();
3235 touch("probe-change-tracking.qbs");
3236 QCOMPARE(runQbs(params), 0);
3237 QVERIFY(m_qbsStdout.contains("Resolving"));
3238 QVERIFY(!m_qbsStdout.contains("running tlpProbe"));
3239 QVERIFY(!m_qbsStdout.contains("running subProbe"));
3240 QVERIFY(!m_qbsStdout.contains("running productProbe"));
3241
3242 // Re-resolving with changed configure scripts.
3243 WAIT_FOR_NEW_TIMESTAMP();
3244 REPLACE_IN_FILE("probe-change-tracking.qbs", "console.info", " console.info");
3245 QCOMPARE(runQbs(params), 0);
3246 QVERIFY(m_qbsStdout.contains("Resolving"));
3247 QVERIFY(m_qbsStdout.contains("running tlpProbe"));
3248 QVERIFY(m_qbsStdout.contains("running subProbe"));
3249 QVERIFY(m_qbsStdout.contains("running productProbe: 12"));
3250
3251 // Re-resolving with added property.
3252 WAIT_FOR_NEW_TIMESTAMP();
3253 REPLACE_IN_FILE("probe-change-tracking.qbs", "condition: product.runProbe",
3254 "condition: product.runProbe\nproperty string something: 'x'");
3255 QCOMPARE(runQbs(params), 0);
3256 QVERIFY(m_qbsStdout.contains("Resolving"));
3257 QVERIFY(!m_qbsStdout.contains("running tlpProbe"));
3258 QVERIFY(!m_qbsStdout.contains("running subProbe"));
3259 QVERIFY(m_qbsStdout.contains("running productProbe: 12"));
3260
3261 // Re-resolving with changed property.
3262 WAIT_FOR_NEW_TIMESTAMP();
3263 REPLACE_IN_FILE("probe-change-tracking.qbs", "'x'", "'y'");
3264 QCOMPARE(runQbs(params), 0);
3265 QVERIFY(m_qbsStdout.contains("Resolving"));
3266 QVERIFY(!m_qbsStdout.contains("running tlpProbe"));
3267 QVERIFY(!m_qbsStdout.contains("running subProbe"));
3268 QVERIFY(m_qbsStdout.contains("running productProbe: 12"));
3269
3270 // Re-resolving with removed property.
3271 WAIT_FOR_NEW_TIMESTAMP();
3272 REPLACE_IN_FILE("probe-change-tracking.qbs", "property string something: 'y'", "");
3273 QCOMPARE(runQbs(params), 0);
3274 QVERIFY(m_qbsStdout.contains("Resolving"));
3275 QVERIFY(!m_qbsStdout.contains("running tlpProbe"));
3276 QVERIFY(!m_qbsStdout.contains("running subProbe"));
3277 QVERIFY(m_qbsStdout.contains("running productProbe: 12"));
3278
3279 // Re-resolving with unchanged probe again.
3280 WAIT_FOR_NEW_TIMESTAMP();
3281 touch("probe-change-tracking.qbs");
3282 QCOMPARE(runQbs(params), 0);
3283 QVERIFY(m_qbsStdout.contains("Resolving"));
3284 QVERIFY(!m_qbsStdout.contains("running tlpProbe"));
3285 QVERIFY(!m_qbsStdout.contains("running subProbe"));
3286 QVERIFY(!m_qbsStdout.contains("running productProbe"));
3287
3288 // Enforcing re-running via command-line option.
3289 params.arguments.prepend("--force-probe-execution");
3290 QCOMPARE(runQbs(params), 0);
3291 QVERIFY(m_qbsStdout.contains("Resolving"));
3292 QVERIFY(m_qbsStdout.contains("running tlpProbe"));
3293 QVERIFY(m_qbsStdout.contains("running subProbe"));
3294 QVERIFY(m_qbsStdout.contains("running productProbe: 12"));
3295 }
3296
probeProperties()3297 void TestBlackbox::probeProperties()
3298 {
3299 QDir::setCurrent(testDataDir + "/probeProperties");
3300 const QByteArray dir = QDir::cleanPath(testDataDir).toLocal8Bit() + "/probeProperties";
3301 QCOMPARE(runQbs(), 0);
3302 QVERIFY2(m_qbsStdout.contains("probe1.fileName=bin/tool"), m_qbsStdout.constData());
3303 QVERIFY2(m_qbsStdout.contains("probe1.path=" + dir), m_qbsStdout.constData());
3304 QVERIFY2(m_qbsStdout.contains("probe1.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData());
3305 QVERIFY2(m_qbsStdout.contains("probe2.fileName=tool"), m_qbsStdout.constData());
3306 QVERIFY2(m_qbsStdout.contains("probe2.path=" + dir + "/bin"), m_qbsStdout.constData());
3307 QVERIFY2(m_qbsStdout.contains("probe2.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData());
3308 QVERIFY2(m_qbsStdout.contains("probe3.fileName=tool"), m_qbsStdout.constData());
3309 QVERIFY2(m_qbsStdout.contains("probe3.path=" + dir + "/bin"), m_qbsStdout.constData());
3310 QVERIFY2(m_qbsStdout.contains("probe3.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData());
3311 }
3312
probesAndShadowProducts()3313 void TestBlackbox::probesAndShadowProducts()
3314 {
3315 QDir::setCurrent(testDataDir + "/probes-and-shadow-products");
3316 QCOMPARE(runQbs(QStringList("--log-time")), 0);
3317 QVERIFY2(m_qbsStdout.contains("2 probes encountered, 1 configure scripts executed"),
3318 m_qbsStdout.constData());
3319 WAIT_FOR_NEW_TIMESTAMP();
3320 touch("probes-and-shadow-products.qbs");
3321 QCOMPARE(runQbs(QStringList("--log-time")), 0);
3322 QVERIFY2(m_qbsStdout.contains("2 probes encountered, 0 configure scripts executed"),
3323 m_qbsStdout.constData());
3324 }
3325
probeInExportedModule()3326 void TestBlackbox::probeInExportedModule()
3327 {
3328 QDir::setCurrent(testDataDir + "/probe-in-exported-module");
3329 QCOMPARE(runQbs(QbsRunParameters(QStringList() << QStringLiteral("-f")
3330 << QStringLiteral("probe-in-exported-module.qbs"))), 0);
3331 QVERIFY2(m_qbsStdout.contains("found: true"), m_qbsStdout.constData());
3332 QVERIFY2(m_qbsStdout.contains("prop: yes"), m_qbsStdout.constData());
3333 QVERIFY2(m_qbsStdout.contains("listProp: myother,my"), m_qbsStdout.constData());
3334 }
3335
probesAndArrayProperties()3336 void TestBlackbox::probesAndArrayProperties()
3337 {
3338 QDir::setCurrent(testDataDir + "/probes-and-array-properties");
3339 QCOMPARE(runQbs(), 0);
3340 QVERIFY2(m_qbsStdout.contains("prop: [\"probe\"]"), m_qbsStdout.constData());
3341 WAIT_FOR_NEW_TIMESTAMP();
3342 REPLACE_IN_FILE("probes-and-array-properties.qbs", "//", "");
3343 QCOMPARE(runQbs(), 0);
3344 QVERIFY2(m_qbsStdout.contains("prop: [\"product\",\"probe\"]"), m_qbsStdout.constData());
3345 }
3346
productProperties()3347 void TestBlackbox::productProperties()
3348 {
3349 QDir::setCurrent(testDataDir + "/productproperties");
3350 QCOMPARE(runQbs(QbsRunParameters(QStringList() << QStringLiteral("-f")
3351 << QStringLiteral("productproperties.qbs"))), 0);
3352 QVERIFY(regularFileExists(relativeExecutableFilePath("blubb_user")));
3353 }
3354
propertyAssignmentOnNonPresentModule()3355 void TestBlackbox::propertyAssignmentOnNonPresentModule()
3356 {
3357 QDir::setCurrent(testDataDir + "/property-assignment-on-non-present-module");
3358 QCOMPARE(runQbs(), 0);
3359 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
3360 }
3361
propertyAssignmentInFailedModule()3362 void TestBlackbox::propertyAssignmentInFailedModule()
3363 {
3364 QDir::setCurrent(testDataDir + "/property-assignment-in-failed-module");
3365 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:false"))), 0);
3366 QbsRunParameters failParams;
3367 failParams.expectFailure = true;
3368 QVERIFY(runQbs(failParams) != 0);
3369 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:true"))), 0);
3370 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
3371 QEXPECT_FAIL(nullptr, "circular dependency between module merging and validation", Continue);
3372 QCOMPARE(runQbs(failParams), 0);
3373 }
3374
propertyChanges()3375 void TestBlackbox::propertyChanges()
3376 {
3377 QDir::setCurrent(testDataDir + "/propertyChanges");
3378 const QString projectFile("propertyChanges.qbs");
3379 QbsRunParameters params(QStringList({"-f", "propertyChanges.qbs", "qbs.enableDebugCode:true"}));
3380
3381 // Initial build.
3382 QCOMPARE(runQbs(params), 0);
3383 QVERIFY(m_qbsStdout.contains("compiling source1.cpp"));
3384 QVERIFY(m_qbsStdout.contains("compiling source2.cpp"));
3385 QVERIFY(m_qbsStdout.contains("compiling source3.cpp"));
3386 QVERIFY(m_qbsStdout.contains("compiling lib.cpp"));
3387 QVERIFY(m_qbsStdout.contains("linking product 1.debug"));
3388 QVERIFY(m_qbsStdout.contains("generated.txt"));
3389 QVERIFY(m_qbsStdout.contains("Making output from input"));
3390 QVERIFY(m_qbsStdout.contains("default value"));
3391 QVERIFY(m_qbsStdout.contains("Making output from other output"));
3392 QFile generatedFile(relativeProductBuildDir("generated text file") + "/generated.txt");
3393 QVERIFY(generatedFile.open(QIODevice::ReadOnly));
3394 QCOMPARE(generatedFile.readAll(), QByteArray("prefix 1contents 1suffix 1"));
3395 generatedFile.close();
3396
3397 // Incremental build with no changes.
3398 QCOMPARE(runQbs(params), 0);
3399 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3400 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3401 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3402 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp.cpp"));
3403 QVERIFY(!m_qbsStdout.contains("linking"));
3404 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3405 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3406 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3407
3408 // Incremental build with no changes, but updated project file timestamp.
3409 WAIT_FOR_NEW_TIMESTAMP();
3410 touch(projectFile);
3411 QCOMPARE(runQbs(params), 0);
3412 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3413 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3414 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3415 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp"));
3416 QVERIFY(!m_qbsStdout.contains("linking"));
3417 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3418 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3419 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3420
3421 // Incremental build, input property changed for first product
3422 WAIT_FOR_NEW_TIMESTAMP();
3423 REPLACE_IN_FILE(projectFile, "blubb1", "blubb01");
3424 QCOMPARE(runQbs(params), 0);
3425 QVERIFY(m_qbsStdout.contains("compiling source1.cpp"));
3426 QVERIFY(m_qbsStdout.contains("linking product 1.debug"));
3427 QVERIFY(!m_qbsStdout.contains("linking product 2"));
3428 QVERIFY(!m_qbsStdout.contains("linking product 3"));
3429 QVERIFY(!m_qbsStdout.contains("linking library"));
3430 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3431 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3432 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3433
3434 // Incremental build, input property changed via project for second product.
3435 WAIT_FOR_NEW_TIMESTAMP();
3436 REPLACE_IN_FILE(projectFile, "blubb2", "blubb02");
3437 QCOMPARE(runQbs(params), 0);
3438 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3439 QVERIFY(m_qbsStdout.contains("compiling source2.cpp"));
3440 QVERIFY(!m_qbsStdout.contains("linking product 3"));
3441 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3442 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3443 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3444
3445 // Incremental build, input property changed via command line for second product.
3446 params.command = "resolve";
3447 params.arguments << "project.projectDefines:blubb002";
3448 QCOMPARE(runQbs(params), 0);
3449 QCOMPARE(runQbs(), 0);
3450 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3451 QVERIFY(m_qbsStdout.contains("compiling source2.cpp"));
3452 QVERIFY(!m_qbsStdout.contains("linking product 3"));
3453 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3454 params.arguments.removeLast();
3455 QCOMPARE(runQbs(params), 0);
3456 QCOMPARE(runQbs(), 0);
3457 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3458 QVERIFY(m_qbsStdout.contains("compiling source2.cpp"));
3459 QVERIFY(!m_qbsStdout.contains("linking product 3"));
3460 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3461 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3462 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3463
3464 // Incremental build, input property changed via environment for third product.
3465 params.environment.insert("QBS_BLACKBOX_DEFINE", "newvalue");
3466 QCOMPARE(runQbs(params), 0);
3467 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3468 QVERIFY(!m_qbsStdout.contains("linking product 2"));
3469 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3470 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3471 params.environment.remove("QBS_BLACKBOX_DEFINE");
3472 QCOMPARE(runQbs(params), 0);
3473 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3474 QVERIFY(!m_qbsStdout.contains("linking product 2"));
3475 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3476 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3477 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3478 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3479 params.environment.insert("QBS_BLACKBOX_DEFINE", "newvalue");
3480 QCOMPARE(runQbs(params), 0);
3481 QCOMPARE(runQbs(), 0);
3482 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3483 QVERIFY(!m_qbsStdout.contains("linking product 2"));
3484 QVERIFY(m_qbsStdout.contains("compiling source3.cpp"));
3485 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3486 params.environment.remove("QBS_BLACKBOX_DEFINE");
3487 QCOMPARE(runQbs(params), 0);
3488 QCOMPARE(runQbs(), 0);
3489 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3490 QVERIFY(!m_qbsStdout.contains("linking product 2"));
3491 QVERIFY(m_qbsStdout.contains("compiling source3.cpp"));
3492 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3493 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3494 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3495
3496 // Incremental build, module property changed via command line.
3497 params.arguments << "qbs.enableDebugCode:false";
3498 QCOMPARE(runQbs(params), 0);
3499 QCOMPARE(runQbs(), 0);
3500 QVERIFY(m_qbsStdout.contains("compiling source1.cpp"));
3501 QVERIFY(m_qbsStdout.contains("linking product 1.release"));
3502 QVERIFY(m_qbsStdout.contains("compiling source2.cpp"));
3503 QVERIFY(m_qbsStdout.contains("compiling source3.cpp"));
3504 QVERIFY(m_qbsStdout.contains("compiling lib.cpp"));
3505 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3506 params.arguments.removeLast();
3507 QCOMPARE(runQbs(params), 0);
3508 QCOMPARE(runQbs(), 0);
3509 QVERIFY(m_qbsStdout.contains("compiling source1.cpp"));
3510 QVERIFY(m_qbsStdout.contains("linking product 1.debug"));
3511 QVERIFY(m_qbsStdout.contains("compiling source2.cpp"));
3512 QVERIFY(m_qbsStdout.contains("compiling source3.cpp"));
3513 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3514 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3515 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3516
3517 // Incremental build, non-essential dependency removed.
3518 WAIT_FOR_NEW_TIMESTAMP();
3519 REPLACE_IN_FILE(projectFile, "Depends { name: 'library' }", "// Depends { name: 'library' }");
3520 params.command = "build";
3521 QCOMPARE(runQbs(params), 0);
3522 QVERIFY(!m_qbsStdout.contains("linking product 1"));
3523 QVERIFY(m_qbsStdout.contains("linking product 2"));
3524 QVERIFY(!m_qbsStdout.contains("linking product 3"));
3525 QVERIFY(!m_qbsStdout.contains("linking library"));
3526 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3527 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3528 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3529
3530 // Incremental build, prepare script of a transformer changed.
3531 WAIT_FOR_NEW_TIMESTAMP();
3532 REPLACE_IN_FILE(projectFile, "contents 1", "contents 2");
3533 QCOMPARE(runQbs(params), 0);
3534 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3535 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3536 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3537 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp"));
3538 QVERIFY(m_qbsStdout.contains("generated.txt"));
3539 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3540 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3541 QVERIFY(generatedFile.open(QIODevice::ReadOnly));
3542 QCOMPARE(generatedFile.readAll(), QByteArray("prefix 1contents 2suffix 1"));
3543 generatedFile.close();
3544
3545 // Incremental build, product property used in JavaScript command changed.
3546 WAIT_FOR_NEW_TIMESTAMP();
3547 REPLACE_IN_FILE(projectFile, "prefix 1", "prefix 2");
3548 QCOMPARE(runQbs(params), 0);
3549 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3550 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3551 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3552 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp"));
3553 QVERIFY(m_qbsStdout.contains("generated.txt"));
3554 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3555 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3556 QVERIFY(generatedFile.open(QIODevice::ReadOnly));
3557 QCOMPARE(generatedFile.readAll(), QByteArray("prefix 2contents 2suffix 1"));
3558 generatedFile.close();
3559
3560 // Incremental build, project property used in JavaScript command changed.
3561 WAIT_FOR_NEW_TIMESTAMP();
3562 REPLACE_IN_FILE(projectFile, "suffix 1", "suffix 2");
3563 QCOMPARE(runQbs(params), 0);
3564 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3565 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3566 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3567 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp"));
3568 QVERIFY(m_qbsStdout.contains("generated.txt"));
3569 QVERIFY(!m_qbsStdout.contains("Making output from input"));
3570 QVERIFY(!m_qbsStdout.contains("Making output from other output"));
3571 QVERIFY(generatedFile.open(QIODevice::ReadOnly));
3572 QCOMPARE(generatedFile.readAll(), QByteArray("prefix 2contents 2suffix 2"));
3573 generatedFile.close();
3574
3575 // Incremental build, module property used in JavaScript command changed.
3576 WAIT_FOR_NEW_TIMESTAMP();
3577 REPLACE_IN_FILE(projectFile, "default value", "new value");
3578 QCOMPARE(runQbs(params), 0);
3579 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3580 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3581 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3582 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp"));
3583 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3584 QVERIFY(m_qbsStdout.contains("Making output from input"));
3585 QVERIFY(m_qbsStdout.contains("Making output from other output"));
3586 QVERIFY(m_qbsStdout.contains("new value"));
3587
3588 // Incremental build, prepare script of a rule in a module changed.
3589 WAIT_FOR_NEW_TIMESTAMP();
3590 REPLACE_IN_FILE("modules/TestModule/module.qbs", "// console.info('Change in source code')",
3591 "console.info('Change in source code')");
3592 QCOMPARE(runQbs(params), 0);
3593 QVERIFY(!m_qbsStdout.contains("compiling source1.cpp"));
3594 QVERIFY(!m_qbsStdout.contains("compiling source2.cpp"));
3595 QVERIFY(!m_qbsStdout.contains("compiling source3.cpp"));
3596 QVERIFY(!m_qbsStdout.contains("compiling lib.cpp"));
3597 QVERIFY(!m_qbsStdout.contains("generated.txt"));
3598 QVERIFY(m_qbsStdout.contains("Making output from input"));
3599 QVERIFY(m_qbsStdout.contains("Making output from other output"));
3600 }
3601
propertyEvaluationContext()3602 void TestBlackbox::propertyEvaluationContext()
3603 {
3604 const QString testDir = testDataDir + "/property-evaluation-context";
3605 QDir::setCurrent(testDir);
3606 QCOMPARE(runQbs(), 0);
3607 QCOMPARE(m_qbsStdout.count("base.productInBase evaluated in: myapp"), 1);
3608 QCOMPARE(m_qbsStdout.count("base.productInTop evaluated in: myapp"), 1);
3609 QCOMPARE(m_qbsStdout.count("top.productInExport evaluated in: mylib"), 1);
3610 QCOMPARE(m_qbsStdout.count("top.productInTop evaluated in: myapp"), 1);
3611 }
3612
qtBug51237()3613 void TestBlackbox::qtBug51237()
3614 {
3615 const SettingsPtr s = settings();
3616 qbs::Internal::TemporaryProfile profile("qbs_autotests_qtBug51237", s.get());
3617 profile.p.setValue("mymodule.theProperty", QStringList());
3618 s->sync();
3619
3620 QDir::setCurrent(testDataDir + "/QTBUG-51237");
3621 QbsRunParameters params;
3622 params.profile = profile.p.name();
3623 QCOMPARE(runQbs(params), 0);
3624 }
3625
dynamicMultiplexRule()3626 void TestBlackbox::dynamicMultiplexRule()
3627 {
3628 const QString testDir = testDataDir + "/dynamicMultiplexRule";
3629 QDir::setCurrent(testDir);
3630 QCOMPARE(runQbs(), 0);
3631 const QString outputFilePath = relativeProductBuildDir("dynamicMultiplexRule") + "/stuff-from-3-inputs";
3632 QVERIFY(regularFileExists(outputFilePath));
3633 WAIT_FOR_NEW_TIMESTAMP();
3634 touch("two.txt");
3635 QCOMPARE(runQbs(), 0);
3636 QVERIFY(regularFileExists(outputFilePath));
3637 }
3638
dynamicProject()3639 void TestBlackbox::dynamicProject()
3640 {
3641 const QString testDir = testDataDir + "/dynamic-project";
3642 QDir::setCurrent(testDir);
3643 QCOMPARE(runQbs(), 0);
3644 QCOMPARE(m_qbsStdout.count("compiling main.cpp"), 2);
3645 }
3646
dynamicRuleOutputs()3647 void TestBlackbox::dynamicRuleOutputs()
3648 {
3649 const QString testDir = testDataDir + "/dynamicRuleOutputs";
3650 QDir::setCurrent(testDir);
3651 if (QFile::exists("work"))
3652 rmDirR("work");
3653 QDir().mkdir("work");
3654 ccp("before", "work");
3655 QDir::setCurrent(testDir + "/work");
3656 QCOMPARE(runQbs(), 0);
3657
3658 const QString appFile = relativeExecutableFilePath("genlexer");
3659 const QString headerFile1 = relativeProductBuildDir("genlexer") + "/GeneratedFiles/numberscanner.h";
3660 const QString sourceFile1 = relativeProductBuildDir("genlexer") + "/GeneratedFiles/numberscanner.c";
3661 const QString sourceFile2 = relativeProductBuildDir("genlexer") + "/GeneratedFiles/lex.yy.c";
3662
3663 // Check build #1: source and header file name are specified in numbers.l
3664 QVERIFY(regularFileExists(appFile));
3665 QVERIFY(regularFileExists(headerFile1));
3666 QVERIFY(regularFileExists(sourceFile1));
3667 QVERIFY(!QFile::exists(sourceFile2));
3668
3669 QDateTime appFileTimeStamp1 = QFileInfo(appFile).lastModified();
3670 WAIT_FOR_NEW_TIMESTAMP();
3671 copyFileAndUpdateTimestamp("../after/numbers.l", "numbers.l");
3672 QCOMPARE(runQbs(), 0);
3673
3674 // Check build #2: no file names are specified in numbers.l
3675 // flex will default to lex.yy.c without header file.
3676 QDateTime appFileTimeStamp2 = QFileInfo(appFile).lastModified();
3677 QVERIFY(appFileTimeStamp1 < appFileTimeStamp2);
3678 QVERIFY(!QFile::exists(headerFile1));
3679 QVERIFY(!QFile::exists(sourceFile1));
3680 QVERIFY(regularFileExists(sourceFile2));
3681
3682 WAIT_FOR_NEW_TIMESTAMP();
3683 copyFileAndUpdateTimestamp("../before/numbers.l", "numbers.l");
3684 QCOMPARE(runQbs(), 0);
3685
3686 // Check build #3: source and header file name are specified in numbers.l
3687 QDateTime appFileTimeStamp3 = QFileInfo(appFile).lastModified();
3688 QVERIFY(appFileTimeStamp2 < appFileTimeStamp3);
3689 QVERIFY(regularFileExists(appFile));
3690 QVERIFY(regularFileExists(headerFile1));
3691 QVERIFY(regularFileExists(sourceFile1));
3692 QVERIFY(!QFile::exists(sourceFile2));
3693 }
3694
emptyProfile()3695 void TestBlackbox::emptyProfile()
3696 {
3697 QDir::setCurrent(testDataDir + "/empty-profile");
3698
3699 const SettingsPtr s = settings();
3700 const Profile buildProfile(profileName(), s.get());
3701 bool isMsvc = false;
3702 auto toolchainType = buildProfile.value(QStringLiteral("qbs.toolchainType")).toString();
3703 QbsRunParameters params;
3704 params.profile = "none";
3705
3706 if (toolchainType.isEmpty()) {
3707 const auto toolchain = buildProfile.value(QStringLiteral("qbs.toolchain")).toStringList();
3708 if (!toolchain.isEmpty())
3709 toolchainType = toolchain.first();
3710 }
3711 if (!toolchainType.isEmpty()) {
3712 params.arguments = QStringList{QStringLiteral("qbs.toolchainType:") + toolchainType};
3713 isMsvc = toolchainType == "msvc" || toolchainType == "clang-cl";
3714 }
3715
3716 if (!isMsvc) {
3717 const auto tcPath =
3718 QDir::toNativeSeparators(
3719 buildProfile.value(QStringLiteral("cpp.toolchainInstallPath")).toString());
3720 auto paths = params.environment.value(QStringLiteral("PATH"))
3721 .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
3722 if (!tcPath.isEmpty() && !paths.contains(tcPath)) {
3723 paths.prepend(tcPath);
3724 params.environment.insert(
3725 QStringLiteral("PATH"), paths.join(HostOsInfo::pathListSeparator()));
3726 }
3727 }
3728 QCOMPARE(runQbs(params), 0);
3729 }
3730
erroneousFiles_data()3731 void TestBlackbox::erroneousFiles_data()
3732 {
3733 QTest::addColumn<QString>("errorMessage");
3734 QTest::newRow("nonexistentWorkingDir")
3735 << "The working directory '.does.not.exist' for process '.*ls.*' is invalid.";
3736 QTest::newRow("outputArtifacts-missing-filePath")
3737 << "Error in Rule\\.outputArtifacts\\[0\\]\n\r?"
3738 "Property filePath must be a non-empty string\\.";
3739 QTest::newRow("outputArtifacts-missing-fileTags")
3740 << "Error in Rule\\.outputArtifacts\\[0\\]\n\r?"
3741 "Property fileTags for artifact 'outputArtifacts-missing-fileTags\\.txt' "
3742 "must be a non-empty string list\\.";
3743 QTest::newRow("texttemplate-unknown-placeholder")
3744 << "Placeholder 'what' is not defined in textemplate.dict for 'boom.txt.in'";
3745 QTest::newRow("tag-mismatch")
3746 << "tag-mismatch.qbs:8:18.*Artifact '.*dummy1' has undeclared file tags "
3747 "\\[\"y\",\"z\"\\].";
3748 }
3749
erroneousFiles()3750 void TestBlackbox::erroneousFiles()
3751 {
3752 QFETCH(QString, errorMessage);
3753 QDir::setCurrent(testDataDir + "/erroneous/" + QTest::currentDataTag());
3754 QbsRunParameters params;
3755 params.expectFailure = true;
3756 QVERIFY(runQbs(params) != 0);
3757 QString err = QString::fromLocal8Bit(m_qbsStderr);
3758 if (!err.contains(QRegularExpression(errorMessage))) {
3759 qDebug().noquote() << "Output: " << err;
3760 qDebug().noquote() << "Expected: " << errorMessage;
3761 QFAIL("Unexpected error message.");
3762 }
3763 }
3764
errorInfo()3765 void TestBlackbox::errorInfo()
3766 {
3767 QDir::setCurrent(testDataDir + "/error-info");
3768 QCOMPARE(runQbs(), 0);
3769
3770 QbsRunParameters resolveParams;
3771 QbsRunParameters buildParams;
3772 buildParams.expectFailure = true;
3773
3774 resolveParams.command = "resolve";
3775 resolveParams.arguments = QStringList() << "project.fail1:true";
3776 QCOMPARE(runQbs(resolveParams), 0);
3777 buildParams.arguments = resolveParams.arguments;
3778 QVERIFY(runQbs(buildParams) != 0);
3779 QVERIFY2(m_qbsStderr.contains("error-info.qbs:24"), m_qbsStderr);
3780
3781 resolveParams.arguments = QStringList() << "project.fail2:true";
3782 QCOMPARE(runQbs(resolveParams), 0);
3783 buildParams.arguments = resolveParams.arguments;
3784 QVERIFY(runQbs(buildParams) != 0);
3785 QVERIFY2(m_qbsStderr.contains("error-info.qbs:36"), m_qbsStderr);
3786
3787 resolveParams.arguments = QStringList() << "project.fail3:true";
3788 QCOMPARE(runQbs(resolveParams), 0);
3789 buildParams.arguments = resolveParams.arguments;
3790 QVERIFY(runQbs(buildParams) != 0);
3791 QVERIFY2(m_qbsStderr.contains("error-info.qbs:51"), m_qbsStderr);
3792
3793 resolveParams.arguments = QStringList() << "project.fail4:true";
3794 QCOMPARE(runQbs(resolveParams), 0);
3795 buildParams.arguments = resolveParams.arguments;
3796 QVERIFY(runQbs(buildParams) != 0);
3797 QVERIFY2(m_qbsStderr.contains("error-info.qbs:66"), m_qbsStderr);
3798
3799 resolveParams.arguments = QStringList() << "project.fail5:true";
3800 QCOMPARE(runQbs(resolveParams), 0);
3801 buildParams.arguments = resolveParams.arguments;
3802 QVERIFY(runQbs(buildParams) != 0);
3803 QVERIFY2(m_qbsStderr.contains("helper.js:4"), m_qbsStderr);
3804
3805 resolveParams.arguments = QStringList() << "project.fail6:true";
3806 QCOMPARE(runQbs(resolveParams), 0);
3807 buildParams.arguments = resolveParams.arguments;
3808 QVERIFY(runQbs(buildParams) != 0);
3809 QVERIFY2(m_qbsStderr.contains("helper.js:8"), m_qbsStderr);
3810
3811 resolveParams.arguments = QStringList() << "project.fail7:true";
3812 QCOMPARE(runQbs(resolveParams), 0);
3813 buildParams.arguments = resolveParams.arguments;
3814 QVERIFY(runQbs(buildParams) != 0);
3815 QVERIFY2(m_qbsStderr.contains("JavaScriptCommand.sourceCode"), m_qbsStderr);
3816 QVERIFY2(m_qbsStderr.contains("error-info.qbs:57"), m_qbsStderr);
3817 }
3818
escapedLinkerFlags()3819 void TestBlackbox::escapedLinkerFlags()
3820 {
3821 const SettingsPtr s = settings();
3822 const Profile buildProfile(profileName(), s.get());
3823 const QStringList toolchain = profileToolchain(buildProfile);
3824 if (!toolchain.contains("gcc"))
3825 QSKIP("escaped linker flags test only applies with gcc and GNU ld");
3826 if (targetOs() == HostOsInfo::HostOsMacos)
3827 QSKIP("Does not apply on macOS");
3828 QDir::setCurrent(testDataDir + "/escaped-linker-flags");
3829 QbsRunParameters params(QStringList("products.app.escapeLinkerFlags:false"));
3830 QCOMPARE(runQbs(params), 0);
3831 params.command = "resolve";
3832 params.arguments = QStringList() << "products.app.escapeLinkerFlags:true";
3833 QCOMPARE(runQbs(params), 0);
3834 params.command = "build";
3835 params.expectFailure = true;
3836 QVERIFY(runQbs(params) != 0);
3837 QVERIFY2(m_qbsStderr.contains("Encountered escaped linker flag"), m_qbsStderr.constData());
3838 }
3839
exportedDependencyInDisabledProduct()3840 void TestBlackbox::exportedDependencyInDisabledProduct()
3841 {
3842 QDir::setCurrent(testDataDir + "/exported-dependency-in-disabled-product");
3843 QFETCH(QString, depCondition);
3844 QFETCH(bool, compileExpected);
3845 rmDirR(relativeBuildDir());
3846 const QString propertyArg = "products.dep.conditionString:" + depCondition;
3847 QCOMPARE(runQbs(QStringList(propertyArg)), 0);
3848 QCOMPARE(m_qbsStdout.contains("compiling"), compileExpected);
3849 }
3850
exportedDependencyInDisabledProduct_data()3851 void TestBlackbox::exportedDependencyInDisabledProduct_data()
3852 {
3853 QTest::addColumn<QString>("depCondition");
3854 QTest::addColumn<bool>("compileExpected");
3855 QTest::newRow("dependency enabled") << "true" << true;
3856 QTest::newRow("dependency directly disabled") << "false" << false;
3857 QTest::newRow("dependency disabled via non-present module") << "nosuchmodule.present" << false;
3858 QTest::newRow("dependency disabled via failed module") << "broken.present" << false;
3859 }
3860
exportedPropertyInDisabledProduct()3861 void TestBlackbox::exportedPropertyInDisabledProduct()
3862 {
3863 QDir::setCurrent(testDataDir + "/exported-property-in-disabled-product");
3864 QFETCH(QString, depCondition);
3865 QFETCH(bool, successExpected);
3866 const QString propertyArg = "products.dep.conditionString:" + depCondition;
3867 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(propertyArg))), 0);
3868 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
3869 QbsRunParameters buildParams;
3870 buildParams.expectFailure = !successExpected;
3871 QCOMPARE(runQbs(buildParams) == 0, successExpected);
3872 }
3873
exportedPropertyInDisabledProduct_data()3874 void TestBlackbox::exportedPropertyInDisabledProduct_data()
3875 {
3876 QTest::addColumn<QString>("depCondition");
3877 QTest::addColumn<bool>("successExpected");
3878 QTest::newRow("dependency enabled") << "true" << false;
3879 QTest::newRow("dependency directly disabled") << "false" << true;
3880 QTest::newRow("dependency disabled via non-present module") << "nosuchmodule.present" << true;
3881 QTest::newRow("dependency disabled via failed module") << "broken.present" << true;
3882 }
3883
systemRunPaths()3884 void TestBlackbox::systemRunPaths()
3885 {
3886 const SettingsPtr s = settings();
3887 const Profile buildProfile(profileName(), s.get());
3888 switch (targetOs()) {
3889 case HostOsInfo::HostOsLinux:
3890 case HostOsInfo::HostOsMacos:
3891 case HostOsInfo::HostOsOtherUnix:
3892 break;
3893 default:
3894 QSKIP("only applies on Unix");
3895 }
3896
3897 const QString lddFilePath = findExecutable(QStringList() << "ldd");
3898 if (lddFilePath.isEmpty())
3899 QSKIP("ldd not found");
3900 QDir::setCurrent(testDataDir + "/system-run-paths");
3901 QFETCH(bool, setRunPaths);
3902 rmDirR(relativeBuildDir());
3903 QbsRunParameters params;
3904 params.arguments << QString("project.setRunPaths:") + (setRunPaths ? "true" : "false");
3905 QCOMPARE(runQbs(params), 0);
3906 QProcess ldd;
3907 ldd.start(lddFilePath, QStringList() << relativeExecutableFilePath("app"));
3908 QVERIFY2(ldd.waitForStarted(), qPrintable(ldd.errorString()));
3909 QVERIFY2(ldd.waitForFinished(), qPrintable(ldd.errorString()));
3910 QVERIFY2(ldd.exitCode() == 0, ldd.readAllStandardError().constData());
3911 const QByteArray output = ldd.readAllStandardOutput();
3912 const QList<QByteArray> outputLines = output.split('\n');
3913 QByteArray libLine;
3914 for (const auto &line : outputLines) {
3915 if (line.contains("theLib")) {
3916 libLine = line;
3917 break;
3918 }
3919 }
3920 QVERIFY2(!libLine.isEmpty(), output.constData());
3921 QVERIFY2(setRunPaths == libLine.contains("not found"), libLine.constData());
3922 }
3923
systemRunPaths_data()3924 void TestBlackbox::systemRunPaths_data()
3925 {
3926 QTest::addColumn<bool>("setRunPaths");
3927 QTest::newRow("do not set system run paths") << false;
3928 QTest::newRow("do set system run paths") << true;
3929 }
3930
exportRule()3931 void TestBlackbox::exportRule()
3932 {
3933 QDir::setCurrent(testDataDir + "/export-rule");
3934 QbsRunParameters params(QStringList{"modules.blubber.enableTagger:false"});
3935 params.expectFailure = true;
3936 QVERIFY(runQbs(params) != 0);
3937 params.command = "resolve";
3938 params.arguments = QStringList{"modules.blubber.enableTagger:true"};
3939 params.expectFailure = false;
3940 QCOMPARE(runQbs(params), 0);
3941 QCOMPARE(runQbs(), 0);
3942 QVERIFY2(m_qbsStdout.contains("Creating C++ source file"), m_qbsStdout.constData());
3943 QVERIFY2(m_qbsStdout.contains("compiling myapp.cpp"), m_qbsStdout.constData());
3944 }
3945
exportToOutsideSearchPath()3946 void TestBlackbox::exportToOutsideSearchPath()
3947 {
3948 QDir::setCurrent(testDataDir + "/export-to-outside-searchpath");
3949 QbsRunParameters params;
3950 params.expectFailure = true;
3951 QVERIFY(runQbs(params) != 0);
3952 QVERIFY2(m_qbsStderr.contains("Dependency 'aModule' not found for product 'theProduct'."),
3953 m_qbsStderr.constData());
3954 }
3955
exportsPkgconfig()3956 void TestBlackbox::exportsPkgconfig()
3957 {
3958 QDir::setCurrent(testDataDir + "/exports-pkgconfig");
3959 QCOMPARE(runQbs(), 0);
3960 QVERIFY2(m_qbsStdout.contains("Creating TheFirstLib.pc"), m_qbsStdout.constData());
3961 QVERIFY2(m_qbsStdout.contains("Creating TheSecondLib.pc"), m_qbsStdout.constData());
3962 QFile sourcePcFile(HostOsInfo::isWindowsHost() ? "TheFirstLib_windows.pc" : "TheFirstLib.pc");
3963 QString generatedPcFilePath = relativeProductBuildDir("TheFirstLib") + "/TheFirstLib.pc";
3964 QFile generatedPcFile(generatedPcFilePath);
3965 QVERIFY2(sourcePcFile.open(QIODevice::ReadOnly), qPrintable(sourcePcFile.errorString()));
3966 QVERIFY2(generatedPcFile.open(QIODevice::ReadOnly), qPrintable(generatedPcFile.errorString()));
3967 QCOMPARE(generatedPcFile.readAll().replace("\r", ""), sourcePcFile.readAll().replace("\r", ""));
3968 sourcePcFile.close();
3969 generatedPcFile.close();
3970 TEXT_FILE_COMPARE(relativeProductBuildDir("TheSecondLib") + "/TheSecondLib.pc",
3971 "TheSecondLib.pc");
3972 WAIT_FOR_NEW_TIMESTAMP();
3973 touch("firstlib.cpp");
3974 QCOMPARE(runQbs(), 0);
3975 QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData());
3976 QVERIFY2(!m_qbsStdout.contains("Creating TheFirstLib.pc"), m_qbsStdout.constData());
3977 QVERIFY2(!m_qbsStdout.contains("Creating TheSecondLib.pc"), m_qbsStdout.constData());
3978 }
3979
exportsQbs()3980 void TestBlackbox::exportsQbs()
3981 {
3982 QDir::setCurrent(testDataDir + "/exports-qbs");
3983
3984 QCOMPARE(runQbs({"resolve", {"-f", "exports-qbs.qbs"}}), 0);
3985 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
3986 QSKIP("Cannot run binaries in cross-compiled build");
3987 // First we build exportable products and use them (as products) inside
3988 // the original project.
3989 QCOMPARE(runQbs(QStringList{"-f", "exports-qbs.qbs", "--command-echo-mode", "command-line"}),
3990 0);
3991 QVERIFY2(m_qbsStdout.contains("somelocaldir"), m_qbsStdout.constData());
3992
3993 // Now we build an external product against the modules that were just installed.
3994 // We try debug and release mode; one module exists for each of them.
3995 QbsRunParameters paramsExternalBuild(QStringList{"-f", "consumer.qbs",
3996 "--command-echo-mode", "command-line",
3997 "modules.qbs.buildVariant:debug",});
3998 paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-debug";
3999 QCOMPARE(runQbs(paramsExternalBuild), 0);
4000 QVERIFY2(!m_qbsStdout.contains("somelocaldir"), m_qbsStdout.constData());
4001
4002 paramsExternalBuild.arguments = QStringList{"-f", "consumer.qbs",
4003 "modules.qbs.buildVariant:release"};
4004 paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-release";
4005 QCOMPARE(runQbs(paramsExternalBuild), 0);
4006
4007 // Trying to build with an unsupported build variant must fail.
4008 paramsExternalBuild.arguments = QStringList{"-f", "consumer.qbs",
4009 "modules.qbs.buildVariant:profiling"};
4010 paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-profile";
4011 paramsExternalBuild.expectFailure = true;
4012 QVERIFY(runQbs(paramsExternalBuild) != 0);
4013 QVERIFY2(m_qbsStderr.contains("MyLib could not be loaded"), m_qbsStderr.constData());
4014
4015 // Removing the condition from the generated module leaves us with two conflicting
4016 // candidates.
4017 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{ "-f", "exports-qbs.qbs",
4018 "modules.Exporter.qbs.additionalContent:''"})), 0);
4019 QCOMPARE(runQbs(), 0);
4020 QVERIFY(runQbs(paramsExternalBuild) != 0);
4021 QVERIFY2(m_qbsStderr.contains("There is more than one equally prioritized candidate "
4022 "for module 'MyLib'."), m_qbsStderr.constData());
4023
4024 // Change tracking for accesses to product.exports (negative).
4025 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"-f", "exports-qbs.qbs"})), 0);
4026 QCOMPARE(runQbs(), 0);
4027 QVERIFY2(m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData());
4028 WAIT_FOR_NEW_TIMESTAMP();
4029 touch("exports-qbs.qbs");
4030 QCOMPARE(runQbs(QStringList({"-p", "MyTool"})), 0);
4031 QVERIFY2(!m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData());
4032
4033 // Rebuilding the target binary should not cause recreating the module file.
4034 WAIT_FOR_NEW_TIMESTAMP();
4035 touch("mylib.cpp");
4036 QCOMPARE(runQbs(), 0);
4037 QVERIFY2(m_qbsStdout.count("linking") >= 2, m_qbsStdout.constData());
4038 QVERIFY2(!m_qbsStdout.contains("Creating MyLib"), m_qbsStdout.constData());
4039 QVERIFY2(!m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData());
4040
4041 // Changing a setting that influences the name of a target artifact should cause
4042 // recreating the module file.
4043 const QbsRunParameters resolveParams("resolve", QStringList{"-f", "exports-qbs.qbs",
4044 "modules.cpp.dynamicLibrarySuffix:.blubb"});
4045 QCOMPARE(runQbs(resolveParams), 0);
4046 QCOMPARE(runQbs(), 0);
4047 QVERIFY2(m_qbsStdout.count("linking") >= 2, m_qbsStdout.constData());
4048 QVERIFY2(m_qbsStdout.count("Creating MyLib") == 2, m_qbsStdout.constData());
4049 QVERIFY2(!m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData());
4050
4051 // Change tracking for accesses to product.exports (positive).
4052 WAIT_FOR_NEW_TIMESTAMP();
4053 REPLACE_IN_FILE("tool.qbs", "exportingProduct.toolTags", "[]");
4054 QCOMPARE(runQbs(QStringList({"-p", "MyTool"})), 0);
4055 QVERIFY2(m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData());
4056 }
4057
externalLibs()4058 void TestBlackbox::externalLibs()
4059 {
4060 QDir::setCurrent(testDataDir + "/external-libs");
4061 QCOMPARE(runQbs(), 0);
4062 }
4063
fileDependencies()4064 void TestBlackbox::fileDependencies()
4065 {
4066 QDir::setCurrent(testDataDir + "/fileDependencies");
4067 rmDirR(relativeBuildDir());
4068 QCOMPARE(runQbs(), 0);
4069 QVERIFY(m_qbsStdout.contains("compiling narf.cpp"));
4070 QVERIFY(m_qbsStdout.contains("compiling zort.cpp"));
4071 const QString productFileName = relativeExecutableFilePath("myapp");
4072 QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName));
4073
4074 // Incremental build without changes.
4075 QCOMPARE(runQbs(), 0);
4076 QVERIFY(!m_qbsStdout.contains("compiling"));
4077 QVERIFY(!m_qbsStdout.contains("linking"));
4078
4079 // Incremental build with changed file dependency.
4080 WAIT_FOR_NEW_TIMESTAMP();
4081 touch("awesomelib/awesome.h");
4082 QCOMPARE(runQbs(), 0);
4083 QVERIFY(m_qbsStdout.contains("compiling narf.cpp"));
4084 QVERIFY(!m_qbsStdout.contains("compiling zort.cpp"));
4085
4086 // Incremental build with changed 2nd level file dependency.
4087 WAIT_FOR_NEW_TIMESTAMP();
4088 touch("awesomelib/magnificent.h");
4089 QCOMPARE(runQbs(), 0);
4090 QVERIFY(m_qbsStdout.contains("compiling narf.cpp"));
4091 QVERIFY(!m_qbsStdout.contains("compiling zort.cpp"));
4092
4093 // Change the product in between to force the list of dependencies to get rescued.
4094 REPLACE_IN_FILE("fileDependencies.qbs", "//", "");
4095 QCOMPARE(runQbs(), 0);
4096 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
4097 QVERIFY(!m_qbsStdout.contains("compiling narf.cpp"));
4098 QVERIFY(!m_qbsStdout.contains("compiling zort.cpp"));
4099 WAIT_FOR_NEW_TIMESTAMP();
4100 touch("awesomelib/magnificent.h");
4101 QCOMPARE(runQbs(), 0);
4102 QVERIFY2(m_qbsStdout.contains("compiling narf.cpp [myapp]"), m_qbsStdout.constData());
4103 QVERIFY(!m_qbsStdout.contains("compiling zort.cpp"));
4104 }
4105
fileTagsFilterMerging()4106 void TestBlackbox::fileTagsFilterMerging()
4107 {
4108 QDir::setCurrent(testDataDir + "/filetagsfilter-merging");
4109 QCOMPARE(runQbs(QStringList{"-f", "filetagsfilter-merging.qbs"}), 0);
4110 const QString installedApp = defaultInstallRoot + "/myapp/binDir/"
4111 + QFileInfo(relativeExecutableFilePath("myapp")).fileName();
4112 QVERIFY2(QFile::exists(installedApp), qPrintable(installedApp));
4113 const QString otherOutput = relativeProductBuildDir("myapp") + "/myapp.txt";
4114 QVERIFY2(QFile::exists(otherOutput), qPrintable(otherOutput));
4115 }
4116
freedesktop()4117 void TestBlackbox::freedesktop()
4118 {
4119 if (!HostOsInfo::isAnyUnixHost())
4120 QSKIP("only applies on Unix");
4121 if (HostOsInfo::isMacosHost())
4122 QSKIP("Does not apply on macOS");
4123 QDir::setCurrent(testDataDir + "/freedesktop");
4124 QCOMPARE(runQbs(), 0);
4125
4126 // Check desktop file
4127 QString desktopFilePath =
4128 defaultInstallRoot + "/usr/local/share/applications/myapp.desktop";
4129 QVERIFY(QFile::exists(desktopFilePath));
4130 QFile desktopFile(desktopFilePath);
4131 QVERIFY2(desktopFile.open(QIODevice::ReadOnly), qPrintable(desktopFile.errorString()));
4132 QByteArrayList lines = desktopFile.readAll().split('\n');
4133 // Automatically filled line:
4134 QVERIFY(lines.contains("Exec=main"));
4135 // Name specified in `freedesktop.name` property
4136 QVERIFY(lines.contains("Name=My App"));
4137 // Overridden line:
4138 QVERIFY(lines.contains("Icon=myapp.png"));
4139 // Untouched line:
4140 QVERIFY(lines.contains("Terminal=false"));
4141
4142 // Check AppStream file
4143 QVERIFY(QFile::exists(defaultInstallRoot +
4144 "/usr/local/share/metainfo/myapp.appdata.xml"));
4145
4146 // Check icon file
4147 QVERIFY(QFile::exists(defaultInstallRoot +
4148 "/usr/local/share/icons/hicolor/scalable/apps/myapp.png"));
4149 }
4150
installedTransformerOutput()4151 void TestBlackbox::installedTransformerOutput()
4152 {
4153 QDir::setCurrent(testDataDir + "/installed-transformer-output");
4154 QCOMPARE(runQbs(), 0);
4155 const QString installedFilePath = defaultInstallRoot + "/textfiles/HelloWorld.txt";
4156 QVERIFY2(QFile::exists(installedFilePath), qPrintable(installedFilePath));
4157 }
4158
installLocations_data()4159 void TestBlackbox::installLocations_data()
4160 {
4161 QTest::addColumn<QString>("binDir");
4162 QTest::addColumn<QString>("dllDir");
4163 QTest::addColumn<QString>("libDir");
4164 QTest::addColumn<QString>("pluginDir");
4165 QTest::addColumn<QString>("dsymDir");
4166 QTest::newRow("explicit values")
4167 << QString("bindir")
4168 << QString("dlldir")
4169 << QString("libdir")
4170 << QString("pluginDir")
4171 << QString("dsymDir");
4172 QTest::newRow("default values")
4173 << QString() << QString() << QString() << QString() << QString();
4174 }
4175
installLocations()4176 void TestBlackbox::installLocations()
4177 {
4178 QDir::setCurrent(testDataDir + "/install-locations");
4179 QFETCH(QString, binDir);
4180 QFETCH(QString, dllDir);
4181 QFETCH(QString, libDir);
4182 QFETCH(QString, pluginDir);
4183 QFETCH(QString, dsymDir);
4184 QbsRunParameters params("resolve");
4185 if (!binDir.isEmpty())
4186 params.arguments.push_back("products.theapp.installDir:" + binDir);
4187 if (!dllDir.isEmpty())
4188 params.arguments.push_back("products.thelib.installDir:" + dllDir);
4189 if (!libDir.isEmpty())
4190 params.arguments.push_back("products.thelib.importLibInstallDir:" + libDir);
4191 if (!pluginDir.isEmpty())
4192 params.arguments.push_back("products.theplugin.installDir:" + pluginDir);
4193 if (!dsymDir.isEmpty()) {
4194 params.arguments.push_back("products.theapp.debugInformationInstallDir:" + dsymDir);
4195 params.arguments.push_back("products.thelib.debugInformationInstallDir:" + dsymDir);
4196 params.arguments.push_back("products.theplugin.debugInformationInstallDir:" + dsymDir);
4197 }
4198 QCOMPARE(runQbs(params), 0);
4199 const bool isWindows = m_qbsStdout.contains("is windows");
4200 const bool isDarwin = m_qbsStdout.contains("is darwin");
4201 const bool isMac = m_qbsStdout.contains("is mac");
4202 const bool isUnix = m_qbsStdout.contains("is unix");
4203 const bool isMingw = m_qbsStdout.contains("is mingw");
4204 QVERIFY(isWindows || isDarwin || isUnix);
4205 QCOMPARE(runQbs(QbsRunParameters(QStringList("--clean-install-root"))), 0);
4206
4207 struct BinaryInfo
4208 {
4209 QString fileName;
4210 QString installDir;
4211 QString subDir;
4212
4213 QString absolutePath(const QString &prefix) const
4214 {
4215 return QDir::cleanPath(prefix + '/' + installDir + '/' + subDir + '/' + fileName);
4216 }
4217 };
4218
4219 const BinaryInfo dll = {
4220 isWindows ? "thelib.dll" : isDarwin ? "thelib" : "libthelib.so",
4221 dllDir.isEmpty()
4222 ? (isDarwin ? "/Library/Frameworks" : (isWindows ? "/bin" : "/lib"))
4223 : dllDir,
4224 isDarwin ? "thelib.framework" : ""
4225 };
4226 const BinaryInfo dllDsym = {
4227 isWindows
4228 ? (!isMingw ? "thelib.pdb" : "thelib.dll.debug")
4229 : isDarwin ? "thelib.framework.dSYM" : "libthelib.so.debug",
4230 dsymDir.isEmpty() ? dll.installDir : dsymDir,
4231 {}
4232 };
4233 const BinaryInfo plugin = {
4234 isWindows ? "theplugin.dll" : isDarwin ? "theplugin" : "libtheplugin.so",
4235 pluginDir.isEmpty() ? dll.installDir : pluginDir,
4236 isDarwin ? (isMac ? "theplugin.bundle/Contents/MacOS" : "theplugin.bundle") : ""
4237 };
4238 const BinaryInfo pluginDsym = {
4239 isWindows
4240 ? (!isMingw ? "theplugin.pdb" : "theplugin.dll.debug")
4241 : isDarwin ? "theplugin.bundle.dSYM" : "libtheplugin.so.debug",
4242 dsymDir.isEmpty() ? plugin.installDir : dsymDir,
4243 {}
4244 };
4245 const BinaryInfo app = {
4246 isWindows ? "theapp.exe" : "theapp",
4247 binDir.isEmpty() ? (isDarwin ? "/Applications" : "/bin") : binDir,
4248 isDarwin ? (isMac ? "theapp.app/Contents/MacOS" : "theapp.app") : ""
4249 };
4250 const BinaryInfo appDsym = {
4251 isWindows
4252 ? (!isMingw ? "theapp.pdb" : "theapp.exe.debug")
4253 : isDarwin ? "theapp.app.dSYM" : "theapp.debug",
4254 dsymDir.isEmpty() ? app.installDir : dsymDir,
4255 {}
4256 };
4257
4258 const QString installRoot = QDir::currentPath() + "/default/install-root";
4259 const QString installPrefix = isWindows ? QString() : "/usr/local";
4260 const QString fullInstallPrefix = installRoot + '/' + installPrefix + '/';
4261 const QString appFilePath = app.absolutePath(fullInstallPrefix);
4262 QVERIFY2(QFile::exists(appFilePath), qPrintable(appFilePath));
4263 const QString dllFilePath = dll.absolutePath(fullInstallPrefix);
4264 QVERIFY2(QFile::exists(dllFilePath), qPrintable(dllFilePath));
4265 if (isWindows) {
4266 const BinaryInfo lib = {
4267 "thelib.lib",
4268 libDir.isEmpty() ? "/lib" : libDir,
4269 ""
4270 };
4271 const QString libFilePath = lib.absolutePath(fullInstallPrefix);
4272 QVERIFY2(QFile::exists(libFilePath), qPrintable(libFilePath));
4273 }
4274 const QString pluginFilePath = plugin.absolutePath(fullInstallPrefix);
4275 QVERIFY2(QFile::exists(pluginFilePath), qPrintable(pluginFilePath));
4276
4277 const QString appDsymFilePath = appDsym.absolutePath(fullInstallPrefix);
4278 QVERIFY2(QFileInfo(appDsymFilePath).exists(), qPrintable(appDsymFilePath));
4279 const QString dllDsymFilePath = dllDsym.absolutePath(fullInstallPrefix);
4280 QVERIFY2(QFileInfo(dllDsymFilePath).exists(), qPrintable(dllDsymFilePath));
4281 const QString pluginDsymFilePath = pluginDsym.absolutePath(fullInstallPrefix);
4282 QVERIFY2(QFile::exists(pluginDsymFilePath), qPrintable(pluginDsymFilePath));
4283 }
4284
inputsFromDependencies()4285 void TestBlackbox::inputsFromDependencies()
4286 {
4287 QDir::setCurrent(testDataDir + "/inputs-from-dependencies");
4288 QCOMPARE(runQbs(), 0);
4289 const QList<QByteArray> output = m_qbsStdout.trimmed().split('\n');
4290 QVERIFY2(output.contains((QDir::currentPath() + "/file1.txt").toUtf8()),
4291 m_qbsStdout.constData());
4292 QVERIFY2(output.contains((QDir::currentPath() + "/file2.txt").toUtf8()),
4293 m_qbsStdout.constData());
4294 QVERIFY2(output.contains((QDir::currentPath() + "/file3.txt").toUtf8()),
4295 m_qbsStdout.constData());
4296 QVERIFY2(!output.contains((QDir::currentPath() + "/file4.txt").toUtf8()),
4297 m_qbsStdout.constData());
4298 }
4299
installPackage()4300 void TestBlackbox::installPackage()
4301 {
4302 if (HostOsInfo::hostOs() == HostOsInfo::HostOsWindows)
4303 QSKIP("Beware of the msys tar");
4304 QString binary = findArchiver("tar");
4305 if (binary.isEmpty())
4306 QSKIP("tar not found");
4307 MacosTarHealer tarHealer;
4308 QDir::setCurrent(testDataDir + "/installpackage");
4309 QCOMPARE(runQbs(), 0);
4310 const QString tarFilePath = relativeProductBuildDir("tar-package") + "/tar-package.tar.gz";
4311 QVERIFY2(regularFileExists(tarFilePath), qPrintable(tarFilePath));
4312 QProcess tarList;
4313 tarList.start(binary, QStringList() << "tf" << tarFilePath);
4314 QVERIFY2(tarList.waitForStarted(), qPrintable(tarList.errorString()));
4315 QVERIFY2(tarList.waitForFinished(), qPrintable(tarList.errorString()));
4316 const QList<QByteArray> outputLines = tarList.readAllStandardOutput().split('\n');
4317 QList<QByteArray> cleanOutputLines;
4318 for (const QByteArray &line : outputLines) {
4319 const QByteArray trimmedLine = line.trimmed();
4320 if (!trimmedLine.isEmpty())
4321 cleanOutputLines.push_back(trimmedLine);
4322 }
4323 QCOMPARE(cleanOutputLines.size(), 3);
4324 for (const QByteArray &line : qAsConst(cleanOutputLines)) {
4325 QVERIFY2(line.contains("public_tool") || line.contains("mylib") || line.contains("lib.h"),
4326 line.constData());
4327 }
4328 }
4329
installRootFromProjectFile()4330 void TestBlackbox::installRootFromProjectFile()
4331 {
4332 QDir::setCurrent(testDataDir + "/install-root-from-project-file");
4333 const QString installRoot = QDir::currentPath() + '/' + relativeBuildDir()
4334 + "/my-install-root/";
4335 QCOMPARE(runQbs(QbsRunParameters(QStringList("products.p.installRoot:" + installRoot))), 0);
4336 const QString installedFile = installRoot + "/install-prefix/install-dir/file.txt";
4337 QVERIFY2(QFile::exists(installedFile), qPrintable(installedFile));
4338 }
4339
installable()4340 void TestBlackbox::installable()
4341 {
4342 QDir::setCurrent(testDataDir + "/installable");
4343 QCOMPARE(runQbs(), 0);
4344 QFile installList(relativeProductBuildDir("install-list") + "/installed-files.txt");
4345 QVERIFY2(installList.open(QIODevice::ReadOnly), qPrintable(installList.errorString()));
4346 QCOMPARE(installList.readAll().count('\n'), 2);
4347 }
4348
installableAsAuxiliaryInput()4349 void TestBlackbox::installableAsAuxiliaryInput()
4350 {
4351 QDir::setCurrent(testDataDir + "/installable-as-auxiliary-input");
4352 QCOMPARE(runQbs({"resolve"}), 0);
4353 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
4354 QSKIP("Cannot run binaries in cross-compiled build");
4355 QCOMPARE(runQbs(QbsRunParameters("run")), 0);
4356 QVERIFY2(m_qbsStdout.contains("f-impl"), m_qbsStdout.constData());
4357 }
4358
installTree()4359 void TestBlackbox::installTree()
4360 {
4361 QDir::setCurrent(testDataDir + "/install-tree");
4362 QbsRunParameters params;
4363 params.command = "install";
4364 QCOMPARE(runQbs(params), 0);
4365 const QString installRoot = relativeBuildDir() + "/install-root/";
4366 QVERIFY(QFile::exists(installRoot + "content/foo.txt"));
4367 QVERIFY(QFile::exists(installRoot + "content/subdir1/bar.txt"));
4368 QVERIFY(QFile::exists(installRoot + "content/subdir2/baz.txt"));
4369 }
4370
invalidCommandProperty_data()4371 void TestBlackbox::invalidCommandProperty_data()
4372 {
4373 QTest::addColumn<QString>("errorType");
4374
4375 QTest::newRow("assigning QObject") << QString("qobject");
4376 QTest::newRow("assigning input artifact") << QString("input");
4377 QTest::newRow("assigning other artifact") << QString("artifact");
4378 }
4379
invalidCommandProperty()4380 void TestBlackbox::invalidCommandProperty()
4381 {
4382 QDir::setCurrent(testDataDir + "/invalid-command-property");
4383 QFETCH(QString, errorType);
4384 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.p.errorType:" + errorType))),
4385 0);
4386 QbsRunParameters params;
4387 params.expectFailure = true;
4388 QVERIFY(runQbs(params) != 0);
4389 QVERIFY2(m_qbsStderr.contains("unsuitable"), m_qbsStderr.constData());
4390 }
4391
invalidLibraryNames()4392 void TestBlackbox::invalidLibraryNames()
4393 {
4394 QDir::setCurrent(testDataDir + "/invalid-library-names");
4395 QFETCH(QString, index);
4396 QFETCH(bool, success);
4397 QFETCH(QStringList, diagnostics);
4398 QbsRunParameters params(QStringList("project.valueIndex:" + index));
4399 params.expectFailure = !success;
4400 QCOMPARE(runQbs(params) == 0, success);
4401 for (const QString &diag : qAsConst(diagnostics))
4402 QVERIFY2(m_qbsStderr.contains(diag.toLocal8Bit()), m_qbsStderr.constData());
4403 }
4404
invalidLibraryNames_data()4405 void TestBlackbox::invalidLibraryNames_data()
4406 {
4407 QTest::addColumn<QString>("index");
4408 QTest::addColumn<bool>("success");
4409 QTest::addColumn<QStringList>("diagnostics");
4410
4411 QTest::newRow("null") << "0" << false << QStringList("is null");
4412 QTest::newRow("undefined") << "1" << false << QStringList("is undefined");
4413 QTest::newRow("number") << "2" << false << QStringList("does not have string type");
4414 QTest::newRow("array") << "3" << false << QStringList("does not have string type");
4415 QTest::newRow("empty string") << "4" << true << (QStringList()
4416 << "WARNING: Removing empty string from value of property "
4417 "'cpp.dynamicLibraries' in product 'invalid-library-names'."
4418 << "WARNING: Removing empty string from value of property "
4419 "'cpp.staticLibraries' in product 'invalid-library-names'.");
4420 }
4421
invalidExtensionInstantiation()4422 void TestBlackbox::invalidExtensionInstantiation()
4423 {
4424 rmDirR(relativeBuildDir());
4425 QDir::setCurrent(testDataDir + "/invalid-extension-instantiation");
4426 QbsRunParameters params;
4427 params.expectFailure = true;
4428 params.arguments << (QString("products.theProduct.extension:") + QTest::currentDataTag());
4429 QVERIFY(runQbs(params) != 0);
4430 QVERIFY2(m_qbsStderr.contains("invalid-extension-instantiation.qbs:17")
4431 && m_qbsStderr.contains('\'' + QByteArray(QTest::currentDataTag())
4432 + "' cannot be instantiated"),
4433 m_qbsStderr.constData());
4434 }
4435
invalidExtensionInstantiation_data()4436 void TestBlackbox::invalidExtensionInstantiation_data()
4437 {
4438 QTest::addColumn<bool>("dummy");
4439
4440 QTest::newRow("Environment");
4441 QTest::newRow("File");
4442 QTest::newRow("FileInfo");
4443 QTest::newRow("Utilities");
4444 }
4445
invalidInstallDir()4446 void TestBlackbox::invalidInstallDir()
4447 {
4448 QDir::setCurrent(testDataDir + "/invalid-install-dir");
4449 QbsRunParameters params;
4450 params.expectFailure = true;
4451 QVERIFY(runQbs(params) != 0);
4452 QVERIFY2(m_qbsStderr.contains("outside of install root"), m_qbsStderr.constData());
4453 }
4454
cli()4455 void TestBlackbox::cli()
4456 {
4457 int status;
4458 findCli(&status);
4459 QCOMPARE(status, 0);
4460
4461 const SettingsPtr s = settings();
4462 Profile p("qbs_autotests-cli", s.get());
4463 const QStringList toolchain = profileToolchain(p);
4464 if (!p.exists() || !(toolchain.contains("dotnet") || toolchain.contains("mono")))
4465 QSKIP("No suitable Common Language Infrastructure test profile");
4466
4467 QDir::setCurrent(testDataDir + "/cli");
4468 QbsRunParameters params(QStringList() << "-f" << "dotnettest.qbs");
4469 params.profile = p.name();
4470
4471 status = runQbs(params);
4472 if (p.value("cli.toolchainInstallPath").toString().isEmpty()
4473 && status != 0 && m_qbsStderr.contains("toolchainInstallPath"))
4474 QSKIP("cli.toolchainInstallPath not set and automatic detection failed");
4475
4476 QCOMPARE(status, 0);
4477 rmDirR(relativeBuildDir());
4478
4479 QbsRunParameters params2(QStringList() << "-f" << "fshello.qbs");
4480 params2.profile = p.name();
4481 QCOMPARE(runQbs(params2), 0);
4482 rmDirR(relativeBuildDir());
4483 }
4484
combinedSources()4485 void TestBlackbox::combinedSources()
4486 {
4487 QDir::setCurrent(testDataDir + "/combined-sources");
4488 QbsRunParameters params(QStringList("modules.cpp.combineCxxSources:false"));
4489 QCOMPARE(runQbs(params), 0);
4490 QVERIFY(m_qbsStdout.contains("compiling main.cpp"));
4491 QVERIFY(m_qbsStdout.contains("compiling combinable.cpp"));
4492 QVERIFY(m_qbsStdout.contains("compiling uncombinable.cpp"));
4493 QVERIFY(!m_qbsStdout.contains("compiling amalgamated_theapp.cpp"));
4494 params.arguments = QStringList("modules.cpp.combineCxxSources:true");
4495 params.command = "resolve";
4496 QCOMPARE(runQbs(params), 0);
4497 WAIT_FOR_NEW_TIMESTAMP();
4498 touch("combinable.cpp");
4499 touch("main.cpp");
4500 touch("uncombinable.cpp");
4501 params.command = "build";
4502 QCOMPARE(runQbs(params), 0);
4503 QVERIFY(!m_qbsStdout.contains("compiling main.cpp"));
4504 QVERIFY(!m_qbsStdout.contains("compiling combinable.cpp"));
4505 QVERIFY(m_qbsStdout.contains("compiling uncombinable.cpp"));
4506 QVERIFY(m_qbsStdout.contains("compiling amalgamated_theapp.cpp"));
4507 }
4508
commandFile()4509 void TestBlackbox::commandFile()
4510 {
4511 QDir::setCurrent(testDataDir + "/command-file");
4512 QbsRunParameters params(QStringList() << "-p" << "theLib");
4513 QCOMPARE(runQbs(params), 0);
4514 params.arguments = QStringList() << "-p" << "theApp";
4515 QCOMPARE(runQbs(params), 0);
4516 }
4517
compilerDefinesByLanguage()4518 void TestBlackbox::compilerDefinesByLanguage()
4519 {
4520 QDir::setCurrent(testDataDir + "/compilerDefinesByLanguage");
4521 QbsRunParameters params(QStringList { "-f", "compilerDefinesByLanguage.qbs" });
4522 QCOMPARE(runQbs(params), 0);
4523 }
4524
jsExtensionsFile()4525 void TestBlackbox::jsExtensionsFile()
4526 {
4527 QDir::setCurrent(testDataDir + "/jsextensions-file");
4528 QFile fileToMove("tomove.txt");
4529 QVERIFY2(fileToMove.open(QIODevice::WriteOnly), qPrintable(fileToMove.errorString()));
4530 fileToMove.close();
4531 fileToMove.setPermissions(fileToMove.permissions() & ~(QFile::ReadUser | QFile::ReadOwner
4532 | QFile::ReadGroup | QFile::ReadOther));
4533 QbsRunParameters params(QStringList() << "-f" << "file.qbs");
4534 QCOMPARE(runQbs(params), 0);
4535 QVERIFY(!QFileInfo("original.txt").exists());
4536 QFile copy("copy.txt");
4537 QVERIFY(copy.exists());
4538 QVERIFY(copy.open(QIODevice::ReadOnly));
4539 const QList<QByteArray> lines = copy.readAll().trimmed().split('\n');
4540 QCOMPARE(lines.size(), 2);
4541 QCOMPARE(lines.at(0).trimmed().constData(), "false");
4542 QCOMPARE(lines.at(1).trimmed().constData(), "true");
4543 }
4544
jsExtensionsFileInfo()4545 void TestBlackbox::jsExtensionsFileInfo()
4546 {
4547 QDir::setCurrent(testDataDir + "/jsextensions-fileinfo");
4548 QbsRunParameters params(QStringList() << "-f" << "fileinfo.qbs");
4549 QCOMPARE(runQbs(params), 0);
4550 QFile output("output.txt");
4551 QVERIFY(output.exists());
4552 QVERIFY(output.open(QIODevice::ReadOnly));
4553 const QList<QByteArray> lines = output.readAll().trimmed().split('\n');
4554 QCOMPARE(lines.size(), 26);
4555 int i = 0;
4556 QCOMPARE(lines.at(i++).trimmed().constData(), "blubb");
4557 QCOMPARE(lines.at(i++).trimmed().constData(), qUtf8Printable(
4558 QFileInfo(QDir::currentPath()).canonicalFilePath()));
4559 QCOMPARE(lines.at(i++).trimmed().constData(), "/usr/bin");
4560 QCOMPARE(lines.at(i++).trimmed().constData(), "blubb.tar");
4561 QCOMPARE(lines.at(i++).trimmed().constData(), "blubb.tar.gz");
4562 QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp/blubb.tar.gz");
4563 QCOMPARE(lines.at(i++).trimmed().constData(), "c:/tmp/blubb.tar.gz");
4564 QCOMPARE(lines.at(i++).trimmed().constData(), "true");
4565 QCOMPARE(lines.at(i++).trimmed().constData(), HostOsInfo::isWindowsHost() ? "true" : "false");
4566 QCOMPARE(lines.at(i++).trimmed().constData(), "false");
4567 QCOMPARE(lines.at(i++).trimmed().constData(), "true");
4568 QCOMPARE(lines.at(i++).trimmed().constData(), "false");
4569 QCOMPARE(lines.at(i++).trimmed().constData(), "false");
4570 QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp/blubb.tar.gz");
4571 QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp/blubb.tar.gz");
4572 QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp");
4573 QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp");
4574 QCOMPARE(lines.at(i++).trimmed().constData(), "/");
4575 QCOMPARE(lines.at(i++).trimmed().constData(), HostOsInfo::isWindowsHost() ? "d:/" : "d:");
4576 QCOMPARE(lines.at(i++).trimmed().constData(), "d:");
4577 QCOMPARE(lines.at(i++).trimmed().constData(), "d:/");
4578 QCOMPARE(lines.at(i++).trimmed().constData(), "blubb.tar.gz");
4579 QCOMPARE(lines.at(i++).trimmed().constData(), "tmp/blubb.tar.gz");
4580 QCOMPARE(lines.at(i++).trimmed().constData(), "../blubb.tar.gz");
4581 QCOMPARE(lines.at(i++).trimmed().constData(), "\\tmp\\blubb.tar.gz");
4582 QCOMPARE(lines.at(i++).trimmed().constData(), "c:\\tmp\\blubb.tar.gz");
4583 }
4584
jsExtensionsProcess()4585 void TestBlackbox::jsExtensionsProcess()
4586 {
4587 QDir::setCurrent(testDataDir + "/jsextensions-process");
4588 QCOMPARE(runQbs({"resolve", {"-f", "process.qbs"}}), 0);
4589 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
4590 QSKIP("Cannot run binaries in cross-compiled build");
4591 QbsRunParameters params(QStringList() << "-f" << "process.qbs");
4592 QCOMPARE(runQbs(params), 0);
4593 QFile output("output.txt");
4594 QVERIFY(output.exists());
4595 QVERIFY(output.open(QIODevice::ReadOnly));
4596 const QList<QByteArray> lines = output.readAll().trimmed().split('\n');
4597 QCOMPARE(lines.size(), 9);
4598 QCOMPARE(lines.at(0).trimmed().constData(), "0");
4599 QVERIFY(lines.at(1).startsWith("qbs "));
4600 QCOMPARE(lines.at(2).trimmed().constData(), "true");
4601 QCOMPARE(lines.at(3).trimmed().constData(), "true");
4602 QCOMPARE(lines.at(4).trimmed().constData(), "0");
4603 QVERIFY(lines.at(5).startsWith("qbs "));
4604 QCOMPARE(lines.at(6).trimmed().constData(), "false");
4605 QCOMPARE(lines.at(7).trimmed().constData(), "should be");
4606 QCOMPARE(lines.at(8).trimmed().constData(), "123");
4607 }
4608
jsExtensionsPropertyList()4609 void TestBlackbox::jsExtensionsPropertyList()
4610 {
4611 if (!HostOsInfo::isMacosHost())
4612 QSKIP("temporarily only applies on macOS");
4613
4614 QDir::setCurrent(testDataDir + "/jsextensions-propertylist");
4615 QbsRunParameters params(QStringList() << "-nf" << "propertylist.qbs");
4616 QCOMPARE(runQbs(params), 0);
4617 QFile file1("test.json");
4618 QVERIFY(file1.exists());
4619 QVERIFY(file1.open(QIODevice::ReadOnly));
4620 QFile file2("test.xml");
4621 QVERIFY(file2.exists());
4622 QVERIFY(file2.open(QIODevice::ReadOnly));
4623 QFile file3("test2.json");
4624 QVERIFY(file3.exists());
4625 QVERIFY(file3.open(QIODevice::ReadOnly));
4626 QByteArray file1Contents = file1.readAll();
4627 QCOMPARE(file3.readAll(), file1Contents);
4628 //QCOMPARE(file1Contents, file2.readAll()); // keys don't have guaranteed order
4629 QJsonParseError err1, err2;
4630 QCOMPARE(QJsonDocument::fromJson(file1Contents, &err1),
4631 QJsonDocument::fromJson(file2.readAll(), &err2));
4632 QVERIFY(err1.error == QJsonParseError::NoError && err2.error == QJsonParseError::NoError);
4633 QFile file4("test.openstep.plist");
4634 QVERIFY(file4.exists());
4635 QFile file5("test3.json");
4636 QVERIFY(file5.exists());
4637 QVERIFY(file5.open(QIODevice::ReadOnly));
4638 QVERIFY(file1Contents != file5.readAll());
4639 }
4640
jsExtensionsTemporaryDir()4641 void TestBlackbox::jsExtensionsTemporaryDir()
4642 {
4643 QDir::setCurrent(testDataDir + "/jsextensions-temporarydir");
4644 QbsRunParameters params;
4645 QCOMPARE(runQbs(params), 0);
4646 }
4647
jsExtensionsTextFile()4648 void TestBlackbox::jsExtensionsTextFile()
4649 {
4650 QDir::setCurrent(testDataDir + "/jsextensions-textfile");
4651 QbsRunParameters params(QStringList() << "-f" << "textfile.qbs");
4652 QCOMPARE(runQbs(params), 0);
4653 QFile file1("file1.txt");
4654 QVERIFY(file1.exists());
4655 QVERIFY(file1.open(QIODevice::ReadOnly));
4656 QCOMPARE(file1.size(), qint64(0));
4657 QFile file2("file2.txt");
4658 QVERIFY(file2.exists());
4659 QVERIFY(file2.open(QIODevice::ReadOnly));
4660 const QList<QByteArray> lines = file2.readAll().trimmed().split('\n');
4661 QCOMPARE(lines.size(), 6);
4662 QCOMPARE(lines.at(0).trimmed().constData(), "false");
4663 QCOMPARE(lines.at(1).trimmed().constData(), "First line.");
4664 QCOMPARE(lines.at(2).trimmed().constData(), "Second line.");
4665 QCOMPARE(lines.at(3).trimmed().constData(), "Third line.");
4666 QCOMPARE(lines.at(4).trimmed().constData(), qPrintable(QDir::currentPath() + "/file1.txt"));
4667 QCOMPARE(lines.at(5).trimmed().constData(), "true");
4668 }
4669
jsExtensionsBinaryFile()4670 void TestBlackbox::jsExtensionsBinaryFile()
4671 {
4672 QDir::setCurrent(testDataDir + "/jsextensions-binaryfile");
4673 QbsRunParameters params(QStringList() << "-f" << "binaryfile.qbs");
4674 QCOMPARE(runQbs(params), 0);
4675 QFile source("source.dat");
4676 QVERIFY(source.exists());
4677 QVERIFY(source.open(QIODevice::ReadOnly));
4678 QCOMPARE(source.size(), qint64(0));
4679 QFile destination("destination.dat");
4680 QVERIFY(destination.exists());
4681 QVERIFY(destination.open(QIODevice::ReadOnly));
4682 const QByteArray data = destination.readAll();
4683 QCOMPARE(data.size(), 8);
4684 QCOMPARE(data.at(0), char(0x00));
4685 QCOMPARE(data.at(1), char(0x01));
4686 QCOMPARE(data.at(2), char(0x02));
4687 QCOMPARE(data.at(3), char(0x03));
4688 QCOMPARE(data.at(4), char(0x04));
4689 QCOMPARE(data.at(5), char(0x05));
4690 QCOMPARE(data.at(6), char(0x06));
4691 QCOMPARE(data.at(7), char(0xFF));
4692 }
4693
lastModuleCandidateBroken()4694 void TestBlackbox::lastModuleCandidateBroken()
4695 {
4696 QDir::setCurrent(testDataDir + "/last-module-candidate-broken");
4697 QbsRunParameters params;
4698 params.expectFailure = true;
4699 QVERIFY(runQbs(params) != 0);
4700 QVERIFY2(m_qbsStderr.contains("Module Foo could not be loaded"), m_qbsStderr);
4701 }
4702
ld()4703 void TestBlackbox::ld()
4704 {
4705 QDir::setCurrent(testDataDir + "/ld");
4706 QCOMPARE(runQbs(), 0);
4707 }
4708
symbolLinkMode()4709 void TestBlackbox::symbolLinkMode()
4710 {
4711 if (!HostOsInfo::isAnyUnixHost())
4712 QSKIP("only applies on Unix");
4713
4714 QDir::setCurrent(testDataDir + "/symbolLinkMode");
4715
4716 QCOMPARE(runQbs({"resolve"}), 0);
4717 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
4718 QSKIP("Cannot run binaries in cross-compiled build");
4719 QbsRunParameters params;
4720 params.command = "run";
4721 const QStringList commonArgs{"-p", "driver", "--setup-run-env-config",
4722 "ignore-lib-dependencies", "qbs.installPrefix:''"};
4723
4724 rmDirR(relativeBuildDir());
4725 params.arguments = QStringList() << commonArgs << "project.shouldInstallLibrary:true";
4726 QCOMPARE(runQbs(params), 0);
4727 QVERIFY2(m_qbsStdout.contains("somefunction existed and it returned 42"),
4728 m_qbsStdout.constData());
4729
4730 rmDirR(relativeBuildDir());
4731 params.arguments = QStringList() << commonArgs << "project.shouldInstallLibrary:false";
4732 QCOMPARE(runQbs(params), 0);
4733 QVERIFY2(m_qbsStdout.contains("somefunction did not exist"), m_qbsStdout.constData());
4734
4735 rmDirR(relativeBuildDir());
4736 params.arguments = QStringList() << commonArgs << "project.lazy:false";
4737 QCOMPARE(runQbs(params), 0);
4738 QVERIFY2(m_qbsStdout.contains("Lib was loaded!\nmeow\n"), m_qbsStdout.constData());
4739
4740 if (HostOsInfo::isMacosHost()) {
4741 rmDirR(relativeBuildDir());
4742 params.arguments = QStringList() << commonArgs << "project.lazy:true";
4743 QCOMPARE(runQbs(params), 0);
4744 QVERIFY2(m_qbsStdout.contains("meow\n") && m_qbsStdout.contains("Lib was loaded!\n"),
4745 m_qbsStdout.constData());
4746 }
4747 }
4748
linkerMode()4749 void TestBlackbox::linkerMode()
4750 {
4751 if (!HostOsInfo::isAnyUnixHost())
4752 QSKIP("only applies on Unix");
4753
4754 QDir::setCurrent(testDataDir + "/linkerMode");
4755 QCOMPARE(runQbs(QbsRunParameters(QStringList("qbs.installPrefix:''"))), 0);
4756
4757 auto testCondition = [&](const QString &lang,
4758 const std::function<bool(const QByteArray &)> &condition) {
4759 if ((lang == "Objective-C" || lang == "Objective-C++")
4760 && HostOsInfo::hostOs() != HostOsInfo::HostOsMacos)
4761 return;
4762 const QString binary = defaultInstallRoot + "/LinkedProduct-" + lang;
4763 QProcess deptool;
4764 if (HostOsInfo::hostOs() == HostOsInfo::HostOsMacos)
4765 deptool.start("otool", QStringList() << "-L" << binary);
4766 else
4767 deptool.start("readelf", QStringList() << "-a" << binary);
4768 QVERIFY(deptool.waitForStarted());
4769 QVERIFY(deptool.waitForFinished());
4770 QByteArray deptoolOutput = deptool.readAllStandardOutput();
4771 if (HostOsInfo::hostOs() != HostOsInfo::HostOsMacos) {
4772 QList<QByteArray> lines = deptoolOutput.split('\n');
4773 int sz = lines.size();
4774 for (int i = 0; i < sz; ++i) {
4775 if (!lines.at(i).contains("NEEDED")) {
4776 lines.removeAt(i--);
4777 sz--;
4778 }
4779 }
4780
4781 deptoolOutput = lines.join('\n');
4782 }
4783 QCOMPARE(deptool.exitCode(), 0);
4784 QVERIFY2(condition(deptoolOutput), deptoolOutput.constData());
4785 };
4786
4787 const QStringList nocpplangs = QStringList() << "Assembly" << "C" << "Objective-C";
4788 for (const QString &lang : nocpplangs)
4789 testCondition(lang, [](const QByteArray &lddOutput) { return !lddOutput.contains("c++"); });
4790
4791 const QStringList cpplangs = QStringList() << "C++" << "Objective-C++";
4792 for (const QString &lang : cpplangs)
4793 testCondition(lang, [](const QByteArray &lddOutput) { return lddOutput.contains("c++"); });
4794
4795 const QStringList objclangs = QStringList() << "Objective-C" << "Objective-C++";
4796 for (const QString &lang : objclangs)
4797 testCondition(lang, [](const QByteArray &lddOutput) { return lddOutput.contains("objc"); });
4798 }
4799
linkerVariant_data()4800 void TestBlackbox::linkerVariant_data()
4801 {
4802 QTest::addColumn<QString>("theType");
4803 QTest::newRow("default") << QString();
4804 QTest::newRow("bfd") << QString("bfd");
4805 QTest::newRow("gold") << QString("gold");
4806 }
4807
linkerVariant()4808 void TestBlackbox::linkerVariant()
4809 {
4810 QDir::setCurrent(testDataDir + "/linker-variant");
4811 QFETCH(QString, theType);
4812 QStringList resolveArgs("--force-probe-execution");
4813 if (!theType.isEmpty())
4814 resolveArgs << ("products.p.linkerVariant:" + theType);
4815 QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0);
4816 const bool isGcc = m_qbsStdout.contains("is GCC: true");
4817 const bool isNotGcc = m_qbsStdout.contains("is GCC: false");
4818 QVERIFY2(isGcc != isNotGcc, m_qbsStdout.constData());
4819 QbsRunParameters buildParams("build", QStringList{"--command-echo-mode", "command-line"});
4820 buildParams.expectFailure = true;
4821 runQbs(buildParams);
4822 if (isGcc && !theType.isEmpty())
4823 QCOMPARE(m_qbsStdout.count("-fuse-ld=" + theType.toLocal8Bit()), 1);
4824 else
4825 QVERIFY2(!m_qbsStdout.contains("-fuse-ld"), m_qbsStdout.constData());
4826 }
4827
lexyacc()4828 void TestBlackbox::lexyacc()
4829 {
4830 if (!lexYaccExist())
4831 QSKIP("lex or yacc not present");
4832 QDir::setCurrent(testDataDir + "/lexyacc/one-grammar");
4833 QCOMPARE(runQbs({"resolve"}), 0);
4834 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
4835 QSKIP("Cannot run binaries in cross-compiled build");
4836 QCOMPARE(runQbs(), 0);
4837 const QString parserBinary = relativeExecutableFilePath("one-grammar");
4838 QProcess p;
4839 const QByteArray magicString = "add to PATH: ";
4840 const int magicStringIndex = m_qbsStdout.indexOf(magicString);
4841 if (magicStringIndex != -1) {
4842 const int newLineIndex = m_qbsStdout.indexOf('\n', magicStringIndex);
4843 QVERIFY(newLineIndex != -1);
4844 const int dirIndex = magicStringIndex + magicString.length();
4845 const QString dir = QString::fromLocal8Bit(m_qbsStdout.mid(dirIndex,
4846 newLineIndex - dirIndex));
4847 QProcessEnvironment env;
4848 env.insert("PATH", dir);
4849 p.setProcessEnvironment(env);
4850 }
4851 p.start(parserBinary, QStringList());
4852 QVERIFY2(p.waitForStarted(), qPrintable(p.errorString()));
4853 p.write("a && b || c && !d");
4854 p.closeWriteChannel();
4855 QVERIFY2(p.waitForFinished(), qPrintable(p.errorString()));
4856 QVERIFY2(p.exitCode() == 0, p.readAllStandardError().constData());
4857 const QByteArray parserOutput = p.readAllStandardOutput();
4858 QVERIFY2(parserOutput.contains("OR AND a b AND c NOT d"), parserOutput.constData());
4859
4860 QDir::setCurrent(testDataDir + "/lexyacc/two-grammars");
4861 QbsRunParameters params;
4862 params.expectFailure = true;
4863 QVERIFY(runQbs(params) != 0);
4864
4865 params.expectFailure = false;
4866 params.command = "resolve";
4867 params.arguments << (QStringList() << "modules.lex_yacc.uniqueSymbolPrefix:true");
4868 QCOMPARE(runQbs(params), 0);
4869 QCOMPARE(runQbs(), 0);
4870 QVERIFY2(!m_qbsStderr.contains("whatever"), m_qbsStderr.constData());
4871 params.arguments << "modules.lex_yacc.enableCompilerWarnings:true";
4872 QCOMPARE(runQbs(params), 0);
4873 QCOMPARE(runQbs(), 0);
4874 const QByteArray outputToCheck = m_qbsStdout + m_qbsStderr;
4875 QVERIFY2(outputToCheck.contains("whatever"), outputToCheck.constData());
4876 }
4877
lexyaccOutputs()4878 void TestBlackbox::lexyaccOutputs()
4879 {
4880 if (!lexYaccExist())
4881 QSKIP("lex or yacc not present");
4882
4883 QFETCH(QString, lexOutputFilePath);
4884 QFETCH(QString, yaccOutputFilePath);
4885 QbsRunParameters params;
4886 if (!lexOutputFilePath.isEmpty())
4887 params.arguments << "modules.lex_yacc.lexOutputFilePath:" + lexOutputFilePath;
4888 if (!yaccOutputFilePath.isEmpty())
4889 params.arguments << "modules.lex_yacc.yaccOutputFilePath:" + yaccOutputFilePath;
4890
4891 #define VERIFY_COMPILATION(file) \
4892 if (!file.isEmpty()) { \
4893 QByteArray expected = "compiling " + file.toUtf8(); \
4894 if (!m_qbsStdout.contains(expected)) { \
4895 qDebug() << "Expected output:" << expected; \
4896 qDebug() << "Actual output:" << m_qbsStdout; \
4897 QFAIL("Expected stdout content missing."); \
4898 } \
4899 }
4900
4901 const auto version = bisonVersion();
4902 if (version >= qbs::Version(2, 6)) {
4903 // prefix only supported starting from bison 2.6
4904 QVERIFY(QDir::setCurrent(testDataDir + "/lexyacc/lex_prefix"));
4905 rmDirR(relativeBuildDir());
4906 QCOMPARE(runQbs(params), 0);
4907 VERIFY_COMPILATION(yaccOutputFilePath);
4908 }
4909
4910 QVERIFY(QDir::setCurrent(testDataDir + "/lexyacc/lex_outfile"));
4911 rmDirR(relativeBuildDir());
4912 QCOMPARE(runQbs(params), 0);
4913 VERIFY_COMPILATION(yaccOutputFilePath);
4914
4915 if (version >= qbs::Version(2, 4)) {
4916 // output syntax was changed in bison 2.4
4917 QVERIFY(QDir::setCurrent(testDataDir + "/lexyacc/yacc_output"));
4918 rmDirR(relativeBuildDir());
4919 QCOMPARE(runQbs(params), 0);
4920 VERIFY_COMPILATION(lexOutputFilePath);
4921 }
4922
4923 #undef VERIFY_COMPILATION
4924 }
4925
lexyaccOutputs_data()4926 void TestBlackbox::lexyaccOutputs_data()
4927 {
4928 QTest::addColumn<QString>("lexOutputFilePath");
4929 QTest::addColumn<QString>("yaccOutputFilePath");
4930 QTest::newRow("none") << QString() << QString();
4931 QTest::newRow("lexOutputFilePath")
4932 << QString{"lex_luthor.cpp"} << QString();
4933 QTest::newRow("yaccOutputFilePath")
4934 << QString() << QString{"shaven_yak.cpp"};
4935 }
4936
linkerLibraryDuplicates()4937 void TestBlackbox::linkerLibraryDuplicates()
4938 {
4939 const SettingsPtr s = settings();
4940 Profile buildProfile(profileName(), s.get());
4941 QStringList toolchain = profileToolchain(buildProfile);
4942 if (!toolchain.contains("gcc"))
4943 QSKIP("linkerLibraryDuplicates test only applies to GCC toolchain");
4944
4945 QDir::setCurrent(testDataDir + "/linker-library-duplicates");
4946 rmDirR(relativeBuildDir());
4947
4948 QFETCH(QString, removeDuplicateLibraries);
4949 QStringList runParams;
4950 if (!removeDuplicateLibraries.isEmpty()) {
4951 runParams.append(removeDuplicateLibraries);
4952 }
4953
4954 QCOMPARE(runQbs(QbsRunParameters("resolve", runParams)), 0);
4955 QCOMPARE(runQbs(QStringList { "--command-echo-mode", "command-line" }), 0);
4956 const QByteArrayList output = m_qbsStdout.split('\n');
4957 QByteArray linkLine;
4958 for (const QByteArray &line : output) {
4959 if (line.contains("main.cpp.o"))
4960 linkLine = line;
4961 }
4962 QVERIFY(!linkLine.isEmpty());
4963
4964 /* Now check the the libraries appear just once. In order to avoid dealing
4965 * with the different file extensions used in different platforms, we check
4966 * only for the library base name. But we must also take into account that
4967 * the build directories of each library will contain the library base name,
4968 * so we now exclude them. */
4969 QByteArrayList elementsWithoutPath;
4970 for (const QByteArray &element: linkLine.split(' ')) {
4971 if (element.indexOf('/') < 0)
4972 elementsWithoutPath.append(element);
4973 }
4974 QByteArray pathLessLinkLine = elementsWithoutPath.join(' ');
4975
4976 typedef QMap<QByteArray,int> ObjectCount;
4977 QFETCH(ObjectCount, expectedObjectCount);
4978 for (auto i = expectedObjectCount.begin();
4979 i != expectedObjectCount.end();
4980 i++) {
4981 QCOMPARE(pathLessLinkLine.count(i.key()), i.value());
4982 }
4983 }
4984
linkerLibraryDuplicates_data()4985 void TestBlackbox::linkerLibraryDuplicates_data()
4986 {
4987 typedef QMap<QByteArray,int> ObjectCount;
4988
4989 QTest::addColumn<QString>("removeDuplicateLibraries");
4990 QTest::addColumn<ObjectCount>("expectedObjectCount");
4991
4992 QTest::newRow("default") <<
4993 QString() <<
4994 ObjectCount {
4995 { "lib1", 1 },
4996 { "lib2", 1 },
4997 { "lib3", 1 },
4998 };
4999
5000 QTest::newRow("enabled") <<
5001 "modules.cpp.removeDuplicateLibraries:true" <<
5002 ObjectCount {
5003 { "lib1", 1 },
5004 { "lib2", 1 },
5005 { "lib3", 1 },
5006 };
5007
5008 QTest::newRow("disabled") <<
5009 "modules.cpp.removeDuplicateLibraries:false" <<
5010 ObjectCount {
5011 { "lib1", 3 },
5012 { "lib2", 2 },
5013 { "lib3", 1 },
5014 };
5015 }
5016
linkerScripts()5017 void TestBlackbox::linkerScripts()
5018 {
5019 const SettingsPtr s = settings();
5020 Profile buildProfile(profileName(), s.get());
5021 QStringList toolchain = profileToolchain(buildProfile);
5022 if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux)
5023 QSKIP("linker script test only applies to Linux ");
5024
5025 QbsRunParameters runParams(QStringList()
5026 // << "--log-level" << "debug"
5027 << ("qbs.installRoot:" + QDir::currentPath()));
5028 const QString sourceDir = QDir::cleanPath(testDataDir + "/linkerscripts");
5029 runParams.buildDirectory = sourceDir + "/build";
5030 runParams.workingDir = sourceDir;
5031
5032 QCOMPARE(runQbs(runParams), 0);
5033 const QString output = QString::fromLocal8Bit(m_qbsStderr);
5034 const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"),
5035 QRegularExpression::DotMatchesEverythingOption);
5036 const QRegularExpressionMatch match = pattern.match(output);
5037 QVERIFY2(match.hasMatch(), qPrintable(output));
5038 QCOMPARE(pattern.captureCount(), 1);
5039 const QString nmPath = match.captured(1);
5040 if (!QFile::exists(nmPath))
5041 QSKIP("Cannot check for symbol presence: No nm found.");
5042
5043 const auto verifySymbols = [nmPath](const QByteArrayList& symbols) -> bool {
5044 QProcess nm;
5045 nm.start(nmPath, QStringList(QDir::currentPath() + "/liblinkerscripts.so"));
5046 if (!nm.waitForStarted()) {
5047 qDebug() << "Wait for process started failed.";
5048 return false;
5049 }
5050 if (!nm.waitForFinished()) {
5051 qDebug() << "Wait for process finished failed.";
5052 return false;
5053 }
5054 if (nm.exitCode() != 0) {
5055 qDebug() << "nm returned exit code " << nm.exitCode();
5056 return false;
5057 }
5058 const QByteArray nmOutput = nm.readAllStandardOutput();
5059 for (const QByteArray& symbol : symbols) {
5060 if (!nmOutput.contains(symbol)) {
5061 qDebug() << "Expected symbol" << symbol
5062 << "not found in" << nmOutput.constData();
5063 return false;
5064 }
5065 }
5066 return true;
5067 };
5068
5069 QVERIFY(verifySymbols({"TEST_SYMBOL1",
5070 "TEST_SYMBOL2",
5071 "TEST_SYMBOL_FROM_INCLUDE",
5072 "TEST_SYMBOL_FROM_DIRECTORY",
5073 "TEST_SYMBOL_FROM_RECURSIVE"}));
5074 WAIT_FOR_NEW_TIMESTAMP();
5075 REPLACE_IN_FILE(sourceDir + "/linkerscript_to_include",
5076 "TEST_SYMBOL_FROM_INCLUDE = 1;",
5077 "TEST_SYMBOL_FROM_INCLUDE_MODIFIED = 1;\n");
5078 QCOMPARE(runQbs(runParams), 0);
5079 QVERIFY2(m_qbsStdout.contains("linking liblinkerscripts.so"),
5080 "No linking after modifying included file");
5081 QVERIFY(verifySymbols({"TEST_SYMBOL1",
5082 "TEST_SYMBOL2",
5083 "TEST_SYMBOL_FROM_INCLUDE_MODIFIED",
5084 "TEST_SYMBOL_FROM_DIRECTORY",
5085 "TEST_SYMBOL_FROM_RECURSIVE"}));
5086 WAIT_FOR_NEW_TIMESTAMP();
5087 REPLACE_IN_FILE(sourceDir + "/scripts/linkerscript_in_directory",
5088 "TEST_SYMBOL_FROM_DIRECTORY = 1;\n",
5089 "TEST_SYMBOL_FROM_DIRECTORY_MODIFIED = 1;\n");
5090 QCOMPARE(runQbs(runParams), 0);
5091 QVERIFY2(m_qbsStdout.contains("linking liblinkerscripts.so"),
5092 "No linking after modifying file in directory");
5093 QVERIFY(verifySymbols({"TEST_SYMBOL1",
5094 "TEST_SYMBOL2",
5095 "TEST_SYMBOL_FROM_INCLUDE_MODIFIED",
5096 "TEST_SYMBOL_FROM_DIRECTORY_MODIFIED",
5097 "TEST_SYMBOL_FROM_RECURSIVE"}));
5098 WAIT_FOR_NEW_TIMESTAMP();
5099 REPLACE_IN_FILE(sourceDir + "/linkerscript_recursive",
5100 "TEST_SYMBOL_FROM_RECURSIVE = 1;\n",
5101 "TEST_SYMBOL_FROM_RECURSIVE_MODIFIED = 1;\n");
5102 QCOMPARE(runQbs(runParams), 0);
5103 QVERIFY2(m_qbsStdout.contains("linking liblinkerscripts.so"),
5104 "No linking after modifying recursive file");
5105 QVERIFY(verifySymbols({"TEST_SYMBOL1",
5106 "TEST_SYMBOL2",
5107 "TEST_SYMBOL_FROM_INCLUDE_MODIFIED",
5108 "TEST_SYMBOL_FROM_DIRECTORY_MODIFIED",
5109 "TEST_SYMBOL_FROM_RECURSIVE_MODIFIED"}));
5110 }
5111
linkerModuleDefinition()5112 void TestBlackbox::linkerModuleDefinition()
5113 {
5114 QDir::setCurrent(testDataDir + "/linker-module-definition");
5115 QCOMPARE(runQbs({"build"}), 0);
5116 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
5117 QSKIP("Cannot run binaries in cross-compiled build");
5118 QCOMPARE(runQbs({"run"}), 0);
5119 const auto verifyOutput = [this](const QByteArrayList &symbols) {
5120 for (const QByteArray &symbol : symbols) {
5121 if (!m_qbsStdout.contains(symbol)) {
5122 qDebug() << "Expected symbol" << symbol
5123 << "not found in" << m_qbsStdout;
5124 return false;
5125 }
5126 }
5127 return true;
5128 };
5129 QVERIFY(verifyOutput({"foo", "bar"}));
5130 }
5131
listProducts()5132 void TestBlackbox::listProducts()
5133 {
5134 QDir::setCurrent(testDataDir + "/list-products");
5135 QCOMPARE(runQbs(QbsRunParameters("list-products")), 0);
5136 m_qbsStdout.replace("\r\n", "\n");
5137 QVERIFY2(m_qbsStdout.contains(
5138 "a\n"
5139 "b {\"architecture\":\"mips\",\"buildVariant\":\"debug\"}\n"
5140 "b {\"architecture\":\"mips\",\"buildVariant\":\"release\"}\n"
5141 "b {\"architecture\":\"vax\",\"buildVariant\":\"debug\"}\n"
5142 "b {\"architecture\":\"vax\",\"buildVariant\":\"release\"}\n"
5143 "c\n"), m_qbsStdout.constData());
5144 }
5145
listPropertiesWithOuter()5146 void TestBlackbox::listPropertiesWithOuter()
5147 {
5148 QDir::setCurrent(testDataDir + "/list-properties-with-outer");
5149 QCOMPARE(runQbs(), 0);
5150 QVERIFY2(m_qbsStdout.contains("listProp: [\"product\",\"higher\",\"group\"]"),
5151 m_qbsStdout.constData());
5152 }
5153
listPropertyOrder()5154 void TestBlackbox::listPropertyOrder()
5155 {
5156 QDir::setCurrent(testDataDir + "/list-property-order");
5157 const QbsRunParameters params(QStringList() << "-q");
5158 QCOMPARE(runQbs(params), 0);
5159 const QByteArray firstOutput = m_qbsStderr;
5160 QVERIFY(firstOutput.contains("listProp = [\"product\",\"higher3\",\"higher2\",\"higher1\",\"lower\"]"));
5161 for (int i = 0; i < 25; ++i) {
5162 rmDirR(relativeBuildDir());
5163 QCOMPARE(runQbs(params), 0);
5164 if (m_qbsStderr != firstOutput)
5165 break;
5166 }
5167 QCOMPARE(m_qbsStderr.constData(), firstOutput.constData());
5168 }
5169
require()5170 void TestBlackbox::require()
5171 {
5172 QDir::setCurrent(testDataDir + "/require");
5173 QCOMPARE(runQbs(), 0);
5174 }
5175
requireDeprecated()5176 void TestBlackbox::requireDeprecated()
5177 {
5178 QDir::setCurrent(testDataDir + "/require-deprecated");
5179 QCOMPARE(runQbs(), 0);
5180 QVERIFY2(m_qbsStderr.contains("loadExtension() function is deprecated"),
5181 m_qbsStderr.constData());
5182 QVERIFY2(m_qbsStderr.contains("loadFile() function is deprecated"),
5183 m_qbsStderr.constData());
5184 }
5185
rescueTransformerData()5186 void TestBlackbox::rescueTransformerData()
5187 {
5188 QDir::setCurrent(testDataDir + "/rescue-transformer-data");
5189 QCOMPARE(runQbs(), 0);
5190 QVERIFY2(m_qbsStdout.contains("compiling main.cpp") && m_qbsStdout.contains("m.p: undefined"),
5191 m_qbsStdout.constData());
5192 WAIT_FOR_NEW_TIMESTAMP();
5193 touch("main.cpp");
5194 QCOMPARE(runQbs(), 0);
5195 QVERIFY2(m_qbsStdout.contains("compiling main.cpp") && !m_qbsStdout.contains("m.p: "),
5196 m_qbsStdout.constData());
5197 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.p:true"))), 0);
5198 QCOMPARE(runQbs(), 0);
5199 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp") && m_qbsStdout.contains("m.p: true"),
5200 m_qbsStdout.constData());
5201 }
5202
multipleChanges()5203 void TestBlackbox::multipleChanges()
5204 {
5205 QDir::setCurrent(testDataDir + "/multiple-changes");
5206 QCOMPARE(runQbs(), 0);
5207 QFile newFile("test.blubb");
5208 QVERIFY(newFile.open(QIODevice::WriteOnly));
5209 newFile.close();
5210 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList() << "project.prop:true")), 0);
5211 QCOMPARE(runQbs(), 0);
5212 QVERIFY(m_qbsStdout.contains("prop: true"));
5213 }
5214
multipleConfigurations()5215 void TestBlackbox::multipleConfigurations()
5216 {
5217 QDir::setCurrent(testDataDir + "/multiple-configurations");
5218 QbsRunParameters params(QStringList{"config:x", "config:y", "config:z"});
5219 params.profile.clear();
5220 struct DefaultProfileSwitcher
5221 {
5222 DefaultProfileSwitcher()
5223 {
5224 const SettingsPtr s = settings();
5225 oldDefaultProfile = s->defaultProfile();
5226 s->setValue("defaultProfile", profileName());
5227 s->sync();
5228 }
5229 ~DefaultProfileSwitcher()
5230 {
5231 const SettingsPtr s = settings();
5232 s->setValue("defaultProfile", oldDefaultProfile);
5233 s->sync();
5234 }
5235 QVariant oldDefaultProfile;
5236 };
5237 DefaultProfileSwitcher dps;
5238 QCOMPARE(runQbs(params), 0);
5239 QCOMPARE(m_qbsStdout.count("compiling lib.cpp"), 3);
5240 QCOMPARE(m_qbsStdout.count("compiling file.cpp"), 3);
5241 QCOMPARE(m_qbsStdout.count("compiling main.cpp"), 3);
5242 }
5243
multiplexedTool()5244 void TestBlackbox::multiplexedTool()
5245 {
5246 QDir::setCurrent(testDataDir + "/multiplexed-tool");
5247 QCOMPARE(runQbs(QStringLiteral("resolve")), 0);
5248 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
5249 QSKIP("Cannot run binaries in cross-compiled build");
5250 QCOMPARE(runQbs(), 0);
5251 QCOMPARE(m_qbsStdout.count("creating tool.out"), 4);
5252 }
5253
nestedGroups()5254 void TestBlackbox::nestedGroups()
5255 {
5256 QDir::setCurrent(testDataDir + "/nested-groups");
5257 QCOMPARE(runQbs(), 0);
5258 QVERIFY(regularFileExists(relativeExecutableFilePath("nested-groups")));
5259 }
5260
nestedProperties()5261 void TestBlackbox::nestedProperties()
5262 {
5263 QDir::setCurrent(testDataDir + "/nested-properties");
5264 QCOMPARE(runQbs(), 0);
5265 QVERIFY2(m_qbsStdout.contains("value in higherlevel"), m_qbsStdout.constData());
5266 }
5267
newOutputArtifact()5268 void TestBlackbox::newOutputArtifact()
5269 {
5270 QDir::setCurrent(testDataDir + "/new-output-artifact");
5271 QCOMPARE(runQbs(), 0);
5272 QVERIFY(regularFileExists(relativeBuildDir() + "/install-root/output_98.out"));
5273 const QString the100thArtifact = relativeBuildDir() + "/install-root/output_99.out";
5274 QVERIFY(!regularFileExists(the100thArtifact));
5275 QbsRunParameters params("resolve", QStringList() << "products.theProduct.artifactCount:100");
5276 QCOMPARE(runQbs(params), 0);
5277 QCOMPARE(runQbs(), 0);
5278 QVERIFY(regularFileExists(the100thArtifact));
5279 }
5280
noExportedSymbols_data()5281 void TestBlackbox::noExportedSymbols_data()
5282 {
5283 QTest::addColumn<bool>("link");
5284 QTest::addColumn<bool>("dryRun");
5285 QTest::newRow("link") << true << false;
5286 QTest::newRow("link (dry run)") << true << true;
5287 QTest::newRow("do not link") << false << false;
5288 }
5289
noExportedSymbols()5290 void TestBlackbox::noExportedSymbols()
5291 {
5292 QDir::setCurrent(testDataDir + "/no-exported-symbols");
5293 QFETCH(bool, link);
5294 QFETCH(bool, dryRun);
5295 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"--force-probe-execution",
5296 QString("products.the_app.link:") + (link ? "true" : "false")})), 0);
5297 const bool isMsvc = m_qbsStdout.contains("compiler is MSVC");
5298 const bool isNotMsvc = m_qbsStdout.contains("compiler is not MSVC");
5299 QVERIFY2(isMsvc || isNotMsvc, m_qbsStdout.constData());
5300 if (isNotMsvc)
5301 QSKIP("Test applies with MSVC only");
5302 QbsRunParameters buildParams;
5303 if (dryRun)
5304 buildParams.arguments << "--dry-run";
5305 buildParams.expectFailure = link && !dryRun;
5306 QCOMPARE(runQbs(buildParams) == 0, !buildParams.expectFailure);
5307 QVERIFY2(m_qbsStderr.contains("This typically happens when a DLL does not export "
5308 "any symbols.") == buildParams.expectFailure,
5309 m_qbsStderr.constData());
5310 }
5311
noProfile()5312 void TestBlackbox::noProfile()
5313 {
5314 QDir::setCurrent(testDataDir + "/no-profile");
5315 QbsRunParameters params;
5316 params.profile = "none";
5317 QCOMPARE(runQbs(params), 0);
5318 QVERIFY2(m_qbsStdout.contains("profile: none"), m_qbsStdout.constData());
5319 }
5320
noSuchProfile()5321 void TestBlackbox::noSuchProfile()
5322 {
5323 QDir::setCurrent(testDataDir + "/no-such-profile");
5324 QbsRunParameters params(QStringList("products.theProduct.p:1"));
5325 params.profile = "jibbetnich";
5326 params.expectFailure = true;
5327 QVERIFY(runQbs(params) != 0);
5328 QVERIFY2(m_qbsStderr.contains("Profile 'jibbetnich' does not exist"), m_qbsStderr.constData());
5329 }
5330
nonBrokenFilesInBrokenProduct()5331 void TestBlackbox::nonBrokenFilesInBrokenProduct()
5332 {
5333 QDir::setCurrent(testDataDir + "/non-broken-files-in-broken-product");
5334 QbsRunParameters params(QStringList() << "-k");
5335 params.expectFailure = true;
5336 QVERIFY(runQbs(params) != 0);
5337 QVERIFY(m_qbsStdout.contains("fine.cpp"));
5338 QVERIFY(runQbs(params) != 0);
5339 QVERIFY(!m_qbsStdout.contains("fine.cpp")); // The non-broken file must not be recompiled.
5340 }
5341
nonDefaultProduct()5342 void TestBlackbox::nonDefaultProduct()
5343 {
5344 QDir::setCurrent(testDataDir + "/non-default-product");
5345 const QString defaultAppExe = relativeExecutableFilePath("default app");
5346 const QString nonDefaultAppExe = relativeExecutableFilePath("non-default app");
5347
5348 QCOMPARE(runQbs(), 0);
5349 QVERIFY2(QFile::exists(defaultAppExe), qPrintable(defaultAppExe));
5350 QVERIFY2(!QFile::exists(nonDefaultAppExe), qPrintable(nonDefaultAppExe));
5351
5352 QCOMPARE(runQbs(QbsRunParameters(QStringList() << "--all-products")), 0);
5353 QVERIFY2(QFile::exists(nonDefaultAppExe), qPrintable(nonDefaultAppExe));
5354 }
5355
notAlwaysUpdated()5356 void TestBlackbox::notAlwaysUpdated()
5357 {
5358 QDir::setCurrent(testDataDir + "/not-always-updated");
5359 QCOMPARE(runQbs(), 0);
5360 QCOMPARE(runQbs(), 0);
5361 }
5362
switchProfileContents(qbs::Profile & p,qbs::Settings * s,bool on)5363 static void switchProfileContents(qbs::Profile &p, qbs::Settings *s, bool on)
5364 {
5365 const QString scalarKey = "leaf.scalarProp";
5366 const QString listKey = "leaf.listProp";
5367 if (on) {
5368 p.setValue(scalarKey, "profile");
5369 p.setValue(listKey, QStringList() << "profile");
5370 } else {
5371 p.remove(scalarKey);
5372 p.remove(listKey);
5373 }
5374 s->sync();
5375 }
5376
switchFileContents(QFile & f,bool on)5377 static void switchFileContents(QFile &f, bool on)
5378 {
5379 f.seek(0);
5380 QByteArray contents = f.readAll();
5381 f.resize(0);
5382 if (on)
5383 contents.replace("// leaf.", "leaf.");
5384 else
5385 contents.replace("leaf.", "// leaf.");
5386 f.write(contents);
5387 f.flush();
5388 }
5389
propertyPrecedence()5390 void TestBlackbox::propertyPrecedence()
5391 {
5392 QDir::setCurrent(testDataDir + "/property-precedence");
5393 const SettingsPtr s = settings();
5394 qbs::Internal::TemporaryProfile profile("qbs_autotests_propPrecedence", s.get());
5395 profile.p.setValue("qbs.architecture", "x86"); // Profiles must not be empty...
5396 s->sync();
5397 const QStringList args = QStringList() << "-f" << "property-precedence.qbs";
5398 QbsRunParameters params(args);
5399 params.profile = profile.p.name();
5400 QbsRunParameters resolveParams = params;
5401 resolveParams.command = "resolve";
5402
5403 // Case 1: [cmdline=0,prod=0,export=0,nonleaf=0,profile=0]
5404 QCOMPARE(runQbs(params), 0);
5405 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5406
5407 QVERIFY2(m_qbsStdout.contains("scalar prop: leaf\n")
5408 && m_qbsStdout.contains("list prop: [\"leaf\"]\n"),
5409 m_qbsStdout.constData());
5410 params.arguments.clear();
5411
5412 // Case 2: [cmdline=0,prod=0,export=0,nonleaf=0,profile=1]
5413 switchProfileContents(profile.p, s.get(), true);
5414 QCOMPARE(runQbs(resolveParams), 0);
5415 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5416
5417 QCOMPARE(runQbs(params), 0);
5418 QVERIFY2(m_qbsStdout.contains("scalar prop: profile\n")
5419 && m_qbsStdout.contains("list prop: [\"profile\"]\n"),
5420 m_qbsStdout.constData());
5421
5422 // Case 3: [cmdline=0,prod=0,export=0,nonleaf=1,profile=0]
5423 QFile nonleafFile("modules/nonleaf/nonleaf.qbs");
5424 QVERIFY2(nonleafFile.open(QIODevice::ReadWrite), qPrintable(nonleafFile.errorString()));
5425 switchProfileContents(profile.p, s.get(), false);
5426 switchFileContents(nonleafFile, true);
5427 QCOMPARE(runQbs(resolveParams), 0);
5428 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5429 QCOMPARE(runQbs(params), 0);
5430 QVERIFY2(m_qbsStdout.contains("scalar prop: nonleaf\n")
5431 && m_qbsStdout.contains("list prop: [\"nonleaf\",\"leaf\"]\n"),
5432 m_qbsStdout.constData());
5433
5434 // Case 4: [cmdline=0,prod=0,export=0,nonleaf=1,profile=1]
5435 switchProfileContents(profile.p, s.get(), true);
5436 QCOMPARE(runQbs(resolveParams), 0);
5437 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5438 QCOMPARE(runQbs(params), 0);
5439 QVERIFY2(m_qbsStdout.contains("scalar prop: nonleaf\n")
5440 && m_qbsStdout.contains("list prop: [\"nonleaf\",\"profile\"]\n"),
5441 m_qbsStdout.constData());
5442
5443 // Case 5: [cmdline=0,prod=0,export=1,nonleaf=0,profile=0]
5444 QFile depFile("dep.qbs");
5445 QVERIFY2(depFile.open(QIODevice::ReadWrite), qPrintable(depFile.errorString()));
5446 switchProfileContents(profile.p, s.get(), false);
5447 switchFileContents(nonleafFile, false);
5448 switchFileContents(depFile, true);
5449 QCOMPARE(runQbs(resolveParams), 0);
5450 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5451 QCOMPARE(runQbs(params), 0);
5452 QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
5453 && m_qbsStdout.contains("list prop: [\"export\",\"leaf\"]\n"),
5454 m_qbsStdout.constData());
5455
5456 // Case 6: [cmdline=0,prod=0,export=1,nonleaf=0,profile=1]
5457 switchProfileContents(profile.p, s.get(), true);
5458 QCOMPARE(runQbs(resolveParams), 0);
5459 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5460 QCOMPARE(runQbs(params), 0);
5461 QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
5462 && m_qbsStdout.contains("list prop: [\"export\",\"profile\"]\n"),
5463 m_qbsStdout.constData());
5464
5465
5466 // Case 7: [cmdline=0,prod=0,export=1,nonleaf=1,profile=0]
5467 switchProfileContents(profile.p, s.get(), false);
5468 switchFileContents(nonleafFile, true);
5469 QCOMPARE(runQbs(resolveParams), 0);
5470 QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at")
5471 && m_qbsStderr.contains("nonleaf.qbs:4:22")
5472 && m_qbsStderr.contains("dep.qbs:6:26"),
5473 m_qbsStderr.constData());
5474 QCOMPARE(runQbs(params), 0);
5475 QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
5476 && m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"leaf\"]\n"),
5477 m_qbsStdout.constData());
5478
5479 // Case 8: [cmdline=0,prod=0,export=1,nonleaf=1,profile=1]
5480 switchProfileContents(profile.p, s.get(), true);
5481 QCOMPARE(runQbs(resolveParams), 0);
5482 QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at")
5483 && m_qbsStderr.contains("nonleaf.qbs:4:22")
5484 && m_qbsStderr.contains("dep.qbs:6:26"),
5485 m_qbsStderr.constData());
5486 QCOMPARE(runQbs(params), 0);
5487 QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
5488 && m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"profile\"]\n"),
5489 m_qbsStdout.constData());
5490
5491 // Case 9: [cmdline=0,prod=1,export=0,nonleaf=0,profile=0]
5492 QFile productFile("property-precedence.qbs");
5493 QVERIFY2(productFile.open(QIODevice::ReadWrite), qPrintable(productFile.errorString()));
5494 switchProfileContents(profile.p, s.get(), false);
5495 switchFileContents(nonleafFile, false);
5496 switchFileContents(depFile, false);
5497 switchFileContents(productFile, true);
5498 QCOMPARE(runQbs(resolveParams), 0);
5499 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5500 QCOMPARE(runQbs(params), 0);
5501 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5502 && m_qbsStdout.contains("list prop: [\"product\",\"leaf\"]\n"),
5503 m_qbsStdout.constData());
5504
5505 // Case 10: [cmdline=0,prod=1,export=0,nonleaf=0,profile=1]
5506 switchProfileContents(profile.p, s.get(), true);
5507 QCOMPARE(runQbs(resolveParams), 0);
5508 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5509 QCOMPARE(runQbs(params), 0);
5510 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5511 && m_qbsStdout.contains("list prop: [\"product\",\"profile\"]\n"),
5512 m_qbsStdout.constData());
5513
5514 // Case 11: [cmdline=0,prod=1,export=0,nonleaf=1,profile=0]
5515 switchProfileContents(profile.p, s.get(), false);
5516 switchFileContents(nonleafFile, true);
5517 QCOMPARE(runQbs(resolveParams), 0);
5518 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5519 QCOMPARE(runQbs(params), 0);
5520 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5521 && m_qbsStdout.contains("list prop: [\"product\",\"nonleaf\",\"leaf\"]\n"),
5522 m_qbsStdout.constData());
5523
5524 // Case 12: [cmdline=0,prod=1,export=0,nonleaf=1,profile=1]
5525 switchProfileContents(profile.p, s.get(), true);
5526 QCOMPARE(runQbs(resolveParams), 0);
5527 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5528 QCOMPARE(runQbs(params), 0);
5529 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5530 && m_qbsStdout.contains("list prop: [\"product\",\"nonleaf\",\"profile\"]\n"),
5531 m_qbsStdout.constData());
5532
5533 // Case 13: [cmdline=0,prod=1,export=1,nonleaf=0,profile=0]
5534 switchProfileContents(profile.p, s.get(), false);
5535 switchFileContents(nonleafFile, false);
5536 switchFileContents(depFile, true);
5537 QCOMPARE(runQbs(resolveParams), 0);
5538 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5539 QCOMPARE(runQbs(params), 0);
5540 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5541 && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"leaf\"]\n"),
5542 m_qbsStdout.constData());
5543
5544 // Case 14: [cmdline=0,prod=1,export=1,nonleaf=0,profile=1]
5545 switchProfileContents(profile.p, s.get(), true);
5546 QCOMPARE(runQbs(resolveParams), 0);
5547 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5548 QCOMPARE(runQbs(params), 0);
5549 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5550 && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"profile\"]\n"),
5551 m_qbsStdout.constData());
5552
5553 // Case 15: [cmdline=0,prod=1,export=1,nonleaf=1,profile=0]
5554 switchProfileContents(profile.p, s.get(), false);
5555 switchFileContents(nonleafFile, true);
5556 QCOMPARE(runQbs(resolveParams), 0);
5557 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5558 QCOMPARE(runQbs(params), 0);
5559 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5560 && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"nonleaf\",\"leaf\"]\n"),
5561 m_qbsStdout.constData());
5562
5563 // Case 16: [cmdline=0,prod=1,export=1,nonleaf=1,profile=1]
5564 switchProfileContents(profile.p, s.get(), true);
5565 QCOMPARE(runQbs(resolveParams), 0);
5566 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5567 QCOMPARE(runQbs(params), 0);
5568 QVERIFY2(m_qbsStdout.contains("scalar prop: product\n")
5569 && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"nonleaf\",\"profile\"]\n"),
5570 m_qbsStdout.constData());
5571
5572 // Command line properties wipe everything, including lists.
5573 // Case 17: [cmdline=1,prod=0,export=0,nonleaf=0,profile=0]
5574 switchProfileContents(profile.p, s.get(), false);
5575 switchFileContents(nonleafFile, false);
5576 switchFileContents(depFile, false);
5577 switchFileContents(productFile, false);
5578 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5579 QCOMPARE(runQbs(resolveParams), 0);
5580 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5581 QCOMPARE(runQbs(params), 0);
5582 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5583 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5584 m_qbsStdout.constData());
5585
5586 // Case 18: [cmdline=1,prod=0,export=0,nonleaf=0,profile=1]
5587 switchProfileContents(profile.p, s.get(), true);
5588 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5589 QCOMPARE(runQbs(resolveParams), 0);
5590 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5591 QCOMPARE(runQbs(params), 0);
5592 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5593 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5594 m_qbsStdout.constData());
5595
5596 // Case 19: [cmdline=1,prod=0,export=0,nonleaf=1,profile=0]
5597 switchProfileContents(profile.p, s.get(), false);
5598 switchFileContents(nonleafFile, true);
5599 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5600 QCOMPARE(runQbs(resolveParams), 0);
5601 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5602 QCOMPARE(runQbs(params), 0);
5603 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5604 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5605 m_qbsStdout.constData());
5606
5607 // Case 20: [cmdline=1,prod=0,export=0,nonleaf=1,profile=1]
5608 switchProfileContents(profile.p, s.get(), true);
5609 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5610 QCOMPARE(runQbs(resolveParams), 0);
5611 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5612 QCOMPARE(runQbs(params), 0);
5613 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5614 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5615 m_qbsStdout.constData());
5616
5617 // Case 21: [cmdline=1,prod=0,export=1,nonleaf=0,profile=0]
5618 switchProfileContents(profile.p, s.get(), false);
5619 switchFileContents(nonleafFile, false);
5620 switchFileContents(depFile, true);
5621 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5622 QCOMPARE(runQbs(resolveParams), 0);
5623 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5624 QCOMPARE(runQbs(params), 0);
5625 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5626 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5627 m_qbsStdout.constData());
5628
5629 // Case 22: [cmdline=1,prod=0,export=1,nonleaf=0,profile=1]
5630 switchProfileContents(profile.p, s.get(), true);
5631 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5632 QCOMPARE(runQbs(resolveParams), 0);
5633 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5634 QCOMPARE(runQbs(params), 0);
5635 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5636 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5637 m_qbsStdout.constData());
5638
5639 // Case 23: [cmdline=1,prod=0,export=1,nonleaf=1,profile=0]
5640 switchProfileContents(profile.p, s.get(), false);
5641 switchFileContents(nonleafFile, true);
5642 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5643 QCOMPARE(runQbs(resolveParams), 0);
5644 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5645 QCOMPARE(runQbs(params), 0);
5646 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5647 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5648 m_qbsStdout.constData());
5649
5650 // Case 24: [cmdline=1,prod=0,export=1,nonleaf=1,profile=1]
5651 switchProfileContents(profile.p, s.get(), true);
5652 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5653 QCOMPARE(runQbs(resolveParams), 0);
5654 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5655 QCOMPARE(runQbs(params), 0);
5656 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5657 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5658 m_qbsStdout.constData());
5659
5660 // Case 25: [cmdline=1,prod=1,export=0,nonleaf=0,profile=0]
5661 switchProfileContents(profile.p, s.get(), false);
5662 switchFileContents(nonleafFile, false);
5663 switchFileContents(depFile, false);
5664 switchFileContents(productFile, true);
5665 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5666 QCOMPARE(runQbs(resolveParams), 0);
5667 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5668 QCOMPARE(runQbs(params), 0);
5669 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5670 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5671 m_qbsStdout.constData());
5672
5673 // Case 26: [cmdline=1,prod=1,export=0,nonleaf=0,profile=1]
5674 switchProfileContents(profile.p, s.get(), true);
5675 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5676 QCOMPARE(runQbs(resolveParams), 0);
5677 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5678 QCOMPARE(runQbs(params), 0);
5679 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5680 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5681 m_qbsStdout.constData());
5682
5683 // Case 27: [cmdline=1,prod=1,export=0,nonleaf=1,profile=0]
5684 switchProfileContents(profile.p, s.get(), false);
5685 switchFileContents(nonleafFile, true);
5686 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5687 QCOMPARE(runQbs(resolveParams), 0);
5688 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5689 QCOMPARE(runQbs(params), 0);
5690 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5691 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5692 m_qbsStdout.constData());
5693
5694 // Case 28: [cmdline=1,prod=1,export=0,nonleaf=1,profile=1]
5695 switchProfileContents(profile.p, s.get(), true);
5696 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5697 QCOMPARE(runQbs(resolveParams), 0);
5698 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5699 QCOMPARE(runQbs(params), 0);
5700 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5701 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5702 m_qbsStdout.constData());
5703
5704 // Case 29: [cmdline=1,prod=1,export=1,nonleaf=0,profile=0]
5705 switchProfileContents(profile.p, s.get(), false);
5706 switchFileContents(nonleafFile, false);
5707 switchFileContents(depFile, true);
5708 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5709 QCOMPARE(runQbs(resolveParams), 0);
5710 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5711 QCOMPARE(runQbs(params), 0);
5712 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5713 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5714 m_qbsStdout.constData());
5715
5716 // Case 30: [cmdline=1,prod=1,export=1,nonleaf=0,profile=1]
5717 switchProfileContents(profile.p, s.get(), true);
5718 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5719 QCOMPARE(runQbs(resolveParams), 0);
5720 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5721 QCOMPARE(runQbs(params), 0);
5722 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5723 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5724 m_qbsStdout.constData());
5725
5726 // Case 31: [cmdline=1,prod=1,export=1,nonleaf=1,profile=0]
5727 switchProfileContents(profile.p, s.get(), false);
5728 switchFileContents(nonleafFile, true);
5729 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5730 QCOMPARE(runQbs(resolveParams), 0);
5731 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5732 QCOMPARE(runQbs(params), 0);
5733 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5734 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5735 m_qbsStdout.constData());
5736
5737 // Case 32: [cmdline=1,prod=1,export=1,nonleaf=1,profile=1]
5738 switchProfileContents(profile.p, s.get(), true);
5739 resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline";
5740 QCOMPARE(runQbs(resolveParams), 0);
5741 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5742 QCOMPARE(runQbs(params), 0);
5743 QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n")
5744 && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"),
5745 m_qbsStdout.constData());
5746 }
5747
productDependenciesByType()5748 void TestBlackbox::productDependenciesByType()
5749 {
5750 QDir::setCurrent(testDataDir + "/product-dependencies-by-type");
5751 QCOMPARE(runQbs(), 0);
5752 QFile appListFile(relativeProductBuildDir("app list") + "/app-list.txt");
5753 QVERIFY2(appListFile.open(QIODevice::ReadOnly), qPrintable(appListFile.fileName()));
5754 const QList<QByteArray> appList = appListFile.readAll().trimmed().split('\n');
5755 QCOMPARE(appList.size(), 6);
5756 QStringList apps = QStringList()
5757 << QDir::currentPath() + '/' + relativeExecutableFilePath("app1")
5758 << QDir::currentPath() + '/' + relativeExecutableFilePath("app2")
5759 << QDir::currentPath() + '/' + relativeExecutableFilePath("app3")
5760 << QDir::currentPath() + '/' + relativeExecutableFilePath("app4")
5761 << QDir::currentPath() + '/' + relativeProductBuildDir("other-product") + "/output.txt"
5762 << QDir::currentPath() + '/' + relativeProductBuildDir("yet-another-product")
5763 + "/output.txt";
5764 for (const QByteArray &line : appList) {
5765 const QString cleanLine = QString::fromLocal8Bit(line.trimmed());
5766 QVERIFY2(apps.removeOne(cleanLine), qPrintable(cleanLine));
5767 }
5768 QVERIFY(apps.empty());
5769 }
5770
productInExportedModule()5771 void TestBlackbox::productInExportedModule()
5772 {
5773 QDir::setCurrent(testDataDir + "/product-in-exported-module");
5774 QCOMPARE(runQbs(), 0);
5775 QEXPECT_FAIL(nullptr, "QBS-1576", Abort);
5776 QVERIFY2(!m_qbsStdout.contains("product: dep"), m_qbsStdout.constData());
5777 }
5778
properQuoting()5779 void TestBlackbox::properQuoting()
5780 {
5781 QDir::setCurrent(testDataDir + "/proper quoting");
5782 QCOMPARE(runQbs(), 0);
5783 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
5784 QSKIP("Cannot run binaries in cross-compiled build");
5785 QbsRunParameters params(QStringLiteral("run"), QStringList() << "-q" << "-p" << "Hello World");
5786 params.expectFailure = true; // Because the exit code is non-zero.
5787 QCOMPARE(runQbs(params), 156);
5788 const char * const expectedOutput = "whitespaceless\ncontains space\ncontains\ttab\n"
5789 "backslash\\\nHello World! The magic number is 156.";
5790 QCOMPARE(unifiedLineEndings(m_qbsStdout).constData(), expectedOutput);
5791 }
5792
propertiesInExportItems()5793 void TestBlackbox::propertiesInExportItems()
5794 {
5795 QDir::setCurrent(testDataDir + "/properties-in-export-items");
5796 QCOMPARE(runQbs(), 0);
5797 QVERIFY(regularFileExists(relativeExecutableFilePath("p1")));
5798 QVERIFY(regularFileExists(relativeExecutableFilePath("p2")));
5799 QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
5800 }
5801
protobuf_data()5802 void TestBlackbox::protobuf_data()
5803 {
5804 QTest::addColumn<QString>("projectFile");
5805 QTest::addColumn<QStringList>("properties");
5806 QTest::addColumn<bool>("hasModules");
5807 QTest::addColumn<bool>("successExpected");
5808 QTest::newRow("cpp") << QString("addressbook_cpp.qbs") << QStringList() << false << true;
5809 QTest::newRow("cpp-pkgconfig")
5810 << QString("addressbook_cpp.qbs")
5811 << QStringList("project.qbsModuleProviders:qbspkgconfig")
5812 << true
5813 << true;
5814 QTest::newRow("objc") << QString("addressbook_objc.qbs") << QStringList() << false << true;
5815 QTest::newRow("nanopb") << QString("addressbook_nanopb.qbs") << QStringList() << false << true;
5816 QTest::newRow("import") << QString("import.qbs") << QStringList() << false << true;
5817 QTest::newRow("missing import dir") << QString("needs-import-dir.qbs")
5818 << QStringList() << false << false;
5819 QTest::newRow("provided import dir")
5820 << QString("needs-import-dir.qbs")
5821 << QStringList("products.app.theImportDir:subdir") << false << true;
5822 QTest::newRow("create proto library")
5823 << QString("create-proto-library.qbs") << QStringList() << false << true;
5824 }
5825
protobuf()5826 void TestBlackbox::protobuf()
5827 {
5828 QDir::setCurrent(testDataDir + "/protobuf");
5829 QFETCH(QString, projectFile);
5830 QFETCH(QStringList, properties);
5831 QFETCH(bool, hasModules);
5832 QFETCH(bool, successExpected);
5833 rmDirR(relativeBuildDir());
5834 QbsRunParameters resolveParams("resolve", QStringList{"-f", projectFile} << properties);
5835 QCOMPARE(runQbs(resolveParams), 0);
5836 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
5837 QSKIP("Cannot run binaries in cross-compiled build");
5838 const bool withProtobuf = m_qbsStdout.contains("has protobuf: true");
5839 const bool withoutProtobuf = m_qbsStdout.contains("has protobuf: false");
5840 QVERIFY2(withProtobuf || withoutProtobuf, m_qbsStdout.constData());
5841 if (withoutProtobuf)
5842 QSKIP("protobuf module not present");
5843 const bool hasMods = m_qbsStdout.contains("has modules: true");
5844 const bool dontHaveMods = m_qbsStdout.contains("has modules: false");
5845 QVERIFY2(hasMods == !dontHaveMods, m_qbsStdout.constData());
5846 QCOMPARE(hasMods, hasModules);
5847 QbsRunParameters runParams("run");
5848 runParams.expectFailure = !successExpected;
5849 QCOMPARE(runQbs(runParams) == 0, successExpected);
5850 }
5851
protobufLibraryInstall()5852 void TestBlackbox::protobufLibraryInstall()
5853 {
5854 QDir::setCurrent(testDataDir + "/protobuf-library-install");
5855 rmDirR(relativeBuildDir());
5856 QbsRunParameters resolveParams("resolve", QStringList{"qbs.installPrefix:/usr/local"});
5857 QCOMPARE(runQbs(resolveParams), 0);
5858 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
5859 QSKIP("Cannot run binaries in cross-compiled build");
5860 const bool withProtobuf = m_qbsStdout.contains("has protobuf: true");
5861 const bool withoutProtobuf = m_qbsStdout.contains("has protobuf: false");
5862 QVERIFY2(withProtobuf || withoutProtobuf, m_qbsStdout.constData());
5863 if (withoutProtobuf)
5864 QSKIP("protobuf module not present");
5865 QbsRunParameters buildParams("build");
5866 buildParams.expectFailure = false;
5867 QCOMPARE(runQbs(buildParams), 0);
5868
5869 const QString installRootInclude = relativeBuildDir() + "/install-root/usr/local/include";
5870 QVERIFY(QFileInfo::exists(installRootInclude + "/hello.pb.h") &&
5871 QFileInfo::exists(installRootInclude + "/hello/world.pb.h"));
5872 }
5873
5874 // Tests whether it is possible to set providers properties in a Product or from command-line
providersProperties()5875 void TestBlackbox::providersProperties()
5876 {
5877 QDir::setCurrent(testDataDir + "/providers-properties");
5878
5879 QbsRunParameters params("build");
5880 params.arguments = QStringList("moduleProviders.provider_b.someProp: \"first,second\"");
5881 QCOMPARE(runQbs(params), 0);
5882 QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.listProp: [\"someValue\"]"), m_qbsStdout);
5883 QVERIFY2(m_qbsStdout.contains(
5884 "p.qbsothermodule.listProp: [\"first\",\"second\"]"), m_qbsStdout);
5885 }
5886
pseudoMultiplexing()5887 void TestBlackbox::pseudoMultiplexing()
5888 {
5889 // This is "pseudo-multiplexing" on all platforms that initialize qbs.architectures
5890 // to an array with one element. See QBS-1243.
5891 QDir::setCurrent(testDataDir + "/pseudo-multiplexing");
5892 QCOMPARE(runQbs(), 0);
5893 }
5894
qbsConfig()5895 void TestBlackbox::qbsConfig()
5896 {
5897 QbsRunParameters params("config");
5898 #ifdef QBS_ENABLE_UNIT_TESTS
5899 QTemporaryDir tempSystemSettingsDir;
5900 params.environment.insert("QBS_AUTOTEST_SYSTEM_SETTINGS_DIR", tempSystemSettingsDir.path());
5901 QTemporaryDir tempUserSettingsDir;
5902 QVERIFY(tempSystemSettingsDir.isValid());
5903 QVERIFY(tempUserSettingsDir.isValid());
5904 const QStringList settingsDirArgs = QStringList{"--settings-dir", tempUserSettingsDir.path()};
5905
5906 // Set values.
5907 params.arguments = settingsDirArgs + QStringList{"--system", "key.subkey.scalar", "s"};
5908 QCOMPARE(runQbs(params), 0);
5909 params.arguments = settingsDirArgs + QStringList{"--system", "key.subkey.list", "['sl']"};
5910 QCOMPARE(runQbs(params), 0);
5911 params.arguments = settingsDirArgs + QStringList{"--user", "key.subkey.scalar", "u"};
5912 QCOMPARE(runQbs(params), 0);
5913 params.arguments = settingsDirArgs + QStringList{"key.subkey.list", "[\"u1\",\"u2\"]"};
5914 QCOMPARE(runQbs(params), 0);
5915
5916 // Check outputs.
5917 const auto valueExtractor = [this] {
5918 const QByteArray trimmed = m_qbsStdout.trimmed();
5919 return trimmed.mid(trimmed.lastIndexOf(':') + 2);
5920 };
5921 params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.scalar"};
5922 QCOMPARE(runQbs(params), 0);
5923 QCOMPARE(valueExtractor(), QByteArray("\"u\""));
5924 params.arguments = settingsDirArgs + QStringList{"--list", "--user", "key.subkey.scalar"};
5925 QCOMPARE(runQbs(params), 0);
5926 QCOMPARE(valueExtractor(), QByteArray("\"u\""));
5927 params.arguments = settingsDirArgs + QStringList{"--list", "--system", "key.subkey.scalar"};
5928 QCOMPARE(runQbs(params), 0);
5929 QCOMPARE(valueExtractor(), QByteArray("\"s\""));
5930 params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.list"};
5931 QCOMPARE(runQbs(params), 0);
5932 QCOMPARE(valueExtractor(), QByteArray("[\"u1\", \"u2\", \"sl\"]"));
5933 params.arguments = settingsDirArgs + QStringList{"--list", "--user", "key.subkey.list"};
5934 QCOMPARE(runQbs(params), 0);
5935 QCOMPARE(valueExtractor(), QByteArray("[\"u1\", \"u2\"]"));
5936 params.arguments = settingsDirArgs + QStringList{"--list", "--system", "key.subkey.list"};
5937 QCOMPARE(runQbs(params), 0);
5938 QCOMPARE(valueExtractor(), QByteArray("[\"sl\"]"));
5939
5940 // Remove some values and re-check.
5941 params.arguments = settingsDirArgs + QStringList{"--unset", "key.subkey.scalar"};
5942 QCOMPARE(runQbs(params), 0);
5943 params.arguments = settingsDirArgs + QStringList{"--system", "--unset", "key.subkey.list"};
5944 QCOMPARE(runQbs(params), 0);
5945 params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.scalar"};
5946 QCOMPARE(runQbs(params), 0);
5947 QCOMPARE(valueExtractor(), QByteArray("\"s\""));
5948 params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.list"};
5949 QCOMPARE(runQbs(params), 0);
5950 QCOMPARE(valueExtractor(), QByteArray("[\"u1\", \"u2\"]"));
5951
5952 // Check preferences.ignoreSystemSearchPaths
5953 params.arguments = settingsDirArgs
5954 + QStringList{"--system", "preferences.qbsSearchPaths", "['/usr/lib/qbs']"};
5955 QCOMPARE(runQbs(params), 0);
5956 params.arguments = settingsDirArgs
5957 + QStringList{"preferences.qbsSearchPaths", "['/home/user/qbs']"};
5958 QCOMPARE(runQbs(params), 0);
5959 qbs::Settings settings(tempUserSettingsDir.path(), tempSystemSettingsDir.path());
5960 const qbs::Preferences prefs(&settings, "SomeProfile");
5961 QVERIFY2(prefs.searchPaths().contains("/usr/lib/qbs")
5962 && prefs.searchPaths().contains("/home/user/qbs"),
5963 qPrintable(prefs.searchPaths().join(',')));
5964 settings.setValue("profiles.SomeProfile.preferences.ignoreSystemSearchPaths", true);
5965 QVERIFY2(!prefs.searchPaths().contains("/usr/lib/qbs")
5966 && prefs.searchPaths().contains("/home/user/qbs"),
5967 qPrintable(prefs.searchPaths().join(',')));
5968
5969 #else
5970 qDebug() << "ability to redirect the system settings dir not compiled in, skipping"
5971 "most qbs-config tests";
5972 #endif // QBS_ENABLE_UNIT_TESTS
5973
5974 bool canWriteToSystemSettings;
5975 QString testSettingsFilePath;
5976 {
5977 QSettings testSettings(
5978 qbs::Settings::defaultSystemSettingsBaseDir() + "/dummyOrg" + "/dummyApp.conf",
5979 QSettings::IniFormat);
5980 testSettings.setValue("dummyKey", "dummyValue");
5981 testSettings.sync();
5982 canWriteToSystemSettings = testSettings.status() == QSettings::NoError;
5983 testSettingsFilePath = testSettings.fileName();
5984 }
5985 if (canWriteToSystemSettings)
5986 QVERIFY(QFile::remove(testSettingsFilePath));
5987
5988 // Check that trying to write to actual system settings causes access failure.
5989 params.expectFailure = !canWriteToSystemSettings;
5990 params.environment.clear();
5991 params.arguments = QStringList{"--system", "key.subkey.scalar", "s"};
5992 QCOMPARE(runQbs(params) == 0, canWriteToSystemSettings);
5993 if (!canWriteToSystemSettings) {
5994 QVERIFY2(m_qbsStderr.contains("You do not have permission to write to that location."),
5995 m_qbsStderr.constData());
5996 }
5997 }
5998
qbsConfigAddProfile()5999 void TestBlackbox::qbsConfigAddProfile()
6000 {
6001 QbsRunParameters params("config");
6002 QTemporaryDir settingsDir1;
6003 QTemporaryDir settingsDir2;
6004 QVERIFY(settingsDir1.isValid());
6005 QVERIFY(settingsDir2.isValid());
6006 const QStringList settingsDir1Args = QStringList{"--settings-dir", settingsDir1.path()};
6007 const QStringList settingsDir2Args = QStringList{"--settings-dir", settingsDir2.path()};
6008
6009 QFETCH(QStringList, args);
6010 QFETCH(QString, errorMsg);
6011
6012 // Step 1: Run --add-profile.
6013 params.arguments = settingsDir1Args;
6014 params.arguments << "--add-profile";
6015 params.arguments << args;
6016 params.expectFailure = !errorMsg.isEmpty();
6017 QCOMPARE(runQbs(params) == 0, !params.expectFailure);
6018 if (params.expectFailure) {
6019 QVERIFY(QString::fromLocal8Bit(m_qbsStderr).contains(errorMsg));
6020 return;
6021 }
6022 params.expectFailure = false;
6023 params.arguments = settingsDir1Args;
6024 params.arguments << "--list";
6025 QCOMPARE(runQbs(params), 0);
6026 const QByteArray output1 = m_qbsStdout;
6027
6028 // Step 2: Set properties manually.
6029 for (int i = 1; i < args.size(); i += 2) {
6030 params.arguments = settingsDir2Args;
6031 params.arguments << ("profiles." + args.first() + '.' + args.at(i)) << args.at(i + 1);
6032 QCOMPARE(runQbs(params), 0);
6033 }
6034 params.arguments = settingsDir2Args;
6035 params.arguments << "--list";
6036 QCOMPARE(runQbs(params), 0);
6037 const QByteArray output2 = m_qbsStdout;
6038
6039 // Step3: Compare results.
6040 QCOMPARE(output1, output2);
6041 }
6042
qbsConfigAddProfile_data()6043 void TestBlackbox::qbsConfigAddProfile_data()
6044 {
6045 QTest::addColumn<QStringList>("args");
6046 QTest::addColumn<QString>("errorMsg");
6047 QTest::newRow("no arguments") << QStringList() << QString("Profile name missing");
6048 QTest::newRow("empty name") << QStringList{"", "p", "v"}
6049 << QString("Profile name must not be empty");
6050 QTest::newRow("no properties") << QStringList("p")
6051 << QString("Profile properties must be provided");
6052 QTest::newRow("one property") << QStringList{"p", "p", "v"} << QString();
6053 QTest::newRow("two properties") << QStringList{"p", "p1", "v1", "p2", "v2"} << QString();
6054 QTest::newRow("missing value") << QStringList{"p", "p"}
6055 << QString("Profile properties must be key/value pairs");
6056 QTest::newRow("missing values") << QStringList{"p", "p1", "v1", "p2"}
6057 << QString("Profile properties must be key/value pairs");
6058 }
6059
6060 // Tests whether it is possible to set qbsModuleProviders in Product and Project items
6061 // and that the order of providers results in correct priority
qbsModuleProviders()6062 void TestBlackbox::qbsModuleProviders()
6063 {
6064 QFETCH(QStringList, arguments);
6065 QFETCH(QString, firstProp);
6066 QFETCH(QString, secondProp);
6067
6068 QDir::setCurrent(testDataDir + "/qbs-module-providers");
6069
6070 QbsRunParameters params("resolve");
6071 params.arguments = arguments;
6072 QCOMPARE(runQbs(params), 0);
6073 QVERIFY2(m_qbsStdout.contains(("p1.qbsmetatestmodule.prop: " + firstProp).toUtf8()),
6074 m_qbsStdout);
6075 QVERIFY2(m_qbsStdout.contains(("p1.qbsothermodule.prop: " + secondProp).toUtf8()),
6076 m_qbsStdout);
6077 QVERIFY2(m_qbsStdout.contains(("p2.qbsmetatestmodule.prop: " + firstProp).toUtf8()),
6078 m_qbsStdout);
6079 QVERIFY2(m_qbsStdout.contains(("p2.qbsothermodule.prop: " + secondProp).toUtf8()),
6080 m_qbsStdout);
6081 }
6082
qbsModuleProviders_data()6083 void TestBlackbox::qbsModuleProviders_data()
6084 {
6085 QTest::addColumn<QStringList>("arguments");
6086 QTest::addColumn<QString>("firstProp");
6087 QTest::addColumn<QString>("secondProp");
6088
6089 QTest::newRow("default") << QStringList() << "from_provider_a" << "undefined";
6090 QTest::newRow("override")
6091 << QStringList("projects.project.qbsModuleProviders:provider_b")
6092 << "from_provider_b"
6093 << "from_provider_b";
6094 QTest::newRow("override list a")
6095 << QStringList("projects.project.qbsModuleProviders:provider_a,provider_b")
6096 << "from_provider_a"
6097 << "from_provider_b";
6098 QTest::newRow("override list b")
6099 << QStringList("projects.project.qbsModuleProviders:provider_b,provider_a")
6100 << "from_provider_b"
6101 << "from_provider_b";
6102 }
6103
6104 // Tests possible use-cases how to override providers from command-line
qbsModuleProvidersCliOverride()6105 void TestBlackbox::qbsModuleProvidersCliOverride()
6106 {
6107 QFETCH(QStringList, arguments);
6108 QFETCH(QString, propertyValue);
6109
6110 QDir::setCurrent(testDataDir + "/qbs-module-providers-cli-override");
6111
6112 QbsRunParameters params("resolve");
6113 params.arguments = arguments;
6114 QCOMPARE(runQbs(params), 0);
6115 QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()),
6116 m_qbsStdout);
6117 }
6118
qbsModuleProvidersCliOverride_data()6119 void TestBlackbox::qbsModuleProvidersCliOverride_data()
6120 {
6121 QTest::addColumn<QStringList>("arguments");
6122 QTest::addColumn<QString>("propertyValue");
6123
6124 QTest::newRow("default") << QStringList() << "undefined";
6125 QTest::newRow("project-wide")
6126 << QStringList("project.qbsModuleProviders:provider_a")
6127 << "from_provider_a";
6128 QTest::newRow("concrete project")
6129 << QStringList("projects.innerProject.qbsModuleProviders:provider_a")
6130 << "from_provider_a";
6131 QTest::newRow("concrete product")
6132 << QStringList("products.product.qbsModuleProviders:provider_a")
6133 << "from_provider_a";
6134 QTest::newRow("concrete project override project-wide")
6135 << QStringList({
6136 "project.qbsModuleProviders:provider_a",
6137 "projects.innerProject.qbsModuleProviders:provider_b"})
6138 << "from_provider_b";
6139 QTest::newRow("concrete product override project-wide")
6140 << QStringList({
6141 "project.qbsModuleProviders:provider_a",
6142 "products.product.qbsModuleProviders:provider_b"})
6143 << "from_provider_b";
6144 }
6145
6146 // Tests whether scoped providers can be used as named, i.e. new provider machinery
6147 // is compatible with the old one
qbsModuleProvidersCompatibility()6148 void TestBlackbox::qbsModuleProvidersCompatibility()
6149 {
6150 QFETCH(QStringList, arguments);
6151 QFETCH(QString, propertyValue);
6152
6153 QDir::setCurrent(testDataDir + "/qbs-module-providers-compatibility");
6154
6155 QbsRunParameters params("resolve");
6156 params.arguments = arguments;
6157 QCOMPARE(runQbs(params), 0);
6158 QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()),
6159 m_qbsStdout);
6160 }
6161
qbsModuleProvidersCompatibility_data()6162 void TestBlackbox::qbsModuleProvidersCompatibility_data()
6163 {
6164 QTest::addColumn<QStringList>("arguments");
6165 QTest::addColumn<QString>("propertyValue");
6166
6167 QTest::newRow("default") << QStringList() << "from_scoped_provider";
6168 QTest::newRow("scoped by name") << QStringList("project.qbsModuleProviders:qbsmetatestmodule") << "from_scoped_provider";
6169 QTest::newRow("named") << QStringList("project.qbsModuleProviders:named_provider") << "from_named_provider";
6170 }
6171
qbspkgconfigModuleProvider()6172 void TestBlackbox::qbspkgconfigModuleProvider()
6173 {
6174 QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider/libs");
6175
6176 const auto commonParams = QbsRunParameters(QStringLiteral("install"), {
6177 QStringLiteral("qbs.installPrefix:/usr/local"),
6178 QStringLiteral("--install-root"),
6179 QStringLiteral("install-root")
6180 });
6181 auto dynamicParams = commonParams;
6182 dynamicParams.arguments << "config:library" << "projects.libs.isBundle:false";
6183 QCOMPARE(runQbs(dynamicParams), 0);
6184
6185 QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider");
6186
6187 QbsRunParameters params;
6188 params.arguments
6189 << "moduleProviders.qbspkgconfig.libDirs:libdir"
6190 << "moduleProviders.qbspkgconfig.sysroot:" + QDir::currentPath() + "/libs/install-root";
6191 QCOMPARE(runQbs(params), 0);
6192 }
6193
getNextSessionPacket(QProcess & session,QByteArray & data)6194 static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data)
6195 {
6196 int totalSize = -1;
6197 QElapsedTimer timer;
6198 timer.start();
6199 QByteArray msg;
6200 while (totalSize == -1 || msg.size() < totalSize) {
6201 if (data.isEmpty())
6202 session.waitForReadyRead(1000);
6203 if (timer.elapsed() >= testTimeoutInMsecs())
6204 return QJsonObject();
6205 data += session.readAllStandardOutput();
6206 if (totalSize == -1) {
6207 static const QByteArray magicString = "qbsmsg:";
6208 const int magicStringOffset = data.indexOf(magicString);
6209 if (magicStringOffset == -1)
6210 continue;
6211 const int sizeOffset = magicStringOffset + magicString.length();
6212 const int newlineOffset = data.indexOf('\n', sizeOffset);
6213 if (newlineOffset == -1)
6214 continue;
6215 const QByteArray sizeString = data.mid(sizeOffset, newlineOffset - sizeOffset);
6216 bool isNumber;
6217 const int size = sizeString.toInt(&isNumber);
6218 if (!isNumber || size <= 0)
6219 return QJsonObject();
6220 data = data.mid(newlineOffset + 1);
6221 totalSize = size;
6222 }
6223 const int bytesToTake = std::min(totalSize - msg.size(), data.size());
6224 msg += data.left(bytesToTake);
6225 data = data.mid(bytesToTake);
6226 }
6227 return QJsonDocument::fromJson(QByteArray::fromBase64(msg)).object();
6228 }
6229
qbsSession()6230 void TestBlackbox::qbsSession()
6231 {
6232 QDir::setCurrent(testDataDir + "/qbs-session");
6233 QProcess sessionProc;
6234 sessionProc.start(qbsExecutableFilePath, QStringList("session"));
6235
6236 // Uncomment for debugging.
6237 /*
6238 connect(&sessionProc, &QProcess::readyReadStandardError, [&sessionProc] {
6239 qDebug() << "stderr:" << sessionProc.readAllStandardError();
6240 });
6241 */
6242
6243 QVERIFY(sessionProc.waitForStarted());
6244
6245 const auto sendPacket = [&sessionProc](const QJsonObject &message) {
6246 const QByteArray data = QJsonDocument(message).toJson().toBase64();
6247 sessionProc.write("qbsmsg:");
6248 sessionProc.write(QByteArray::number(data.length()));
6249 sessionProc.write("\n");
6250 sessionProc.write(data);
6251 };
6252
6253 static const auto envToJson = [](const QProcessEnvironment &env) {
6254 QJsonObject envObj;
6255 const QStringList keys = env.keys();
6256 for (const QString &key : keys)
6257 envObj.insert(key, env.value(key));
6258 return envObj;
6259 };
6260
6261 static const auto envFromJson = [](const QJsonValue &v) {
6262 const QJsonObject obj = v.toObject();
6263 QProcessEnvironment env;
6264 for (auto it = obj.begin(); it != obj.end(); ++it)
6265 env.insert(it.key(), it.value().toString());
6266 return env;
6267 };
6268
6269 QByteArray incomingData;
6270
6271 // Wait for and verify hello packet.
6272 QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6273 QCOMPARE(receivedMessage.value("type"), "hello");
6274 QCOMPARE(receivedMessage.value("api-level").toInt(), 2);
6275 QCOMPARE(receivedMessage.value("api-compat-level").toInt(), 2);
6276
6277 // Resolve & verify structure
6278 QJsonObject resolveMessage;
6279 resolveMessage.insert("type", "resolve-project");
6280 resolveMessage.insert("top-level-profile", profileName());
6281 resolveMessage.insert("configuration-name", "my-config");
6282 resolveMessage.insert("project-file-path", QDir::currentPath() + "/qbs-session.qbs");
6283 resolveMessage.insert("build-root", QDir::currentPath());
6284 resolveMessage.insert("settings-directory", settings()->baseDirectory());
6285 QJsonObject overriddenValues;
6286 overriddenValues.insert("products.theLib.cpp.cxxLanguageVersion", "c++17");
6287 resolveMessage.insert("overridden-properties", overriddenValues);
6288 resolveMessage.insert("environment", envToJson(QbsRunParameters::defaultEnvironment()));
6289 resolveMessage.insert("data-mode", "only-if-changed");
6290 resolveMessage.insert("log-time", true);
6291 resolveMessage.insert("module-properties",
6292 QJsonArray::fromStringList({"cpp.cxxLanguageVersion"}));
6293 sendPacket(resolveMessage);
6294 bool receivedLogData = false;
6295 bool receivedStartedSignal = false;
6296 bool receivedProgressData = false;
6297 bool receivedReply = false;
6298 while (!receivedReply) {
6299 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6300 QVERIFY(!receivedMessage.isEmpty());
6301 const QString msgType = receivedMessage.value("type").toString();
6302 if (msgType == "project-resolved") {
6303 receivedReply = true;
6304 const QJsonObject error = receivedMessage.value("error").toObject();
6305 if (!error.isEmpty())
6306 qDebug() << error;
6307 QVERIFY(error.isEmpty());
6308 const QJsonObject projectData = receivedMessage.value("project-data").toObject();
6309 QCOMPARE(projectData.value("name").toString(), "qbs-session");
6310 const QJsonArray products = projectData.value("products").toArray();
6311 QCOMPARE(products.size(), 2);
6312 for (const QJsonValue &v : products) {
6313 const QJsonObject product = v.toObject();
6314 const QString productName = product.value("name").toString();
6315 QVERIFY(!productName.isEmpty());
6316 QVERIFY2(product.value("is-enabled").toBool(), qPrintable(productName));
6317 bool theLib = false;
6318 bool theApp = false;
6319 if (productName == "theLib")
6320 theLib = true;
6321 else if (productName == "theApp")
6322 theApp = true;
6323 QVERIFY2(theLib || theApp, qPrintable(productName));
6324 const QJsonArray groups = product.value("groups").toArray();
6325 if (theLib)
6326 QVERIFY(groups.size() >= 3);
6327 else
6328 QVERIFY(!groups.isEmpty());
6329 for (const QJsonValue &v : groups) {
6330 const QJsonObject group = v.toObject();
6331 const QJsonArray sourceArtifacts
6332 = group.value("source-artifacts").toArray();
6333 const auto findArtifact = [&sourceArtifacts](const QString &fileName) {
6334 for (const QJsonValue &v : sourceArtifacts) {
6335 const QJsonObject artifact = v.toObject();
6336 if (QFileInfo(artifact.value("file-path").toString()).fileName()
6337 == fileName) {
6338 return artifact;
6339 }
6340 }
6341 return QJsonObject();
6342 };
6343 const QString groupName = group.value("name").toString();
6344 const auto getCxxLanguageVersion = [&group, &product] {
6345 QJsonObject moduleProperties = group.value("module-properties").toObject();
6346 if (moduleProperties.isEmpty())
6347 moduleProperties = product.value("module-properties").toObject();
6348 return moduleProperties.toVariantMap().value("cpp.cxxLanguageVersion")
6349 .toStringList();
6350 };
6351 if (groupName == "sources") {
6352 const QJsonObject artifact = findArtifact("lib.cpp");
6353 QVERIFY2(!artifact.isEmpty(), "lib.cpp");
6354 QCOMPARE(getCxxLanguageVersion(), {"c++17"});
6355 } else if (groupName == "headers") {
6356 const QJsonObject artifact = findArtifact("lib.h");
6357 QVERIFY2(!artifact.isEmpty(), "lib.h");
6358 } else if (groupName == "theApp") {
6359 const QJsonObject artifact = findArtifact("main.cpp");
6360 QVERIFY2(!artifact.isEmpty(), "main.cpp");
6361 QCOMPARE(getCxxLanguageVersion(), {"c++14"});
6362 }
6363 }
6364 }
6365 break;
6366 } else if (msgType == "log-data") {
6367 if (receivedMessage.value("message").toString().contains("activity"))
6368 receivedLogData = true;
6369 } else if (msgType == "task-started") {
6370 receivedStartedSignal = true;
6371 } else if (msgType == "task-progress") {
6372 receivedProgressData = true;
6373 } else if (msgType != "new-max-progress") {
6374 QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType)));
6375 }
6376 }
6377 QVERIFY(receivedReply);
6378 QVERIFY(receivedLogData);
6379 QVERIFY(receivedStartedSignal);
6380 QVERIFY(receivedProgressData);
6381
6382 // First build: No install, log time, default command description.
6383 QJsonObject buildRequest;
6384 buildRequest.insert("type", "build-project");
6385 buildRequest.insert("log-time", true);
6386 buildRequest.insert("install", false);
6387 buildRequest.insert("data-mode", "only-if-changed");
6388 sendPacket(buildRequest);
6389 receivedReply = false;
6390 receivedLogData = false;
6391 receivedStartedSignal = false;
6392 receivedProgressData = false;
6393 bool receivedCommandDescription = false;
6394 bool receivedProcessResult = false;
6395 while (!receivedReply) {
6396 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6397 QVERIFY(!receivedMessage.isEmpty());
6398 const QString msgType = receivedMessage.value("type").toString();
6399 if (msgType == "project-built") {
6400 receivedReply = true;
6401 const QJsonObject error = receivedMessage.value("error").toObject();
6402 if (!error.isEmpty())
6403 qDebug() << error;
6404 QVERIFY(error.isEmpty());
6405 const QJsonObject projectData = receivedMessage.value("project-data").toObject();
6406 QCOMPARE(projectData.value("name").toString(), "qbs-session");
6407 } else if (msgType == "log-data") {
6408 if (receivedMessage.value("message").toString().contains("activity"))
6409 receivedLogData = true;
6410 } else if (msgType == "task-started") {
6411 receivedStartedSignal = true;
6412 } else if (msgType == "task-progress") {
6413 receivedProgressData = true;
6414 } else if (msgType == "command-description") {
6415 if (receivedMessage.value("message").toString().contains("compiling main.cpp"))
6416 receivedCommandDescription = true;
6417 } else if (msgType == "process-result") {
6418 QCOMPARE(receivedMessage.value("exit-code").toInt(), 0);
6419 receivedProcessResult = true;
6420 } else if (msgType != "new-max-progress") {
6421 QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType)));
6422 }
6423 }
6424 QVERIFY(receivedReply);
6425 QVERIFY(receivedLogData);
6426 QVERIFY(receivedStartedSignal);
6427 QVERIFY(receivedProgressData);
6428 QVERIFY(receivedCommandDescription);
6429 QVERIFY(receivedProcessResult);
6430 const QString &exeFilePath = QDir::currentPath() + '/'
6431 + relativeExecutableFilePath("theApp", "my-config");
6432 QVERIFY2(regularFileExists(exeFilePath), qPrintable(exeFilePath));
6433 const QString defaultInstallRoot = QDir::currentPath() + '/'
6434 + relativeBuildDir("my-config") + "/install-root";
6435 QVERIFY2(!directoryExists(defaultInstallRoot), qPrintable(defaultInstallRoot));
6436
6437 // Clean.
6438 QJsonObject cleanRequest;
6439 cleanRequest.insert("type", "clean-project");
6440 cleanRequest.insert("settings-dir", settings()->baseDirectory());
6441 cleanRequest.insert("log-time", true);
6442 sendPacket(cleanRequest);
6443 receivedReply = false;
6444 receivedLogData = false;
6445 receivedStartedSignal = false;
6446 receivedProgressData = false;
6447 while (!receivedReply) {
6448 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6449 QVERIFY(!receivedMessage.isEmpty());
6450 const QString msgType = receivedMessage.value("type").toString();
6451 if (msgType == "project-cleaned") {
6452 receivedReply = true;
6453 const QJsonObject error = receivedMessage.value("error").toObject();
6454 if (!error.isEmpty())
6455 qDebug() << error;
6456 QVERIFY(error.isEmpty());
6457 } else if (msgType == "log-data") {
6458 if (receivedMessage.value("message").toString().contains("activity"))
6459 receivedLogData = true;
6460 } else if (msgType == "task-started") {
6461 receivedStartedSignal = true;
6462 } else if (msgType == "task-progress") {
6463 receivedProgressData = true;
6464 } else if (msgType != "new-max-progress") {
6465 QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType)));
6466 }
6467 }
6468 QVERIFY(receivedReply);
6469 QVERIFY(receivedLogData);
6470 QVERIFY(receivedStartedSignal);
6471 QVERIFY(receivedProgressData);
6472 QVERIFY2(!regularFileExists(exeFilePath), qPrintable(exeFilePath));
6473
6474 // Second build: Do not log the time, show command lines.
6475 buildRequest.insert("log-time", false);
6476 buildRequest.insert("command-echo-mode", "command-line");
6477 sendPacket(buildRequest);
6478 receivedReply = false;
6479 receivedLogData = false;
6480 receivedStartedSignal = false;
6481 receivedProgressData = false;
6482 receivedCommandDescription = false;
6483 receivedProcessResult = false;
6484 while (!receivedReply) {
6485 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6486 QVERIFY(!receivedMessage.isEmpty());
6487 const QString msgType = receivedMessage.value("type").toString();
6488 if (msgType == "project-built") {
6489 receivedReply = true;
6490 const QJsonObject error = receivedMessage.value("error").toObject();
6491 if (!error.isEmpty())
6492 qDebug() << error;
6493 QVERIFY(error.isEmpty());
6494 const QJsonObject projectData = receivedMessage.value("project-data").toObject();
6495 QVERIFY(projectData.isEmpty());
6496 } else if (msgType == "log-data") {
6497 if (receivedMessage.value("message").toString().contains("activity"))
6498 receivedLogData = true;
6499 } else if (msgType == "task-started") {
6500 receivedStartedSignal = true;
6501 } else if (msgType == "task-progress") {
6502 receivedProgressData = true;
6503 } else if (msgType == "command-description") {
6504 if (QDir::fromNativeSeparators(receivedMessage.value("message").toString())
6505 .contains("/main.cpp")) {
6506 receivedCommandDescription = true;
6507 }
6508 } else if (msgType == "process-result") {
6509 QCOMPARE(receivedMessage.value("exit-code").toInt(), 0);
6510 receivedProcessResult = true;
6511 } else if (msgType != "new-max-progress") {
6512 QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType)));
6513 }
6514 }
6515 QVERIFY(receivedReply);
6516 QVERIFY(!receivedLogData);
6517 QVERIFY(receivedStartedSignal);
6518 QVERIFY(receivedProgressData);
6519 QVERIFY(receivedCommandDescription);
6520 QVERIFY(receivedProcessResult);
6521 QVERIFY2(regularFileExists(exeFilePath), qPrintable(exeFilePath));
6522 QVERIFY2(!directoryExists(defaultInstallRoot), qPrintable(defaultInstallRoot));
6523
6524 // Install.
6525 QJsonObject installRequest;
6526 installRequest.insert("type", "install-project");
6527 installRequest.insert("log-time", true);
6528 const QString customInstallRoot = QDir::currentPath() + "/my-install-root";
6529 QVERIFY2(!QFile::exists(customInstallRoot), qPrintable(customInstallRoot));
6530 installRequest.insert("install-root", customInstallRoot);
6531 sendPacket(installRequest);
6532 receivedReply = false;
6533 receivedLogData = false;
6534 receivedStartedSignal = false;
6535 receivedProgressData = false;
6536 while (!receivedReply) {
6537 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6538 QVERIFY(!receivedMessage.isEmpty());
6539 const QString msgType = receivedMessage.value("type").toString();
6540 if (msgType == "install-done") {
6541 receivedReply = true;
6542 const QJsonObject error = receivedMessage.value("error").toObject();
6543 if (!error.isEmpty())
6544 qDebug() << error;
6545 QVERIFY(error.isEmpty());
6546 } else if (msgType == "log-data") {
6547 if (receivedMessage.value("message").toString().contains("activity"))
6548 receivedLogData = true;
6549 } else if (msgType == "task-started") {
6550 receivedStartedSignal = true;
6551 } else if (msgType == "task-progress") {
6552 receivedProgressData = true;
6553 } else if (msgType != "new-max-progress") {
6554 QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType)));
6555 }
6556 }
6557 QVERIFY(receivedReply);
6558 QVERIFY(receivedLogData);
6559 QVERIFY(receivedStartedSignal);
6560 QVERIFY(receivedProgressData);
6561 QVERIFY2(!directoryExists(defaultInstallRoot), qPrintable(defaultInstallRoot));
6562 QVERIFY2(directoryExists(customInstallRoot), qPrintable(customInstallRoot));
6563
6564 // Retrieve modified environment.
6565 QJsonObject getRunEnvRequest;
6566 getRunEnvRequest.insert("type", "get-run-environment");
6567 getRunEnvRequest.insert("product", "theApp");
6568 const QProcessEnvironment inEnv = QProcessEnvironment::systemEnvironment();
6569 QVERIFY(!inEnv.contains("MY_MODULE"));
6570 getRunEnvRequest.insert("base-environment", envToJson(inEnv));
6571 sendPacket(getRunEnvRequest);
6572 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6573 QCOMPARE(receivedMessage.value("type").toString(), QString("run-environment"));
6574 QJsonObject error = receivedMessage.value("error").toObject();
6575 if (!error.isEmpty())
6576 qDebug() << error;
6577 QVERIFY(error.isEmpty());
6578 const QProcessEnvironment outEnv = envFromJson(receivedMessage.value("full-environment"));
6579 QVERIFY(outEnv.keys().size() > inEnv.keys().size());
6580 QCOMPARE(outEnv.value("MY_MODULE"), QString("1"));
6581
6582 // Add two files to library and re-build.
6583 QJsonObject addFilesRequest;
6584 addFilesRequest.insert("type", "add-files");
6585 addFilesRequest.insert("product", "theLib");
6586 addFilesRequest.insert("group", "sources");
6587 addFilesRequest.insert("files",
6588 QJsonArray::fromStringList({QDir::currentPath() + "/file1.cpp",
6589 QDir::currentPath() + "/file2.cpp"}));
6590 sendPacket(addFilesRequest);
6591 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6592 QCOMPARE(receivedMessage.value("type").toString(), QString("files-added"));
6593 error = receivedMessage.value("error").toObject();
6594 if (!error.isEmpty()) {
6595 for (const auto item: error[QStringLiteral("items")].toArray()) {
6596 const auto description = QStringLiteral("Project file updates are not enabled");
6597 if (item.toObject()[QStringLiteral("description")].toString().contains(description))
6598 QSKIP("File updates are disabled");
6599 }
6600 qDebug() << error;
6601 }
6602 QVERIFY(error.isEmpty());
6603
6604 receivedReply = false;
6605 sendPacket(resolveMessage);
6606 while (!receivedReply) {
6607 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6608 QVERIFY(!receivedMessage.isEmpty());
6609 const QString msgType = receivedMessage.value("type").toString();
6610 if (msgType == "project-resolved") {
6611 receivedReply = true;
6612 const QJsonObject error = receivedMessage.value("error").toObject();
6613 if (!error.isEmpty())
6614 qDebug() << error;
6615 QVERIFY(error.isEmpty());
6616 const QJsonObject projectData = receivedMessage.value("project-data").toObject();
6617 QJsonArray products = projectData.value("products").toArray();
6618 bool file1 = false;
6619 bool file2 = false;
6620 for (const QJsonValue v : products) {
6621 const QJsonObject product = v.toObject();
6622 const QString productName = product.value("full-display-name").toString();
6623 const QJsonArray groups = product.value("groups").toArray();
6624 for (const QJsonValue &v : groups) {
6625 const QJsonObject group = v.toObject();
6626 const QString groupName = group.value("name").toString();
6627 const QJsonArray sourceArtifacts = group.value("source-artifacts").toArray();
6628 for (const QJsonValue &v : sourceArtifacts) {
6629 const QString filePath = v.toObject().value("file-path").toString();
6630 if (filePath.endsWith("file1.cpp")) {
6631 QCOMPARE(productName, QString("theLib"));
6632 QCOMPARE(groupName, QString("sources"));
6633 file1 = true;
6634 } else if (filePath.endsWith("file2.cpp")) {
6635 QCOMPARE(productName, QString("theLib"));
6636 QCOMPARE(groupName, QString("sources"));
6637 file2 = true;
6638 }
6639 }
6640 }
6641 }
6642 QVERIFY(file1);
6643 QVERIFY(file2);
6644 }
6645 }
6646 QVERIFY(receivedReply);
6647
6648 receivedReply = false;
6649 receivedProcessResult = false;
6650 bool compiledFile1 = false;
6651 bool compiledFile2 = false;
6652 bool compiledMain = false;
6653 bool compiledLib = false;
6654 buildRequest.remove("command-echo-mode");
6655 sendPacket(buildRequest);
6656 while (!receivedReply) {
6657 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6658 QVERIFY(!receivedMessage.isEmpty());
6659 const QString msgType = receivedMessage.value("type").toString();
6660 if (msgType == "project-built") {
6661 receivedReply = true;
6662 const QJsonObject error = receivedMessage.value("error").toObject();
6663 if (!error.isEmpty())
6664 qDebug() << error;
6665 QVERIFY(error.isEmpty());
6666 } else if (msgType == "command-description") {
6667 const QString msg = receivedMessage.value("message").toString();
6668 if (msg.contains("compiling file1.cpp"))
6669 compiledFile1 = true;
6670 else if (msg.contains("compiling file2.cpp"))
6671 compiledFile2 = true;
6672 else if (msg.contains("compiling main.cpp"))
6673 compiledMain = true;
6674 else if (msg.contains("compiling lib.cpp"))
6675 compiledLib = true;
6676 } else if (msgType == "process-result") {
6677 QCOMPARE(receivedMessage.value("exit-code").toInt(), 0);
6678 receivedProcessResult = true;
6679 }
6680 }
6681 QVERIFY(receivedReply);
6682 QVERIFY(!receivedProcessResult);
6683 QVERIFY(compiledFile1);
6684 QVERIFY(compiledFile2);
6685 QVERIFY(!compiledLib);
6686 QVERIFY(!compiledMain);
6687
6688 // Remove one of the newly added files again and re-build.
6689 WAIT_FOR_NEW_TIMESTAMP();
6690 touch("file1.cpp");
6691 touch("file2.cpp");
6692 touch("main.cpp");
6693 QJsonObject removeFilesRequest;
6694 removeFilesRequest.insert("type", "remove-files");
6695 removeFilesRequest.insert("product", "theLib");
6696 removeFilesRequest.insert("group", "sources");
6697 removeFilesRequest.insert("files",
6698 QJsonArray::fromStringList({QDir::currentPath() + "/file1.cpp"}));
6699 sendPacket(removeFilesRequest);
6700 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6701 QCOMPARE(receivedMessage.value("type").toString(), QString("files-removed"));
6702 error = receivedMessage.value("error").toObject();
6703 if (!error.isEmpty())
6704 qDebug() << error;
6705 QVERIFY(error.isEmpty());
6706 receivedReply = false;
6707 sendPacket(resolveMessage);
6708 while (!receivedReply) {
6709 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6710 QVERIFY(!receivedMessage.isEmpty());
6711 const QString msgType = receivedMessage.value("type").toString();
6712 if (msgType == "project-resolved") {
6713 receivedReply = true;
6714 const QJsonObject error = receivedMessage.value("error").toObject();
6715 if (!error.isEmpty())
6716 qDebug() << error;
6717 QVERIFY(error.isEmpty());
6718 const QJsonObject projectData = receivedMessage.value("project-data").toObject();
6719 QJsonArray products = projectData.value("products").toArray();
6720 bool file1 = false;
6721 bool file2 = false;
6722 for (const QJsonValue v : products) {
6723 const QJsonObject product = v.toObject();
6724 const QString productName = product.value("full-display-name").toString();
6725 const QJsonArray groups = product.value("groups").toArray();
6726 for (const QJsonValue &v : groups) {
6727 const QJsonObject group = v.toObject();
6728 const QString groupName = group.value("name").toString();
6729 const QJsonArray sourceArtifacts = group.value("source-artifacts").toArray();
6730 for (const QJsonValue &v : sourceArtifacts) {
6731 const QString filePath = v.toObject().value("file-path").toString();
6732 if (filePath.endsWith("file1.cpp")) {
6733 file1 = true;
6734 } else if (filePath.endsWith("file2.cpp")) {
6735 QCOMPARE(productName, QString("theLib"));
6736 QCOMPARE(groupName, QString("sources"));
6737 file2 = true;
6738 }
6739 }
6740 }
6741 }
6742 QVERIFY(!file1);
6743 QVERIFY(file2);
6744 }
6745 }
6746 QVERIFY(receivedReply);
6747
6748 receivedReply = false;
6749 receivedProcessResult = false;
6750 compiledFile1 = false;
6751 compiledFile2 = false;
6752 compiledMain = false;
6753 compiledLib = false;
6754 sendPacket(buildRequest);
6755 while (!receivedReply) {
6756 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6757 QVERIFY(!receivedMessage.isEmpty());
6758 const QString msgType = receivedMessage.value("type").toString();
6759 if (msgType == "project-built") {
6760 receivedReply = true;
6761 const QJsonObject error = receivedMessage.value("error").toObject();
6762 if (!error.isEmpty())
6763 qDebug() << error;
6764 QVERIFY(error.isEmpty());
6765 } else if (msgType == "command-description") {
6766 const QString msg = receivedMessage.value("message").toString();
6767 if (msg.contains("compiling file1.cpp"))
6768 compiledFile1 = true;
6769 else if (msg.contains("compiling file2.cpp"))
6770 compiledFile2 = true;
6771 else if (msg.contains("compiling main.cpp"))
6772 compiledMain = true;
6773 else if (msg.contains("compiling lib.cpp"))
6774 compiledLib = true;
6775 } else if (msgType == "process-result") {
6776 QCOMPARE(receivedMessage.value("exit-code").toInt(), 0);
6777 receivedProcessResult = true;
6778 }
6779 }
6780 QVERIFY(receivedReply);
6781 QVERIFY(receivedProcessResult);
6782 QVERIFY(!compiledFile1);
6783 QVERIFY(compiledFile2);
6784 QVERIFY(!compiledLib);
6785 QVERIFY(compiledMain);
6786
6787 // Get generated files.
6788 QJsonObject genFilesRequestPerFile;
6789 genFilesRequestPerFile.insert("source-file", QDir::currentPath() + "/main.cpp");
6790 genFilesRequestPerFile.insert("tags", QJsonArray{QJsonValue("obj")});
6791 QJsonObject genFilesRequestPerProduct;
6792 genFilesRequestPerProduct.insert("full-display-name", "theApp");
6793 genFilesRequestPerProduct.insert("requests", QJsonArray({genFilesRequestPerFile}));
6794 QJsonObject genFilesRequest;
6795 genFilesRequest.insert("type", "get-generated-files-for-sources");
6796 genFilesRequest.insert("products", QJsonArray({genFilesRequestPerProduct}));
6797 sendPacket(genFilesRequest);
6798 receivedReply = false;
6799 while (!receivedReply) {
6800 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6801 QCOMPARE(receivedMessage.value("type").toString(), QString("generated-files-for-sources"));
6802 const QJsonArray products = receivedMessage.value("products").toArray();
6803 QCOMPARE(products.size(), 1);
6804 const QJsonArray results = products.first().toObject().value("results").toArray();
6805 QCOMPARE(results.size(), 1);
6806 const QJsonObject result = results.first().toObject();
6807 QCOMPARE(result.value("source-file"), QDir::currentPath() + "/main.cpp");
6808 const QJsonArray generatedFiles = result.value("generated-files").toArray();
6809 QCOMPARE(generatedFiles.count(), 1);
6810 QCOMPARE(QFileInfo(generatedFiles.first().toString()).fileName(),
6811 objectFileName("main.cpp", profileName()));
6812 receivedReply = true;
6813 }
6814 QVERIFY(receivedReply);
6815
6816 // Release project.
6817 const QJsonObject releaseRequest{qMakePair(QString("type"), QJsonValue("release-project"))};
6818 sendPacket(releaseRequest);
6819 receivedReply = false;
6820 while (!receivedReply) {
6821 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6822 QCOMPARE(receivedMessage.value("type").toString(), QString("project-released"));
6823 const QJsonObject error = receivedMessage.value("error").toObject();
6824 if (!error.isEmpty())
6825 qDebug() << error;
6826 QVERIFY(error.isEmpty());
6827 receivedReply = true;
6828 }
6829 QVERIFY(receivedReply);
6830
6831 // Get build graph info.
6832 QJsonObject loadProjectMessage;
6833 loadProjectMessage.insert("type", "resolve-project");
6834 loadProjectMessage.insert("configuration-name", "my-config");
6835 loadProjectMessage.insert("build-root", QDir::currentPath());
6836 loadProjectMessage.insert("settings-dir", settings()->baseDirectory());
6837 loadProjectMessage.insert("restore-behavior", "restore-only");
6838 loadProjectMessage.insert("data-mode", "only-if-changed");
6839 sendPacket(loadProjectMessage);
6840 receivedReply = false;
6841 while (!receivedReply) {
6842 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6843 if (receivedMessage.value("type") != "project-resolved")
6844 continue;
6845 receivedReply = true;
6846 const QJsonObject error = receivedMessage.value("error").toObject();
6847 if (!error.isEmpty())
6848 qDebug() << error;
6849 QVERIFY(error.isEmpty());
6850 const QString bgFilePath = QDir::currentPath() + '/'
6851 + relativeBuildGraphFilePath("my-config");
6852 const QJsonObject projectData = receivedMessage.value("project-data").toObject();
6853 QCOMPARE(projectData.value("build-graph-file-path").toString(), bgFilePath);
6854 QCOMPARE(projectData.value("overridden-properties"), overriddenValues);
6855 }
6856 QVERIFY(receivedReply);
6857
6858 // Send unknown request.
6859 const QJsonObject unknownRequest({qMakePair(QString("type"), QJsonValue("blubb"))});
6860 sendPacket(unknownRequest);
6861 receivedReply = false;
6862 while (!receivedReply) {
6863 receivedMessage = getNextSessionPacket(sessionProc, incomingData);
6864 QCOMPARE(receivedMessage.value("type").toString(), QString("protocol-error"));
6865 receivedReply = true;
6866 }
6867 QVERIFY(receivedReply);
6868
6869 QJsonObject quitRequest;
6870 quitRequest.insert("type", "quit");
6871 sendPacket(quitRequest);
6872 QVERIFY(sessionProc.waitForFinished(3000));
6873 }
6874
radAfterIncompleteBuild_data()6875 void TestBlackbox::radAfterIncompleteBuild_data()
6876 {
6877 QTest::addColumn<QString>("projectFileName");
6878 QTest::newRow("Project with Rule") << "project_with_rule.qbs";
6879 QTest::newRow("Project with Transformer") << "project_with_transformer.qbs";
6880 }
6881
radAfterIncompleteBuild()6882 void TestBlackbox::radAfterIncompleteBuild()
6883 {
6884 QDir::setCurrent(testDataDir + "/rad-after-incomplete-build");
6885 rmDirR(relativeBuildDir());
6886 const QString projectFileName = "project_with_rule.qbs";
6887
6888 // Step 1: Have a directory where a file used to be.
6889 QbsRunParameters params(QStringList() << "-f" << projectFileName);
6890 QCOMPARE(runQbs(params), 0);
6891 WAIT_FOR_NEW_TIMESTAMP();
6892 REPLACE_IN_FILE(projectFileName, "oldfile", "oldfile/newfile");
6893 params.expectFailure = true;
6894 QVERIFY(runQbs(params) != 0);
6895 WAIT_FOR_NEW_TIMESTAMP();
6896 REPLACE_IN_FILE(projectFileName, "oldfile/newfile", "newfile");
6897 params.expectFailure = false;
6898 QCOMPARE(runQbs(params), 0);
6899 WAIT_FOR_NEW_TIMESTAMP();
6900 REPLACE_IN_FILE(projectFileName, "newfile", "oldfile/newfile");
6901 QCOMPARE(runQbs(params), 0);
6902
6903 // Step 2: Have a file where a directory used to be.
6904 WAIT_FOR_NEW_TIMESTAMP();
6905 REPLACE_IN_FILE(projectFileName, "oldfile/newfile", "oldfile");
6906 params.expectFailure = true;
6907 QVERIFY(runQbs(params) != 0);
6908 WAIT_FOR_NEW_TIMESTAMP();
6909 REPLACE_IN_FILE(projectFileName, "oldfile", "newfile");
6910 params.expectFailure = false;
6911 QCOMPARE(runQbs(params), 0);
6912 WAIT_FOR_NEW_TIMESTAMP();
6913 REPLACE_IN_FILE(projectFileName, "newfile", "oldfile");
6914 QCOMPARE(runQbs(params), 0);
6915 }
6916
subProfileChangeTracking()6917 void TestBlackbox::subProfileChangeTracking()
6918 {
6919 QDir::setCurrent(testDataDir + "/subprofile-change-tracking");
6920 const SettingsPtr s = settings();
6921 qbs::Internal::TemporaryProfile subProfile("qbs-autotests-subprofile", s.get());
6922 subProfile.p.setValue("baseProfile", profileName());
6923 subProfile.p.setValue("cpp.includePaths", QStringList("/tmp/include1"));
6924 s->sync();
6925 QCOMPARE(runQbs(), 0);
6926
6927 subProfile.p.setValue("cpp.includePaths", QStringList("/tmp/include2"));
6928 s->sync();
6929 QbsRunParameters params;
6930 params.command = "resolve";
6931 QCOMPARE(runQbs(params), 0);
6932 params.command = "build";
6933 QCOMPARE(runQbs(params), 0);
6934 QVERIFY(!m_qbsStdout.contains("main1.cpp"));
6935 QVERIFY(m_qbsStdout.contains("main2.cpp"));
6936 }
6937
successiveChanges()6938 void TestBlackbox::successiveChanges()
6939 {
6940 QDir::setCurrent(testDataDir + "/successive-changes");
6941 QCOMPARE(runQbs(), 0);
6942
6943 QbsRunParameters params("resolve", QStringList() << "products.theProduct.type:output,blubb");
6944 QCOMPARE(runQbs(params), 0);
6945 QCOMPARE(runQbs(), 0);
6946
6947 params.arguments << "project.version:2";
6948 QCOMPARE(runQbs(params), 0);
6949 QCOMPARE(runQbs(), 0);
6950 QFile output(relativeProductBuildDir("theProduct") + "/output.out");
6951 QVERIFY2(output.open(QIODevice::ReadOnly), qPrintable(output.errorString()));
6952 const QByteArray version = output.readAll();
6953 QCOMPARE(version.constData(), "2");
6954 }
6955
installedApp()6956 void TestBlackbox::installedApp()
6957 {
6958 QDir::setCurrent(testDataDir + "/installed_artifact");
6959
6960 QCOMPARE(runQbs(), 0);
6961 QVERIFY(regularFileExists(defaultInstallRoot
6962 + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/bin/installedApp"))));
6963
6964 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("qbs.installRoot:" + testDataDir
6965 + "/installed-app"))), 0);
6966 QCOMPARE(runQbs(), 0);
6967 QVERIFY(regularFileExists(testDataDir
6968 + HostOsInfo::appendExecutableSuffix("/installed-app/usr/bin/installedApp")));
6969
6970 QFile addedFile(defaultInstallRoot + QLatin1String("/blubb.txt"));
6971 QVERIFY(addedFile.open(QIODevice::WriteOnly));
6972 addedFile.close();
6973 QVERIFY(addedFile.exists());
6974 QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
6975 QCOMPARE(runQbs(QbsRunParameters(QStringList() << "--clean-install-root")), 0);
6976 QVERIFY(regularFileExists(defaultInstallRoot
6977 + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/bin/installedApp"))));
6978 QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/src/main.cpp")));
6979 QVERIFY(!addedFile.exists());
6980
6981 // Check whether changing install parameters on the product causes re-installation.
6982 WAIT_FOR_NEW_TIMESTAMP();
6983 REPLACE_IN_FILE("installed_artifact.qbs", "qbs.installPrefix: \"/usr\"",
6984 "qbs.installPrefix: '/usr/local'");
6985 QCOMPARE(runQbs(), 0);
6986 QVERIFY(regularFileExists(defaultInstallRoot
6987 + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/local/bin/installedApp"))));
6988 QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/local/src/main.cpp")));
6989
6990 // Check whether changing install parameters on the artifact causes re-installation.
6991 WAIT_FOR_NEW_TIMESTAMP();
6992 REPLACE_IN_FILE("installed_artifact.qbs", "qbs.installDir: \"bin\"",
6993 "qbs.installDir: 'custom'");
6994 QCOMPARE(runQbs(), 0);
6995 QVERIFY(regularFileExists(defaultInstallRoot
6996 + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/local/custom/installedApp"))));
6997
6998 // Check whether changing install parameters on a source file causes re-installation.
6999 WAIT_FOR_NEW_TIMESTAMP();
7000 REPLACE_IN_FILE("installed_artifact.qbs", "qbs.installDir: \"src\"",
7001 "qbs.installDir: 'source'");
7002 QCOMPARE(runQbs(), 0);
7003 QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/local/source/main.cpp")));
7004
7005 // Check whether changing install parameters on the command line causes re-installation.
7006 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("qbs.installRoot:" + relativeBuildDir()
7007 + "/blubb"))), 0);
7008 QCOMPARE(runQbs(), 0);
7009 QVERIFY(regularFileExists(relativeBuildDir() + "/blubb/usr/local/source/main.cpp"));
7010
7011 // Check --no-install
7012 rmDirR(relativeBuildDir());
7013 QCOMPARE(runQbs(QbsRunParameters(QStringList() << "--no-install")), 0);
7014 QCOMPARE(QDir(defaultInstallRoot).entryList(QDir::NoDotAndDotDot).size(), 0);
7015
7016 // Check --no-build (with and without an existing build graph)
7017 QbsRunParameters params("install", QStringList() << "--no-build");
7018 QCOMPARE(runQbs(params), 0);
7019 rmDirR(relativeBuildDir());
7020 params.expectFailure = true;
7021 QVERIFY(runQbs(params) != 0);
7022 QVERIFY2(m_qbsStderr.contains("Build graph not found"), m_qbsStderr.constData());
7023 }
7024
installDuplicates()7025 void TestBlackbox::installDuplicates()
7026 {
7027 QDir::setCurrent(testDataDir + "/install-duplicates");
7028
7029 QbsRunParameters params;
7030 params.expectFailure = true;
7031 QVERIFY(runQbs(params) != 0);
7032 QVERIFY(m_qbsStderr.contains("Cannot install files"));
7033 }
7034
installDuplicatesNoError()7035 void TestBlackbox::installDuplicatesNoError()
7036 {
7037 QDir::setCurrent(testDataDir + "/install-duplicates-no-error");
7038
7039 QbsRunParameters params;
7040 QCOMPARE(runQbs(params), 0);
7041 }
7042
installedSourceFiles()7043 void TestBlackbox::installedSourceFiles()
7044 {
7045 QDir::setCurrent(testDataDir + "/installed-source-files");
7046
7047 QCOMPARE(runQbs(), 0);
7048 QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/readme.txt")));
7049 QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/main.cpp")));
7050 }
7051
toolLookup()7052 void TestBlackbox::toolLookup()
7053 {
7054 QbsRunParameters params(QStringLiteral("setup-toolchains"), QStringList("--help"));
7055 params.profile.clear();
7056 QCOMPARE(runQbs(params), 0);
7057 }
7058
topLevelSearchPath()7059 void TestBlackbox::topLevelSearchPath()
7060 {
7061 QDir::setCurrent(testDataDir + "/toplevel-searchpath");
7062
7063 QbsRunParameters params;
7064 params.expectFailure = true;
7065 QVERIFY(runQbs(params) != 0);
7066 QVERIFY2(m_qbsStderr.contains("MyProduct"), m_qbsStderr.constData());
7067 params.arguments << ("project.qbsSearchPaths:" + QDir::currentPath() + "/qbs-resources");
7068 QCOMPARE(runQbs(params), 0);
7069 }
7070
checkProjectFilePath()7071 void TestBlackbox::checkProjectFilePath()
7072 {
7073 QDir::setCurrent(testDataDir + "/project_filepath_check");
7074 QbsRunParameters params(QStringList("-f") << "project1.qbs");
7075 QCOMPARE(runQbs(params), 0);
7076 QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData());
7077 QCOMPARE(runQbs(params), 0);
7078
7079 params.arguments = QStringList("-f") << "project2.qbs";
7080 params.expectFailure = true;
7081 QVERIFY(runQbs(params) != 0);
7082 QVERIFY(m_qbsStderr.contains("project file"));
7083
7084 params.arguments = QStringList("-f") << "project2.qbs";
7085 params.command = "resolve";
7086 params.expectFailure = false;
7087 QCOMPARE(runQbs(params), 0);
7088 params.command = "build";
7089 QCOMPARE(runQbs(params), 0);
7090 QVERIFY2(m_qbsStdout.contains("main2.cpp"), m_qbsStdout.constData());
7091 }
7092
checkTimestamps()7093 void TestBlackbox::checkTimestamps()
7094 {
7095 QDir::setCurrent(testDataDir + "/check-timestamps");
7096 QCOMPARE(runQbs(), 0);
7097 QVERIFY2(m_qbsStdout.contains("compiling file.cpp"), m_qbsStdout.constData());
7098 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
7099 QVERIFY(QFile::remove(relativeBuildGraphFilePath()));
7100 WAIT_FOR_NEW_TIMESTAMP();
7101 touch("file.h");
7102 QCOMPARE(runQbs(QStringList("--check-timestamps")), 0);
7103 QVERIFY2(m_qbsStdout.contains("compiling file.cpp"), m_qbsStdout.constData());
7104 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
7105 }
7106
chooseModuleInstanceByPriority()7107 void TestBlackbox::chooseModuleInstanceByPriority()
7108 {
7109 QFETCH(QString, idol);
7110 QFETCH(QStringList, expectedSubStrings);
7111 QFETCH(bool, expectSuccess);
7112 QDir::setCurrent(testDataDir + "/choose-module-instance");
7113 rmDirR(relativeBuildDir());
7114 QbsRunParameters params(QStringList("modules.qbs.targetPlatform:" + idol));
7115 params.expectFailure = !expectSuccess;
7116 if (expectSuccess) {
7117 QCOMPARE(runQbs(params), 0);
7118 } else {
7119 QVERIFY(runQbs(params) != 0);
7120 return;
7121 }
7122
7123 const QString installRoot = relativeBuildDir() + "/install-root/";
7124 QVERIFY(QFile::exists(installRoot + "/gerbil.txt"));
7125 QFile file(installRoot + "/gerbil.txt");
7126 QVERIFY(file.open(QIODevice::ReadOnly));
7127 const QString content = QString::fromUtf8(file.readAll());
7128 for (const auto &str : expectedSubStrings) {
7129 if (content.contains(str))
7130 continue;
7131 qDebug() << "content:" << content;
7132 qDebug() << "substring:" << str;
7133 QFAIL("missing substring");
7134 }
7135 }
7136
chooseModuleInstanceByPriority_data()7137 void TestBlackbox::chooseModuleInstanceByPriority_data()
7138 {
7139 QTest::addColumn<QString>("idol");
7140 QTest::addColumn<QStringList>("expectedSubStrings");
7141 QTest::addColumn<bool>("expectSuccess");
7142 QTest::newRow("ringo")
7143 << "Beatles" << QStringList() << false;
7144 QTest::newRow("ritchie1")
7145 << "Deep Purple" << QStringList{"slipped", "litchi", "ritchie"} << true;
7146 QTest::newRow("ritchie2")
7147 << "Rainbow" << QStringList{"slipped", "litchi", "ritchie"} << true;
7148 QTest::newRow("lord")
7149 << "Whitesnake" << QStringList{"chewed", "cord", "lord"} << true;
7150 }
7151
7152 class TemporaryDefaultProfileRemover
7153 {
7154 public:
TemporaryDefaultProfileRemover(qbs::Settings * settings)7155 TemporaryDefaultProfileRemover(qbs::Settings *settings)
7156 : m_settings(settings), m_defaultProfile(settings->defaultProfile())
7157 {
7158 m_settings->remove(QStringLiteral("defaultProfile"));
7159 }
7160
~TemporaryDefaultProfileRemover()7161 ~TemporaryDefaultProfileRemover()
7162 {
7163 if (!m_defaultProfile.isEmpty())
7164 m_settings->setValue(QStringLiteral("defaultProfile"), m_defaultProfile);
7165 }
7166
7167 private:
7168 qbs::Settings *m_settings;
7169 const QString m_defaultProfile;
7170 };
7171
assembly()7172 void TestBlackbox::assembly()
7173 {
7174 QDir::setCurrent(testDataDir + "/assembly");
7175 QVERIFY(runQbs() == 0);
7176
7177 const QVariantMap properties = ([&]() {
7178 QFile propertiesFile(relativeProductBuildDir("assembly") + "/properties.json");
7179 if (propertiesFile.open(QIODevice::ReadOnly))
7180 return QJsonDocument::fromJson(propertiesFile.readAll()).toVariant().toMap();
7181 return QVariantMap{};
7182 })();
7183 QVERIFY(!properties.empty());
7184 const auto toolchain = properties.value("qbs.toolchain").toStringList();
7185 QVERIFY(!toolchain.empty());
7186 const bool haveGcc = toolchain.contains("gcc");
7187 const bool haveMSVC = toolchain.contains("msvc");
7188
7189 QCOMPARE(m_qbsStdout.contains("assembling testa.s"), haveGcc);
7190 QCOMPARE(m_qbsStdout.contains("compiling testb.S"), haveGcc);
7191 QCOMPARE(m_qbsStdout.contains("compiling testc.sx"), haveGcc);
7192 QCOMPARE(m_qbsStdout.contains("creating libtesta.a"), haveGcc);
7193 QCOMPARE(m_qbsStdout.contains("creating libtestb.a"), haveGcc);
7194 QCOMPARE(m_qbsStdout.contains("creating libtestc.a"), haveGcc);
7195 QCOMPARE(m_qbsStdout.contains("creating testd.lib"), haveMSVC);
7196 }
7197
autotestWithDependencies()7198 void TestBlackbox::autotestWithDependencies()
7199 {
7200 QDir::setCurrent(testDataDir + "/autotest-with-dependencies");
7201 QCOMPARE(runQbs({"resolve"}), 0);
7202 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7203 QSKIP("Cannot run binaries in cross-compiled build");
7204 QCOMPARE(runQbs(QStringList({"-p", "autotest-runner"})), 0);
7205 QVERIFY2(m_qbsStdout.contains("i am the test app")
7206 && m_qbsStdout.contains("i am the helper"), m_qbsStdout.constData());
7207 }
7208
autotestTimeout()7209 void TestBlackbox::autotestTimeout()
7210 {
7211 QFETCH(QStringList, resolveParams);
7212 QFETCH(bool, expectFailure);
7213 QDir::setCurrent(testDataDir + "/autotest-timeout");
7214 QbsRunParameters resolveParameters("resolve", resolveParams);
7215 QCOMPARE(runQbs(resolveParameters), 0);
7216 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7217 QSKIP("Cannot run binaries in cross-compiled build");
7218 QbsRunParameters buildParameters(QStringList({"-p", "autotest-runner"}));
7219 buildParameters.expectFailure = expectFailure;
7220 if (expectFailure) {
7221 QVERIFY(runQbs(buildParameters) != 0);
7222 QVERIFY(m_qbsStderr.contains("cancelled") && m_qbsStderr.contains("timeout"));
7223 }
7224 else
7225 QVERIFY(runQbs(buildParameters) == 0);
7226 }
7227
autotestTimeout_data()7228 void TestBlackbox::autotestTimeout_data()
7229 {
7230 QTest::addColumn<QStringList>("resolveParams");
7231 QTest::addColumn<bool>("expectFailure");
7232 QTest::newRow("no timeout") << QStringList() << false;
7233 QTest::newRow("timeout on test") << QStringList({"products.testApp.autotest.timeout:2"})
7234 << true;
7235 QTest::newRow("timeout on runner") << QStringList({"products.autotest-runner.timeout:2"})
7236 << true;
7237 }
7238
autotests_data()7239 void TestBlackbox::autotests_data()
7240 {
7241 QTest::addColumn<QString>("evilPropertySpec");
7242 QTest::addColumn<QByteArray>("expectedErrorMessage");
7243 QTest::newRow("missing arguments") << QString("products.test1.autotest.arguments:[]")
7244 << QByteArray("This test needs exactly one argument");
7245 QTest::newRow("missing working dir") << QString("products.test2.autotest.workingDir:''")
7246 << QByteArray("Test resource not found");
7247 QTest::newRow("missing flaky specifier")
7248 << QString("products.test3.autotest.allowFailure:false")
7249 << QByteArray("I am an awful test");
7250 QTest::newRow("everything's fine") << QString() << QByteArray();
7251 }
7252
autotests()7253 void TestBlackbox::autotests()
7254 {
7255 QDir::setCurrent(testDataDir + "/autotests");
7256 QFETCH(QString, evilPropertySpec);
7257 QFETCH(QByteArray, expectedErrorMessage);
7258 QbsRunParameters resolveParams("resolve");
7259 if (!evilPropertySpec.isEmpty())
7260 resolveParams.arguments << evilPropertySpec;
7261 QCOMPARE(runQbs(resolveParams), 0);
7262 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7263 QSKIP("Cannot run binaries in cross-compiled build");
7264 QbsRunParameters testParams(QStringList{"-p", "autotest-runner"});
7265 if (!evilPropertySpec.isEmpty())
7266 testParams.expectFailure = true;
7267 QCOMPARE(runQbs(testParams) == 0, !testParams.expectFailure);
7268 if (testParams.expectFailure) {
7269 QVERIFY2(m_qbsStderr.contains(expectedErrorMessage), m_qbsStderr.constData());
7270 return;
7271 }
7272 QVERIFY2(m_qbsStdout.contains("Running test test1")
7273 && m_qbsStdout.contains("Running test test2")
7274 && m_qbsStdout.contains("Running test test3"), m_qbsStdout.constData());
7275 QCOMPARE(m_qbsStdout.count("PASS"), 2);
7276 QCOMPARE(m_qbsStderr.count("FAIL"), 1);
7277 }
7278
auxiliaryInputsFromDependencies()7279 void TestBlackbox::auxiliaryInputsFromDependencies()
7280 {
7281 QDir::setCurrent(testDataDir + "/aux-inputs-from-deps");
7282 QCOMPARE(runQbs(), 0);
7283 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
7284 QVERIFY2(m_qbsStdout.contains("generating dummy.out"), m_qbsStdout.constData());
7285 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.dep.sleep:false"))), 0);
7286 WAIT_FOR_NEW_TIMESTAMP();
7287 QCOMPARE(runQbs(), 0);
7288 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
7289 QVERIFY2(m_qbsStdout.contains("generating dummy.out"), m_qbsStdout.constData());
7290 }
7291
explicitlyDependsOn()7292 void TestBlackbox::explicitlyDependsOn()
7293 {
7294 QFETCH(QString, useExplicitlyDependsOn);
7295 QFETCH(QString, useExplicitlyDependsOnFromDependencies);
7296 QFETCH(QString, useModule);
7297 QFETCH(bool, expectFailure);
7298
7299 QDir::setCurrent(testDataDir + "/explicitly-depends-on");
7300 QbsRunParameters params("",
7301 QStringList("products.prod1.useExplicitlyDependsOn:" + useExplicitlyDependsOn)
7302 << "products.prod1.useExplicitlyDependsOnFromDependencies:"
7303 + useExplicitlyDependsOnFromDependencies
7304 << "projects.proj1.useModule:"
7305 + useModule);
7306 params.expectFailure = expectFailure;
7307
7308 rmDirR(relativeBuildDir());
7309
7310 if (params.expectFailure) {
7311 // Build should fail because a rule cycle is created within the product when
7312 // explicitlyDependsOn is used.
7313 QVERIFY(runQbs(params) != 0);
7314 QVERIFY2(m_qbsStderr.contains("Cycle detected in rule dependencies"),
7315 m_qbsStderr.constData());
7316 } else {
7317 // When explicitlyDependsOnFromDependencies is used, build should succeed due to the
7318 // "final" tag being pulled in from dependencies.
7319 QCOMPARE(runQbs(params), 0);
7320
7321 if (useModule == QLatin1String("false")) {
7322 QVERIFY2(m_qbsStdout.contains("creating 'product-fish.txt' tagged with 'final'"),
7323 m_qbsStdout.constData());
7324 QVERIFY2(m_qbsStdout.contains("Using explicitlyDependsOnArtifact: product-fish.txt"),
7325 m_qbsStdout.constData());
7326 QVERIFY2(m_qbsStdout.contains("step1 -> step2"), m_qbsStdout.constData());
7327 QVERIFY2(m_qbsStdout.contains("step2 -> step3"), m_qbsStdout.constData());
7328 QVERIFY2(m_qbsStdout.contains("step3 -> final"), m_qbsStdout.constData());
7329 } else {
7330 QVERIFY2(!m_qbsStdout.contains("creating 'product-fish.txt' tagged with 'final'"),
7331 m_qbsStdout.constData());
7332 QVERIFY2(m_qbsStdout.contains("Using explicitlyDependsOnArtifact: module-fish.txt"),
7333 m_qbsStdout.constData());
7334 QVERIFY2(m_qbsStdout.contains("step1 -> step2"), m_qbsStdout.constData());
7335 QVERIFY2(m_qbsStdout.contains("step2 -> step3"), m_qbsStdout.constData());
7336 QVERIFY2(m_qbsStdout.contains("step3 -> final"), m_qbsStdout.constData());
7337 }
7338 }
7339 }
7340
explicitlyDependsOn_data()7341 void TestBlackbox::explicitlyDependsOn_data()
7342 {
7343 QTest::addColumn<QString>("useExplicitlyDependsOn");
7344 QTest::addColumn<QString>("useExplicitlyDependsOnFromDependencies");
7345 QTest::addColumn<QString>("useModule");
7346 QTest::addColumn<bool>("expectFailure");
7347
7348 QTest::newRow("useExplicitlyDependsOn -> causes cycle")
7349 << "true" << "false" << "false" << true;
7350 QTest::newRow("explicitlyDependsOnFromDependencies + product")
7351 << "false" << "true" << "false" << false;
7352 QTest::newRow("explicitlyDependsOnFromDependencies + module + filesAreTargets")
7353 << "false" << "true" << "true" << false;
7354 }
7355
haveMakeNsis()7356 static bool haveMakeNsis()
7357 {
7358 QStringList regKeys;
7359 regKeys << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS")
7360 << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS");
7361
7362 QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH")
7363 .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
7364
7365 for (const QString &key : qAsConst(regKeys)) {
7366 QSettings settings(key, QSettings::NativeFormat);
7367 QString str = settings.value(QStringLiteral(".")).toString();
7368 if (!str.isEmpty())
7369 paths.prepend(str);
7370 }
7371
7372 bool haveMakeNsis = false;
7373 for (const QString &path : qAsConst(paths)) {
7374 if (regularFileExists(QDir::fromNativeSeparators(path) +
7375 HostOsInfo::appendExecutableSuffix(QStringLiteral("/makensis")))) {
7376 haveMakeNsis = true;
7377 break;
7378 }
7379 }
7380
7381 return haveMakeNsis;
7382 }
7383
nsis()7384 void TestBlackbox::nsis()
7385 {
7386 if (!haveMakeNsis()) {
7387 QSKIP("makensis is not installed");
7388 return;
7389 }
7390
7391 bool targetIsWindows = targetOs() == HostOsInfo::HostOsWindows;
7392 QDir::setCurrent(testDataDir + "/nsis");
7393 QVERIFY(runQbs() == 0);
7394 QCOMPARE((bool)m_qbsStdout.contains("compiling hello.nsi"), targetIsWindows);
7395 QCOMPARE((bool)m_qbsStdout.contains("SetCompressor ignored due to previous call with the /FINAL switch"), targetIsWindows);
7396 QVERIFY(!QFile::exists(defaultInstallRoot + "/you-should-not-see-a-file-with-this-name.exe"));
7397 }
7398
nsisDependencies()7399 void TestBlackbox::nsisDependencies()
7400 {
7401 if (!haveMakeNsis()) {
7402 QSKIP("makensis is not installed");
7403 return;
7404 }
7405
7406 bool targetIsWindows = targetOs() == HostOsInfo::HostOsWindows;
7407 QDir::setCurrent(testDataDir + "/nsisDependencies");
7408 QCOMPARE(runQbs(), 0);
7409 QCOMPARE(m_qbsStdout.contains("compiling hello.nsi"), targetIsWindows);
7410 }
7411
outOfDateMarking()7412 void TestBlackbox::outOfDateMarking()
7413 {
7414 QDir::setCurrent(testDataDir + "/out-of-date-marking");
7415 for (int i = 0; i < 25; ++i) {
7416 QCOMPARE(runQbs(), 0);
7417 QVERIFY2(m_qbsStdout.contains("generating myheader.h"), qPrintable(QString::number(i)));
7418 QVERIFY2(m_qbsStdout.contains("compiling main.c"), qPrintable(QString::number(i)));
7419 }
7420 }
7421
enableExceptions()7422 void TestBlackbox::enableExceptions()
7423 {
7424 QFETCH(QString, file);
7425 QFETCH(bool, enable);
7426 QFETCH(bool, expectSuccess);
7427
7428 QDir::setCurrent(testDataDir + QStringLiteral("/enableExceptions"));
7429
7430 QbsRunParameters params;
7431 params.arguments = QStringList() << "-f" << file
7432 << (QStringLiteral("modules.cpp.enableExceptions:")
7433 + (enable ? "true" : "false"));
7434 params.expectFailure = !expectSuccess;
7435 rmDirR(relativeBuildDir());
7436 if (!params.expectFailure)
7437 QCOMPARE(runQbs(params), 0);
7438 else
7439 QVERIFY(runQbs(params) != 0);
7440 }
7441
enableExceptions_data()7442 void TestBlackbox::enableExceptions_data()
7443 {
7444 QTest::addColumn<QString>("file");
7445 QTest::addColumn<bool>("enable");
7446 QTest::addColumn<bool>("expectSuccess");
7447
7448 QTest::newRow("no exceptions, enabled") << "none.qbs" << true << true;
7449 QTest::newRow("no exceptions, disabled") << "none.qbs" << false << true;
7450
7451 QTest::newRow("C++ exceptions, enabled") << "exceptions.qbs" << true << true;
7452 QTest::newRow("C++ exceptions, disabled") << "exceptions.qbs" << false << false;
7453
7454 if (HostOsInfo::isMacosHost()) {
7455 QTest::newRow("Objective-C exceptions, enabled") << "exceptions-objc.qbs" << true << true;
7456 QTest::newRow("Objective-C exceptions in Objective-C++ source, enabled") << "exceptions-objcpp.qbs" << true << true;
7457 QTest::newRow("C++ exceptions in Objective-C++ source, enabled") << "exceptions-objcpp-cpp.qbs" << true << true;
7458 QTest::newRow("Objective-C, disabled") << "exceptions-objc.qbs" << false << false;
7459 QTest::newRow("Objective-C exceptions in Objective-C++ source, disabled") << "exceptions-objcpp.qbs" << false << false;
7460 QTest::newRow("C++ exceptions in Objective-C++ source, disabled") << "exceptions-objcpp-cpp.qbs" << false << false;
7461 }
7462 }
7463
enableRtti()7464 void TestBlackbox::enableRtti()
7465 {
7466 QDir::setCurrent(testDataDir + QStringLiteral("/enableRtti"));
7467
7468 QbsRunParameters params;
7469
7470 params.arguments = QStringList() << "modules.cpp.enableRtti:true";
7471 rmDirR(relativeBuildDir());
7472 QCOMPARE(runQbs(params), 0);
7473
7474 if (HostOsInfo::isMacosHost()) {
7475 params.arguments = QStringList() << "modules.cpp.enableRtti:true"
7476 << "project.treatAsObjcpp:true";
7477 rmDirR(relativeBuildDir());
7478 QCOMPARE(runQbs(params), 0);
7479 }
7480
7481 params.expectFailure = true;
7482
7483 params.arguments = QStringList() << "modules.cpp.enableRtti:false";
7484 rmDirR(relativeBuildDir());
7485 QVERIFY(runQbs(params) != 0);
7486
7487 if (HostOsInfo::isMacosHost()) {
7488 params.arguments = QStringList() << "modules.cpp.enableRtti:false"
7489 << "project.treatAsObjcpp:true";
7490 rmDirR(relativeBuildDir());
7491 QVERIFY(runQbs(params) != 0);
7492 }
7493 }
7494
envMerging()7495 void TestBlackbox::envMerging()
7496 {
7497 QDir::setCurrent(testDataDir + "/env-merging");
7498 QbsRunParameters params("resolve");
7499 QString pathVal = params.environment.value("PATH");
7500 pathVal.prepend(HostOsInfo::pathListSeparator()).prepend("/opt/blackbox/bin");
7501 const QString keyName = HostOsInfo::isWindowsHost() ? "pATh" : "PATH";
7502 params.environment.insert(keyName, pathVal);
7503 QCOMPARE(runQbs(params), 0);
7504 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7505 QSKIP("Cannot run binaries in cross-compiled build");
7506 params.command = "build";
7507 QCOMPARE(runQbs(params), 0);
7508 QVERIFY2(m_qbsStdout.contains(QByteArray("PATH=/opt/tool/bin")
7509 + HostOsInfo::pathListSeparator().toLatin1())
7510 && m_qbsStdout.contains(HostOsInfo::pathListSeparator().toLatin1()
7511 + QByteArray("/opt/blackbox/bin")),
7512 m_qbsStdout.constData());
7513 }
7514
envNormalization()7515 void TestBlackbox::envNormalization()
7516 {
7517 QDir::setCurrent(testDataDir + "/env-normalization");
7518 QbsRunParameters params;
7519 params.environment.insert("myvar", "x");
7520 QCOMPARE(runQbs(params), 0);
7521 if (HostOsInfo::isWindowsHost())
7522 QVERIFY2(m_qbsStdout.contains("\"MYVAR\":\"x\""), m_qbsStdout.constData());
7523 else
7524 QVERIFY2(m_qbsStdout.contains("\"myvar\":\"x\""), m_qbsStdout.constData());
7525 }
7526
generatedArtifactAsInputToDynamicRule()7527 void TestBlackbox::generatedArtifactAsInputToDynamicRule()
7528 {
7529 QDir::setCurrent(testDataDir + "/generated-artifact-as-input-to-dynamic-rule");
7530 QCOMPARE(runQbs(), 0);
7531 const QString oldFile = relativeProductBuildDir("p") + "/old.txt";
7532 QVERIFY2(regularFileExists(oldFile), qPrintable(oldFile));
7533 WAIT_FOR_NEW_TIMESTAMP();
7534 QFile inputFile("input.txt");
7535 QVERIFY2(inputFile.open(QIODevice::WriteOnly), qPrintable(inputFile.errorString()));
7536 inputFile.resize(0);
7537 inputFile.write("new.txt");
7538 inputFile.close();
7539 QCOMPARE(runQbs(), 0);
7540 QVERIFY2(!regularFileExists(oldFile), qPrintable(oldFile));
7541 const QString newFile = relativeProductBuildDir("p") + "/new.txt";
7542 QVERIFY2(regularFileExists(newFile), qPrintable(oldFile));
7543 QVERIFY2(m_qbsStdout.contains("generating"), m_qbsStdout.constData());
7544 QCOMPARE(runQbs(), 0);
7545 QVERIFY2(!m_qbsStdout.contains("generating"), m_qbsStdout.constData());
7546 }
7547
generateLinkerMapFile()7548 void TestBlackbox::generateLinkerMapFile()
7549 {
7550 QDir::setCurrent(testDataDir + "/generate-linker-map-file");
7551 QCOMPARE(runQbs(), 0);
7552 const bool isUsed = m_qbsStdout.contains("use test: true");
7553 const bool isNotUsed = m_qbsStdout.contains("use test: false");
7554 QVERIFY(isUsed != isNotUsed);
7555 if (isUsed) {
7556 QVERIFY(QFile::exists(relativeProductBuildDir("app-map")
7557 + "/app-map.map"));
7558 QVERIFY(!QFile::exists(relativeProductBuildDir("app-nomap")
7559 + "/app-nomap.map"));
7560 QVERIFY(!QFile::exists(relativeProductBuildDir("app-nomap-default")
7561 + "/app-nomap-default.map"));
7562 } else {
7563 QSKIP("Unsupported toolchain. Skipping.");
7564 }
7565 }
7566
generator()7567 void TestBlackbox::generator()
7568 {
7569 QFETCH(QString, inputFile);
7570 QFETCH(QStringList, toBeCompiled);
7571 QDir::setCurrent(testDataDir + "/generator");
7572 if (!inputFile.isEmpty()) {
7573 WAIT_FOR_NEW_TIMESTAMP();
7574 QFile input(inputFile);
7575 QFile output("input.txt");
7576 QVERIFY2(!output.exists() || output.remove(), qPrintable(output.errorString()));
7577 QVERIFY2(input.copy(output.fileName()), qPrintable(input.errorString()));
7578 touch(output.fileName());
7579 }
7580 QCOMPARE(runQbs(), 0);
7581 QCOMPARE(toBeCompiled.contains("main.cpp"), m_qbsStdout.contains("compiling main.cpp"));
7582 QCOMPARE(toBeCompiled.contains("file1.cpp"), m_qbsStdout.contains("compiling file1.cpp"));
7583 QCOMPARE(toBeCompiled.contains("file2.cpp"), m_qbsStdout.contains("compiling file2.cpp"));
7584 }
7585
generator_data()7586 void TestBlackbox::generator_data()
7587 {
7588 QTest::addColumn<QString>("inputFile");
7589 QTest::addColumn<QStringList>("toBeCompiled");
7590 QTest::newRow("both") << "input.both.txt" << QStringList{"main.cpp", "file1.cpp", "file2.cpp"};
7591 QTest::newRow("file1") << "input.file1.txt" << QStringList{"file1.cpp"};
7592 QTest::newRow("file2") << "input.file2.txt" << QStringList{"file2.cpp"};
7593 QTest::newRow("none") << "input.none.txt" << QStringList();
7594 QTest::newRow("both again") << "input.both.txt" << QStringList{"file1.cpp", "file2.cpp"};
7595 QTest::newRow("no update") << QString() << QStringList();
7596 }
7597
nodejs()7598 void TestBlackbox::nodejs()
7599 {
7600 const SettingsPtr s = settings();
7601 Profile p(profileName(), s.get());
7602
7603 int status;
7604 findNodejs(&status);
7605 QCOMPARE(status, 0);
7606
7607 QDir::setCurrent(testDataDir + QLatin1String("/nodejs"));
7608
7609 status = runQbs();
7610 if (p.value("nodejs.toolchainInstallPath").toString().isEmpty()
7611 && status != 0 && m_qbsStderr.contains("toolchainInstallPath")) {
7612 QSKIP("nodejs.toolchainInstallPath not set and automatic detection failed");
7613 }
7614
7615 if (p.value("nodejs.packageManagerPrefixPath").toString().isEmpty()
7616 && status != 0 && m_qbsStderr.contains("nodejs.packageManagerPrefixPath")) {
7617 QSKIP("nodejs.packageManagerFilePath not set and automatic detection failed");
7618 }
7619
7620 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7621 QSKIP("Cannot run binaries in cross-compiled build");
7622 QCOMPARE(status, 0);
7623
7624 QbsRunParameters params;
7625 params.command = QLatin1String("run");
7626 QCOMPARE(runQbs(params), 0);
7627 QVERIFY((bool)m_qbsStdout.contains("hello world"));
7628 QVERIFY(regularFileExists(relativeProductBuildDir("hello") + "/hello.js"));
7629 }
7630
typescript()7631 void TestBlackbox::typescript()
7632 {
7633 if (qEnvironmentVariableIsSet("GITHUB_ACTIONS"))
7634 QSKIP("Skip this test when running on GitHub");
7635
7636 const SettingsPtr s = settings();
7637 Profile p(profileName(), s.get());
7638
7639 int status;
7640 findTypeScript(&status);
7641 QCOMPARE(status, 0);
7642
7643 QDir::setCurrent(testDataDir + QLatin1String("/typescript"));
7644
7645 QbsRunParameters params;
7646 params.expectFailure = true;
7647 status = runQbs(params);
7648 if (p.value("typescript.toolchainInstallPath").toString().isEmpty() && status != 0) {
7649 if (m_qbsStderr.contains("Path\" must be specified"))
7650 QSKIP("typescript probe failed");
7651 if (m_qbsStderr.contains("typescript.toolchainInstallPath"))
7652 QSKIP("typescript.toolchainInstallPath not set and automatic detection failed");
7653 if (m_qbsStderr.contains("nodejs.interpreterFilePath"))
7654 QSKIP("nodejs.interpreterFilePath not set and automatic detection failed");
7655 }
7656
7657 if (status != 0)
7658 qDebug() << m_qbsStderr;
7659 QCOMPARE(status, 0);
7660
7661 params.expectFailure = false;
7662 params.command = QStringLiteral("run");
7663 params.arguments = QStringList() << "-p" << "animals";
7664 QCOMPARE(runQbs(params), 0);
7665
7666 QVERIFY(regularFileExists(relativeProductBuildDir("animals") + "/animals.js"));
7667 QVERIFY(regularFileExists(relativeProductBuildDir("animals") + "/extra.js"));
7668 QVERIFY(regularFileExists(relativeProductBuildDir("animals") + "/main.js"));
7669 }
7670
undefinedTargetPlatform()7671 void TestBlackbox::undefinedTargetPlatform()
7672 {
7673 QDir::setCurrent(testDataDir + "/undefined-target-platform");
7674 QCOMPARE(runQbs(), 0);
7675 }
7676
importInPropertiesCondition()7677 void TestBlackbox::importInPropertiesCondition()
7678 {
7679 QDir::setCurrent(testDataDir + "/import-in-properties-condition");
7680 QCOMPARE(runQbs(), 0);
7681 }
7682
importSearchPath()7683 void TestBlackbox::importSearchPath()
7684 {
7685 QDir::setCurrent(testDataDir + "/import-searchpath");
7686 QCOMPARE(runQbs(), 0);
7687 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
7688 QVERIFY2(!m_qbsStdout.contains("compiling somefile.cpp"), m_qbsStdout.constData());
7689 }
7690
importingProduct()7691 void TestBlackbox::importingProduct()
7692 {
7693 QDir::setCurrent(testDataDir + "/importing-product");
7694 QCOMPARE(runQbs(), 0);
7695 }
7696
importsConflict()7697 void TestBlackbox::importsConflict()
7698 {
7699 QDir::setCurrent(testDataDir + "/imports-conflict");
7700 QCOMPARE(runQbs(), 0);
7701 }
7702
includeLookup()7703 void TestBlackbox::includeLookup()
7704 {
7705 QDir::setCurrent(testDataDir + "/includeLookup");
7706 QCOMPARE(runQbs({"resolve"}), 0);
7707 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7708 QSKIP("Cannot run binaries in cross-compiled build");
7709 QbsRunParameters params;
7710 params.command = "run";
7711 QCOMPARE(runQbs(params), 0);
7712 QVERIFY2(m_qbsStdout.contains("definition.."), m_qbsStdout.constData());
7713 }
7714
inputTagsChangeTracking_data()7715 void TestBlackbox::inputTagsChangeTracking_data()
7716 {
7717 QTest::addColumn<QString>("generateInput");
7718 QTest::newRow("source artifact") << QString("no");
7719 QTest::newRow("generated artifact (static)") << QString("static");
7720 QTest::newRow("generated artifact (dynamic)") << QString("dynamic");
7721 }
7722
inputTagsChangeTracking()7723 void TestBlackbox::inputTagsChangeTracking()
7724 {
7725 QDir::setCurrent(testDataDir + "/input-tags-change-tracking");
7726 const QString xOut = QDir::currentPath() + '/' + relativeProductBuildDir("p") + "/x.out";
7727 const QString yOut = QDir::currentPath() + '/' + relativeProductBuildDir("p") + "/y.out";
7728 QFETCH(QString, generateInput);
7729 const QbsRunParameters resolveParams("resolve",
7730 QStringList("products.p.generateInput:" + generateInput));
7731 QCOMPARE(runQbs(resolveParams), 0);
7732 QCOMPARE(runQbs(), 0);
7733 QVERIFY(m_qbsStdout.contains("generating input.txt") == (generateInput == "static"));
7734 QVERIFY2(!QFile::exists(xOut), qPrintable(xOut));
7735 QVERIFY2(!QFile::exists(yOut), qPrintable(yOut));
7736 WAIT_FOR_NEW_TIMESTAMP();
7737 REPLACE_IN_FILE("input-tags-change-tracking.qbs", "Tags: [\"txt\", \"empty\"]",
7738 "Tags: \"txt\"");
7739 QCOMPARE(runQbs(), 0);
7740 QVERIFY2(QFile::exists(xOut), qPrintable(xOut));
7741 QVERIFY2(!QFile::exists(yOut), qPrintable(yOut));
7742 WAIT_FOR_NEW_TIMESTAMP();
7743 REPLACE_IN_FILE("input-tags-change-tracking.qbs", "Tags: \"txt\"",
7744 "Tags: [\"txt\", \"y\"]");
7745 QCOMPARE(runQbs(), 0);
7746 QVERIFY2(!QFile::exists(xOut), qPrintable(xOut));
7747 QVERIFY2(QFile::exists(yOut), qPrintable(yOut));
7748 WAIT_FOR_NEW_TIMESTAMP();
7749 REPLACE_IN_FILE("input-tags-change-tracking.qbs", "Tags: [\"txt\", \"y\"]",
7750 "Tags: [\"txt\", \"empty\"]");
7751 QCOMPARE(runQbs(), 0);
7752 QVERIFY2(!QFile::exists(xOut), qPrintable(xOut));
7753 QVERIFY2(!QFile::exists(yOut), qPrintable(yOut));
7754 }
7755
outputArtifactAutoTagging()7756 void TestBlackbox::outputArtifactAutoTagging()
7757 {
7758 QDir::setCurrent(testDataDir + QLatin1String("/output-artifact-auto-tagging"));
7759
7760 QCOMPARE(runQbs(), 0);
7761 QVERIFY(regularFileExists(relativeExecutableFilePath("output-artifact-auto-tagging")));
7762 }
7763
outputRedirection()7764 void TestBlackbox::outputRedirection()
7765 {
7766 QDir::setCurrent(testDataDir + "/output-redirection");
7767 QCOMPARE(runQbs(), 0);
7768 TEXT_FILE_COMPARE("output.txt", relativeProductBuildDir("the-product") + "/output.txt");
7769 TEXT_FILE_COMPARE("output.bin", relativeProductBuildDir("the-product") + "/output.bin");
7770 }
7771
wildCardsAndRules()7772 void TestBlackbox::wildCardsAndRules()
7773 {
7774 QDir::setCurrent(testDataDir + "/wildcards-and-rules");
7775 QCOMPARE(runQbs(), 0);
7776 QVERIFY(m_qbsStdout.contains("Creating output artifact"));
7777 QFile output(relativeProductBuildDir("wildcards-and-rules") + "/test.mytype");
7778 QVERIFY2(output.open(QIODevice::ReadOnly), qPrintable(output.errorString()));
7779 QCOMPARE(output.readAll().count('\n'), 1);
7780 output.close();
7781
7782 // Add input.
7783 WAIT_FOR_NEW_TIMESTAMP();
7784 touch("input2.inp");
7785 QbsRunParameters params;
7786 params.expectFailure = true;
7787 QCOMPARE(runQbs(params), 0);
7788 QVERIFY(m_qbsStdout.contains("Creating output artifact"));
7789 QVERIFY2(output.open(QIODevice::ReadOnly), qPrintable(output.errorString()));
7790 QCOMPARE(output.readAll().count('\n'), 2);
7791 output.close();
7792
7793 // Add "explicitlyDependsOn".
7794 WAIT_FOR_NEW_TIMESTAMP();
7795 touch("dep.dep");
7796 QCOMPARE(runQbs(), 0);
7797 QVERIFY(m_qbsStdout.contains("Creating output artifact"));
7798
7799 // Add nothing.
7800 QCOMPARE(runQbs(), 0);
7801 QVERIFY(!m_qbsStdout.contains("Creating output artifact"));
7802 }
7803
loadableModule()7804 void TestBlackbox::loadableModule()
7805 {
7806 QDir::setCurrent(testDataDir + QLatin1String("/loadablemodule"));
7807
7808 QCOMPARE(runQbs({"resolve"}), 0);
7809 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7810 QSKIP("Cannot run binaries in cross-compiled build");
7811 QbsRunParameters params;
7812 params.command = "run";
7813 QCOMPARE(runQbs(params), 0);
7814 QVERIFY2(m_qbsStdout.contains("foo = 42"), m_qbsStdout.constData());
7815 }
7816
localDeployment()7817 void TestBlackbox::localDeployment()
7818 {
7819 QDir::setCurrent(testDataDir + "/localDeployment");
7820 QFile main("main.cpp");
7821 QVERIFY(main.open(QIODevice::ReadOnly));
7822 QByteArray content = main.readAll();
7823 content.replace('\r', "");
7824 QCOMPARE(runQbs({"resolve"}), 0);
7825 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7826 QSKIP("Cannot run binaries in cross-compiled build");
7827
7828 QbsRunParameters params;
7829 params.command = "run";
7830 QCOMPARE(runQbs(params), 0);
7831 QVERIFY2(m_qbsStdout.contains(content), m_qbsStdout.constData());
7832 }
7833
makefileGenerator()7834 void TestBlackbox::makefileGenerator()
7835 {
7836 QDir::setCurrent(testDataDir + "/makefile-generator");
7837 const QbsRunParameters params("generate", QStringList{"-g", "makefile"});
7838 QCOMPARE(runQbs(params), 0);
7839 if (HostOsInfo::isWindowsHost())
7840 return;
7841 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7842 QSKIP("Cannot run binaries in cross-compiled build");
7843 QProcess make;
7844 make.setWorkingDirectory(QDir::currentPath() + '/' + relativeBuildDir());
7845 const QString customInstallRoot = QDir::currentPath() + "/my-install-root";
7846 make.start("make", QStringList{"INSTALL_ROOT=" + customInstallRoot, "install"});
7847 QVERIFY(waitForProcessSuccess(make));
7848 QVERIFY(QFile::exists(relativeExecutableFilePath("the app")));
7849 QVERIFY(!QFile::exists(relativeBuildGraphFilePath()));
7850 QProcess app;
7851 app.start(customInstallRoot + "/usr/local/bin/the app", QStringList());
7852 QVERIFY(waitForProcessSuccess(app));
7853 const QByteArray appStdout = app.readAllStandardOutput();
7854 QVERIFY2(appStdout.contains("Hello, World!"), appStdout.constData());
7855 make.start("make", QStringList("clean"));
7856 QVERIFY(waitForProcessSuccess(make));
7857 QVERIFY(!QFile::exists(relativeExecutableFilePath("the app")));
7858 }
7859
maximumCLanguageVersion()7860 void TestBlackbox::maximumCLanguageVersion()
7861 {
7862 QDir::setCurrent(testDataDir + "/maximum-c-language-version");
7863 QCOMPARE(runQbs(QbsRunParameters("resolve",
7864 QStringList("products.app.enableNewestModule:true"))), 0);
7865 if (m_qbsStdout.contains("is msvc"))
7866 QSKIP("MSVC has no support for setting the C language version.");
7867 QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0);
7868 QVERIFY2(m_qbsStdout.contains("c11") || m_qbsStdout.contains("c1x"), m_qbsStdout.constData());
7869 QCOMPARE(runQbs(QbsRunParameters("resolve",
7870 QStringList("products.app.enableNewestModule:false"))), 0);
7871 QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0);
7872 QVERIFY2(m_qbsStdout.contains("c99"), m_qbsStdout.constData());
7873 }
7874
maximumCxxLanguageVersion()7875 void TestBlackbox::maximumCxxLanguageVersion()
7876 {
7877 QDir::setCurrent(testDataDir + "/maximum-cxx-language-version");
7878 QCOMPARE(runQbs(QbsRunParameters("resolve",
7879 QStringList("products.app.enableNewestModule:true"))), 0);
7880 QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0);
7881 QVERIFY2(m_qbsStdout.contains("c++23") || m_qbsStdout.contains("c++2b")
7882 || m_qbsStdout.contains("c++latest"), m_qbsStdout.constData());
7883 QCOMPARE(runQbs(QbsRunParameters("resolve",
7884 QStringList("products.app.enableNewestModule:false"))), 0);
7885 QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0);
7886 QVERIFY2(m_qbsStdout.contains("c++14") || m_qbsStdout.contains("c++1y"),
7887 m_qbsStdout.constData());
7888 }
7889
moduleProviders()7890 void TestBlackbox::moduleProviders()
7891 {
7892 QDir::setCurrent(testDataDir + "/module-providers");
7893
7894 // Resolving in dry-run mode must not leave any data behind.
7895 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0);
7896 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
7897 QSKIP("Cannot run binaries in cross-compiled build");
7898 QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2);
7899 QVERIFY(!QFile::exists(relativeBuildDir()));
7900
7901 // Initial build.
7902 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
7903 QVERIFY(QFile::exists(relativeBuildDir()));
7904 QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2);
7905 QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
7906 QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app1"), m_qbsStdout.constData());
7907 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
7908 QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData());
7909 QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app2"), m_qbsStdout.constData());
7910
7911 // Rebuild with overridden module provider config. The output for product 2 must change,
7912 // but no setup script must be re-run, because both config values have already been
7913 // handled in the first run.
7914 const QStringList resolveArgs("moduleProviders.mygenerator.chooseLettersFrom:beginning");
7915 QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0);
7916 QVERIFY2(!m_qbsStdout.contains("Running setup script"), m_qbsStdout.constData());
7917 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
7918 QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
7919 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
7920 QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
7921
7922 // Forcing Probe execution triggers a re-run of the setup script. But only once,
7923 // because the module provider config is the same now.
7924 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(resolveArgs)
7925 << "--force-probe-execution")), 0);
7926 QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1);
7927 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
7928 QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
7929 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
7930 QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
7931
7932 // Now re-run without the module provider config override. Again, the setup script must
7933 // run once, for the config value that was not present in the last run.
7934 QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
7935 QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1);
7936 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
7937 QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
7938 QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
7939 QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData());
7940 }
7941
fallbackModuleProvider_data()7942 void TestBlackbox::fallbackModuleProvider_data()
7943 {
7944 QTest::addColumn<bool>("fallbacksEnabledGlobally");
7945 QTest::addColumn<bool>("fallbacksEnabledInProduct");
7946 QTest::addColumn<QStringList>("pkgConfigLibDirs");
7947 QTest::addColumn<bool>("successExpected");
7948 QTest::newRow("without custom lib dir, fallbacks disabled globally and in product")
7949 << false << false << QStringList() << false;
7950 QTest::newRow("without custom lib dir, fallbacks disabled globally, enabled in product")
7951 << false << true << QStringList() << false;
7952 QTest::newRow("without custom lib dir, fallbacks enabled globally, disabled in product")
7953 << true << false << QStringList() << false;
7954 QTest::newRow("without custom lib dir, fallbacks enabled globally and in product")
7955 << true << true << QStringList() << false;
7956 QTest::newRow("with custom lib dir, fallbacks disabled globally and in product")
7957 << false << false << QStringList(testDataDir + "/fallback-module-provider/libdir")
7958 << false;
7959 QTest::newRow("with custom lib dir, fallbacks disabled globally, enabled in product")
7960 << false << true << QStringList(testDataDir + "/fallback-module-provider/libdir")
7961 << false;
7962 QTest::newRow("with custom lib dir, fallbacks enabled globally, disabled in product")
7963 << true << false << QStringList(testDataDir + "/fallback-module-provider/libdir")
7964 << false;
7965 QTest::newRow("with custom lib dir, fallbacks enabled globally and in product")
7966 << true << true << QStringList(testDataDir + "/fallback-module-provider/libdir")
7967 << true;
7968 }
7969
fallbackModuleProvider()7970 void TestBlackbox::fallbackModuleProvider()
7971 {
7972 QFETCH(bool, fallbacksEnabledInProduct);
7973 QFETCH(bool, fallbacksEnabledGlobally);
7974 QFETCH(QStringList, pkgConfigLibDirs);
7975 QFETCH(bool, successExpected);
7976
7977 QDir::setCurrent(testDataDir + "/fallback-module-provider");
7978 static const auto b2s = [](bool b) { return QString(b ? "true" : "false"); };
7979 QbsRunParameters resolveParams("resolve",
7980 QStringList{"modules.pkgconfig.libDirs:" + pkgConfigLibDirs.join(','),
7981 "products.p.fallbacksEnabled:" + b2s(fallbacksEnabledInProduct),
7982 "--force-probe-execution"});
7983 if (!fallbacksEnabledGlobally)
7984 resolveParams.arguments << "--no-fallback-module-provider";
7985 QCOMPARE(runQbs(resolveParams), 0);
7986 const bool pkgConfigPresent = m_qbsStdout.contains("pkg-config present: true");
7987 const bool pkgConfigNotPresent = m_qbsStdout.contains("pkg-config present: false");
7988 QVERIFY(pkgConfigPresent != pkgConfigNotPresent);
7989 if (pkgConfigNotPresent)
7990 successExpected = false;
7991 QbsRunParameters buildParams;
7992 buildParams.expectFailure = !successExpected;
7993 QCOMPARE(runQbs(buildParams) == 0, successExpected);
7994 }
7995
minimumSystemVersion()7996 void TestBlackbox::minimumSystemVersion()
7997 {
7998 rmDirR(relativeBuildDir());
7999 QDir::setCurrent(testDataDir + "/minimumSystemVersion");
8000 QFETCH(QString, file);
8001 QFETCH(QString, output);
8002 QbsRunParameters params({ "-f", file + ".qbs" });
8003 params.command = "resolve";
8004 QCOMPARE(runQbs(params), 0);
8005 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
8006 QSKIP("Cannot run binaries in cross-compiled build");
8007 params.command = "run";
8008 QCOMPARE(runQbs(params), 0);
8009 if (m_qbsStdout.contains("Unsupported compiler"))
8010 QSKIP("Unsupported compiler");
8011 if (!m_qbsStdout.contains(output.toUtf8())) {
8012 qDebug() << "expected output:" << qPrintable(output);
8013 qDebug() << "actual output:" << m_qbsStdout.constData();
8014 }
8015 QVERIFY(m_qbsStdout.contains(output.toUtf8()));
8016 }
8017
fromMinimumDeploymentTargetValue(int v,bool isMacOS)8018 static qbs::Version fromMinimumDeploymentTargetValue(int v, bool isMacOS)
8019 {
8020 if (isMacOS && v < 100000)
8021 return qbs::Version(v / 100, v / 10 % 10, v % 10);
8022 return qbs::Version(v / 10000, v / 100 % 100, v % 100);
8023 }
8024
toMinimumDeploymentTargetValue(const qbs::Version & v,bool isMacOS)8025 static int toMinimumDeploymentTargetValue(const qbs::Version &v, bool isMacOS)
8026 {
8027 if (isMacOS && v < qbs::Version(10, 10))
8028 return (v.majorVersion() * 100) + (v.minorVersion() * 10) + v.patchLevel();
8029 return (v.majorVersion() * 10000) + (v.minorVersion() * 100) + v.patchLevel();
8030 }
8031
defaultClangMinimumDeploymentTarget()8032 static qbs::Version defaultClangMinimumDeploymentTarget()
8033 {
8034 QProcess process;
8035 process.start("/usr/bin/xcrun", {"-sdk", "macosx", "clang++",
8036 "-target", "x86_64-apple-macosx-macho",
8037 "-dM", "-E", "-x", "objective-c++", "/dev/null"});
8038 if (waitForProcessSuccess(process)) {
8039 const auto lines = process.readAllStandardOutput().split('\n');
8040 for (const auto &line : lines) {
8041 static const QByteArray prefix =
8042 "#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ ";
8043 if (line.startsWith(prefix)) {
8044 bool ok = false;
8045 int v = line.mid(prefix.size()).trimmed().toInt(&ok);
8046 if (ok)
8047 return fromMinimumDeploymentTargetValue(v, true);
8048 break;
8049 }
8050 }
8051 }
8052
8053 return qbs::Version();
8054 }
8055
minimumSystemVersion_data()8056 void TestBlackbox::minimumSystemVersion_data()
8057 {
8058 QTest::addColumn<QString>("file");
8059 QTest::addColumn<QString>("output");
8060
8061 // Don't check for the full "version X.Y.Z\n" on macOS as some older versions of otool don't
8062 // show the patch version. Instead, simply check for "version X.Y" with no trailing \n.
8063
8064 const QString unspecified = []() -> QString {
8065 if (HostOsInfo::isMacosHost()) {
8066 const auto v = defaultClangMinimumDeploymentTarget();
8067 auto result = "__MAC_OS_X_VERSION_MIN_REQUIRED="
8068 + QString::number(toMinimumDeploymentTargetValue(v, true));
8069 if (v >= qbs::Version(10, 14))
8070 result += "\nminos ";
8071 else
8072 result += "\nversion ";
8073 result += QString::number(v.majorVersion()) + "." + QString::number(v.minorVersion());
8074 return result;
8075 }
8076
8077 if (HostOsInfo::isWindowsHost())
8078 return "WINVER is not defined\n";
8079
8080 return "";
8081 }();
8082
8083 const QString specific = []() -> QString {
8084 if (HostOsInfo::isMacosHost())
8085 return "__MAC_OS_X_VERSION_MIN_REQUIRED=1070\nversion 10.7\n";
8086
8087 if (HostOsInfo::isWindowsHost())
8088 return "WINVER=1536\n6.00 operating system version\n6.00 subsystem version\n";
8089
8090 return "";
8091 }();
8092
8093 QTest::newRow("unspecified") << "unspecified" << unspecified;
8094 QTest::newRow("unspecified-forced") << "unspecified-forced" << unspecified;
8095 if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacosHost())
8096 QTest::newRow("specific") << "specific" << specific;
8097 if (HostOsInfo::isWindowsHost())
8098 QTest::newRow("fakewindows") << "fakewindows" << "WINVER=1283\n5.03 operating system "
8099 "version\n5.03 subsystem version\n";
8100 if (HostOsInfo::isMacosHost())
8101 QTest::newRow("macappstore") << "macappstore" << "__MAC_OS_X_VERSION_MIN_REQUIRED=1071\n"
8102 "version 10.7";
8103 }
8104
missingBuildGraph()8105 void TestBlackbox::missingBuildGraph()
8106 {
8107 QTemporaryDir tmpDir;
8108 QVERIFY(tmpDir.isValid());
8109 QDir::setCurrent(tmpDir.path());
8110 QFETCH(QString, configName);
8111 const QStringList commands({"clean", "dump-nodes-tree", "status", "update-timestamps"});
8112 const QString actualConfigName = configName.isEmpty() ? QString("default") : configName;
8113 QbsRunParameters params;
8114 params.expectFailure = true;
8115 params.arguments << QLatin1String("config:") + actualConfigName;
8116 for (const QString &command : qAsConst(commands)) {
8117 params.command = command;
8118 QVERIFY2(runQbs(params) != 0, qPrintable(command));
8119 const QString expectedErrorMessage = QString("Build graph not found for "
8120 "configuration '%1'").arg(actualConfigName);
8121 if (!m_qbsStderr.contains(expectedErrorMessage.toLocal8Bit())) {
8122 qDebug() << command;
8123 qDebug() << expectedErrorMessage;
8124 qDebug() << m_qbsStderr;
8125 QFAIL("unexpected error message");
8126 }
8127 }
8128 }
8129
missingBuildGraph_data()8130 void TestBlackbox::missingBuildGraph_data()
8131 {
8132 QTest::addColumn<QString>("configName");
8133 QTest::newRow("implicit config name") << QString();
8134 QTest::newRow("explicit config name") << QString("customConfig");
8135 }
8136
missingDependency()8137 void TestBlackbox::missingDependency()
8138 {
8139 QDir::setCurrent(testDataDir + "/missing-dependency");
8140 QbsRunParameters params;
8141 params.expectFailure = true;
8142 params.arguments << "-p" << "theApp";
8143 QVERIFY(runQbs(params) != 0);
8144 QVERIFY2(!m_qbsStderr.contains("ASSERT"), m_qbsStderr.constData());
8145 QCOMPARE(runQbs(QbsRunParameters(QStringList() << "-p" << "theDep")), 0);
8146 params.expectFailure = false;
8147 params.arguments << "-vv";
8148 QCOMPARE(runQbs(params), 0);
8149 QVERIFY(m_qbsStderr.contains("false positive"));
8150 }
8151
missingProjectFile()8152 void TestBlackbox::missingProjectFile()
8153 {
8154 QDir::setCurrent(testDataDir + "/missing-project-file/empty-dir");
8155 QbsRunParameters params;
8156 params.expectFailure = true;
8157 QVERIFY(runQbs(params) != 0);
8158 QVERIFY2(m_qbsStderr.contains("No project file given and none found in current directory"),
8159 m_qbsStderr.constData());
8160 QDir::setCurrent(testDataDir + "/missing-project-file");
8161 params.arguments << "-f" << "empty-dir";
8162 QVERIFY(runQbs(params) != 0);
8163 QVERIFY2(m_qbsStderr.contains("No project file found in directory"), m_qbsStderr.constData());
8164 params.arguments = QStringList() << "-f" << "ambiguous-dir";
8165 QVERIFY(runQbs(params) != 0);
8166 QVERIFY2(m_qbsStderr.contains("More than one project file found in directory"),
8167 m_qbsStderr.constData());
8168 params.expectFailure = false;
8169 params.arguments = QStringList() << "-f" << "project-dir";
8170 QCOMPARE(runQbs(params), 0);
8171 WAIT_FOR_NEW_TIMESTAMP();
8172 touch("project-dir/file.cpp");
8173 QCOMPARE(runQbs(), 0);
8174 QVERIFY2(m_qbsStdout.contains("compiling file.cpp"), m_qbsStdout.constData());
8175 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8176 }
8177
missingOverridePrefix()8178 void TestBlackbox::missingOverridePrefix()
8179 {
8180 QDir::setCurrent(testDataDir + "/missing-override-prefix");
8181 QbsRunParameters params;
8182 params.expectFailure = true;
8183 params.arguments << "blubb.whatever:false";
8184 QVERIFY(runQbs(params) != 0);
8185 QVERIFY2(m_qbsStderr.contains("Property override key 'blubb.whatever' not understood"),
8186 m_qbsStderr.constData());
8187 }
8188
moduleConditions()8189 void TestBlackbox::moduleConditions()
8190 {
8191 QDir::setCurrent(testDataDir + "/module-conditions");
8192 QCOMPARE(runQbs(), 0);
8193 QCOMPARE(m_qbsStdout.count("loaded m1"), 1);
8194 QCOMPARE(m_qbsStdout.count("loaded m2"), 2);
8195 QCOMPARE(m_qbsStdout.count("loaded m3"), 1);
8196 QCOMPARE(m_qbsStdout.count("loaded m4"), 1);
8197 }
8198
movedFileDependency()8199 void TestBlackbox::movedFileDependency()
8200 {
8201 QDir::setCurrent(testDataDir + "/moved-file-dependency");
8202 const QString subdir2 = QDir::currentPath() + "/subdir2";
8203 QVERIFY(QDir::current().mkdir(subdir2));
8204 const QString oldHeaderFilePath = QDir::currentPath() + "/subdir1/theheader.h";
8205 const QString newHeaderFilePath = subdir2 + "/theheader.h";
8206 QCOMPARE(runQbs(), 0);
8207 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8208 QCOMPARE(runQbs(), 0);
8209 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8210
8211 QFile f(oldHeaderFilePath);
8212 QVERIFY2(f.rename(newHeaderFilePath), qPrintable(f.errorString()));
8213 QCOMPARE(runQbs(), 0);
8214 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8215 QCOMPARE(runQbs(), 0);
8216 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8217
8218 f.setFileName(newHeaderFilePath);
8219 QVERIFY2(f.rename(oldHeaderFilePath), qPrintable(f.errorString()));
8220 QCOMPARE(runQbs(), 0);
8221 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8222 QCOMPARE(runQbs(), 0);
8223 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8224 }
8225
badInterpreter()8226 void TestBlackbox::badInterpreter()
8227 {
8228 if (!HostOsInfo::isAnyUnixHost())
8229 QSKIP("only applies on Unix");
8230
8231 QDir::setCurrent(testDataDir + QLatin1String("/badInterpreter"));
8232 QCOMPARE(runQbs(), 0);
8233
8234 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
8235 QSKIP("Cannot run binaries in cross-compiled build");
8236
8237 QbsRunParameters params("run");
8238 params.expectFailure = true;
8239
8240 const QRegularExpression reNoSuchFileOrDir("bad interpreter:.* No such file or directory");
8241 const QRegularExpression rePermissionDenied("bad interpreter:.* Permission denied");
8242
8243 params.arguments = QStringList() << "-p" << "script-interp-missing";
8244 QCOMPARE(runQbs(params), 1);
8245 QString strerr = QString::fromLocal8Bit(m_qbsStderr);
8246 QVERIFY2(strerr.contains(reNoSuchFileOrDir), m_qbsStderr);
8247
8248 params.arguments = QStringList() << "-p" << "script-interp-noexec";
8249 QCOMPARE(runQbs(params), 1);
8250 strerr = QString::fromLocal8Bit(m_qbsStderr);
8251 QVERIFY2(strerr.contains(reNoSuchFileOrDir) || strerr.contains(rePermissionDenied)
8252 || strerr.contains("script-noexec: bad interpreter: execve: Exec format error"),
8253 qPrintable(strerr));
8254
8255 params.arguments = QStringList() << "-p" << "script-noexec";
8256 QCOMPARE(runQbs(params), 1);
8257 QCOMPARE(runQbs(QbsRunParameters("run", QStringList() << "-p" << "script-ok")), 0);
8258 }
8259
bomSources()8260 void TestBlackbox::bomSources()
8261 {
8262 QDir::setCurrent(testDataDir + "/bom-sources");
8263 const bool success = runQbs() == 0;
8264 if (!success)
8265 QSKIP("Assuming compiler cannot deal with byte order mark");
8266 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8267 WAIT_FOR_NEW_TIMESTAMP();
8268 QCOMPARE(runQbs(), 0);
8269 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8270 touch("theheader.h");
8271 QCOMPARE(runQbs(), 0);
8272 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8273 }
8274
buildDataOfDisabledProduct()8275 void TestBlackbox::buildDataOfDisabledProduct()
8276 {
8277 QDir::setCurrent(testDataDir + QLatin1String("/build-data-of-disabled-product"));
8278 QCOMPARE(runQbs(), 0);
8279 QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8280 QVERIFY2(m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData());
8281
8282 // Touch a source file, disable the product, rebuild the project, verify nothing happens.
8283 WAIT_FOR_NEW_TIMESTAMP();
8284 touch("test.cpp");
8285 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.app.condition:false"))), 0);
8286 QCOMPARE(runQbs(), 0);
8287 QVERIFY2(!m_qbsStdout.contains("compiling"), m_qbsStdout.constData());
8288 QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData());
8289
8290 // Enable the product again, rebuild the project, verify that only the changed source file
8291 // is rebuilt.
8292 QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.app.condition:true"))), 0);
8293 QCOMPARE(runQbs(), 0);
8294 QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
8295 QVERIFY2(m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData());
8296 }
8297
qbsVersion()8298 void TestBlackbox::qbsVersion()
8299 {
8300 const auto v = qbs::LanguageInfo::qbsVersion();
8301 QDir::setCurrent(testDataDir + QLatin1String("/qbsVersion"));
8302 QbsRunParameters params;
8303 params.arguments = QStringList()
8304 << "project.qbsVersion:" + v.toString()
8305 << "project.qbsVersionMajor:" + QString::number(v.majorVersion())
8306 << "project.qbsVersionMinor:" + QString::number(v.minorVersion())
8307 << "project.qbsVersionPatch:" + QString::number(v.patchLevel());
8308 QCOMPARE(runQbs(params), 0);
8309
8310 params.arguments.push_back("project.qbsVersionPatch:" + QString::number(v.patchLevel() + 1));
8311 params.expectFailure = true;
8312 QVERIFY(runQbs(params) != 0);
8313 }
8314
transitiveInvalidDependencies()8315 void TestBlackbox::transitiveInvalidDependencies()
8316 {
8317 QDir::setCurrent(testDataDir + "/transitive-invalid-dependencies");
8318 QbsRunParameters params;
8319 QCOMPARE(runQbs(params), 0);
8320 QVERIFY2(m_qbsStdout.contains("b.present = false"), m_qbsStdout);
8321 QVERIFY2(m_qbsStdout.contains("c.present = true"), m_qbsStdout);
8322 QVERIFY2(m_qbsStdout.contains("d.present = false"), m_qbsStdout);
8323 }
8324
transitiveOptionalDependencies()8325 void TestBlackbox::transitiveOptionalDependencies()
8326 {
8327 QDir::setCurrent(testDataDir + "/transitive-optional-dependencies");
8328 QbsRunParameters params;
8329 QCOMPARE(runQbs(params), 0);
8330 }
8331
groupsInModules()8332 void TestBlackbox::groupsInModules()
8333 {
8334 QDir::setCurrent(testDataDir + "/groups-in-modules");
8335 QCOMPARE(runQbs({"resolve"}), 0);
8336 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
8337 QSKIP("Cannot run binaries in cross-compiled build");
8338 QbsRunParameters params;
8339 QCOMPARE(runQbs(params), 0);
8340 QVERIFY(m_qbsStdout.contains("compile rock.coal => rock.diamond"));
8341 QVERIFY(m_qbsStdout.contains("compile chunk.coal => chunk.diamond"));
8342 QVERIFY(m_qbsStdout.contains("compiling helper2.c"));
8343 QVERIFY(!m_qbsStdout.contains("compiling helper3.c"));
8344 QVERIFY(m_qbsStdout.contains("compiling helper4.c"));
8345 QVERIFY(m_qbsStdout.contains("compiling helper5.c"));
8346 QVERIFY(!m_qbsStdout.contains("compiling helper6.c"));
8347
8348 QCOMPARE(runQbs(params), 0);
8349 QVERIFY(!m_qbsStdout.contains("compile rock.coal => rock.diamond"));
8350 QVERIFY(!m_qbsStdout.contains("compile chunk.coal => chunk.diamond"));
8351
8352 WAIT_FOR_NEW_TIMESTAMP();
8353 touch("modules/helper/diamondc.c");
8354
8355 waitForFileUnlock();
8356 QCOMPARE(runQbs(params), 0);
8357 QVERIFY(m_qbsStdout.contains("compiling diamondc.c"));
8358 QVERIFY(m_qbsStdout.contains("compile rock.coal => rock.diamond"));
8359 QVERIFY(m_qbsStdout.contains("compile chunk.coal => chunk.diamond"));
8360 QVERIFY(regularFileExists(relativeProductBuildDir("groups-in-modules") + "/rock.diamond"));
8361 QFile output(relativeProductBuildDir("groups-in-modules") + "/rock.diamond");
8362 QVERIFY(output.open(QIODevice::ReadOnly));
8363 QCOMPARE(output.readAll().trimmed(), QByteArray("diamond"));
8364 }
8365
grpc_data()8366 void TestBlackbox::grpc_data()
8367 {
8368 QTest::addColumn<QString>("projectFile");
8369 QTest::addColumn<QStringList>("arguments");
8370 QTest::addColumn<bool>("hasModules");
8371
8372 QTest::newRow("cpp") << QString("grpc_cpp.qbs") << QStringList() << false;
8373
8374 QStringList pkgConfigArgs("project.qbsModuleProviders:qbspkgconfig");
8375 // on macOS, openSSL is hidden from pkg-config by default
8376 if (qbs::Internal::HostOsInfo::isMacosHost()) {
8377 pkgConfigArgs
8378 << "moduleProviders.qbspkgconfig.extraPaths:/usr/local/opt/openssl@1.1/lib/pkgconfig";
8379 }
8380 QTest::newRow("cpp-pkgconfig") << QString("grpc_cpp.qbs") << pkgConfigArgs << true;
8381 }
8382
grpc()8383 void TestBlackbox::grpc()
8384 {
8385 QDir::setCurrent(testDataDir + "/grpc");
8386 QFETCH(QString, projectFile);
8387 QFETCH(QStringList, arguments);
8388 QFETCH(bool, hasModules);
8389
8390 rmDirR(relativeBuildDir());
8391 QbsRunParameters resolveParams("resolve", QStringList{"-f", projectFile});
8392 resolveParams.arguments << arguments;
8393 QCOMPARE(runQbs(resolveParams), 0);
8394 const bool withGrpc = m_qbsStdout.contains("has grpc: true");
8395 const bool withoutGrpc = m_qbsStdout.contains("has grpc: false");
8396 QVERIFY2(withGrpc || withoutGrpc, m_qbsStdout.constData());
8397 if (withoutGrpc)
8398 QSKIP("grpc module not present");
8399
8400 const bool hasMods = m_qbsStdout.contains("has modules: true");
8401 const bool dontHaveMods = m_qbsStdout.contains("has modules: false");
8402 QVERIFY2(hasMods == !dontHaveMods, m_qbsStdout.constData());
8403 QCOMPARE(hasMods, hasModules);
8404
8405 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
8406 QSKIP("Cannot run binaries in cross-compiled build");
8407
8408 QbsRunParameters runParams;
8409 QCOMPARE(runQbs(runParams), 0);
8410 }
8411
hostOsProperties()8412 void TestBlackbox::hostOsProperties()
8413 {
8414 QDir::setCurrent(testDataDir + "/host-os-properties");
8415 QCOMPARE(runQbs(QStringLiteral("resolve")), 0);
8416 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
8417 QSKIP("Cannot run binaries in cross-compiled build");
8418 QCOMPARE(runQbs(QStringLiteral("run")), 0);
8419 QVERIFY2(m_qbsStdout.contains(
8420 ("HOST_ARCHITECTURE = " + HostOsInfo::hostOSArchitecture()).data()),
8421 m_qbsStdout.constData());
8422 QVERIFY2(m_qbsStdout.contains(("HOST_PLATFORM = " + HostOsInfo::hostOSIdentifier()).data()),
8423 m_qbsStdout.constData());
8424 }
8425
ico()8426 void TestBlackbox::ico()
8427 {
8428 QDir::setCurrent(testDataDir + "/ico");
8429 QbsRunParameters params;
8430 params.expectFailure = true;
8431 params.arguments << "--command-echo-mode" << "command-line";
8432 const int status = runQbs(params);
8433 if (status != 0) {
8434 if (m_qbsStderr.contains("Could not find icotool in any of the following locations:"))
8435 QSKIP("icotool is not installed");
8436 if (!m_qbsStderr.isEmpty())
8437 qDebug("%s", m_qbsStderr.constData());
8438 if (!m_qbsStdout.isEmpty())
8439 qDebug("%s", m_qbsStdout.constData());
8440 }
8441 QCOMPARE(status, 0);
8442
8443 QVERIFY(QFileInfo::exists(relativeProductBuildDir("icon") + "/icon.ico"));
8444 {
8445 QFile f(relativeProductBuildDir("icon") + "/icon.ico");
8446 QVERIFY(f.open(QIODevice::ReadOnly));
8447 const auto b = f.readAll().toStdString();
8448 QCOMPARE(b.at(2), '\x1'); // icon
8449 QCOMPARE(b.at(4), '\x2'); // 2 images
8450 QVERIFY(b.find("\x89PNG") == std::string::npos);
8451 }
8452
8453 QVERIFY(QFileInfo::exists(relativeProductBuildDir("icon-alpha") + "/icon-alpha.ico"));
8454 {
8455 QFile f(relativeProductBuildDir("icon-alpha") + "/icon-alpha.ico");
8456 QVERIFY(f.open(QIODevice::ReadOnly));
8457 const auto b = f.readAll().toStdString();
8458 QCOMPARE(b.at(2), '\x1'); // icon
8459 QCOMPARE(b.at(4), '\x2'); // 2 images
8460 QVERIFY(b.find("\x89PNG") == std::string::npos);
8461 QVERIFY2(m_qbsStdout.contains("--alpha-threshold="), m_qbsStdout.constData());
8462 }
8463
8464 QVERIFY(QFileInfo::exists(relativeProductBuildDir("icon-big") + "/icon-big.ico"));
8465 {
8466 QFile f(relativeProductBuildDir("icon-big") + "/icon-big.ico");
8467 QVERIFY(f.open(QIODevice::ReadOnly));
8468 const auto b = f.readAll().toStdString();
8469 QCOMPARE(b.at(2), '\x1'); // icon
8470 QCOMPARE(b.at(4), '\x5'); // 5 images
8471 QVERIFY(b.find("\x89PNG") != std::string::npos);
8472 }
8473
8474 QVERIFY(QFileInfo::exists(relativeProductBuildDir("cursor") + "/cursor.cur"));
8475 {
8476 QFile f(relativeProductBuildDir("cursor") + "/cursor.cur");
8477 QVERIFY(f.open(QIODevice::ReadOnly));
8478 const auto b = f.readAll();
8479 QVERIFY(b.size() > 0);
8480 QCOMPARE(b.at(2), '\x2'); // cursor
8481 QCOMPARE(b.at(4), '\x2'); // 2 images
8482 QCOMPARE(b.at(10), '\0');
8483 QCOMPARE(b.at(12), '\0');
8484 QCOMPARE(b.at(26), '\0');
8485 QCOMPARE(b.at(28), '\0');
8486 }
8487
8488 QVERIFY(QFileInfo::exists(relativeProductBuildDir("cursor-hotspot") + "/cursor-hotspot.cur"));
8489 {
8490 QFile f(relativeProductBuildDir("cursor-hotspot") + "/cursor-hotspot.cur");
8491 QVERIFY(f.open(QIODevice::ReadOnly));
8492 const auto b = f.readAll();
8493 QVERIFY(b.size() > 0);
8494 QCOMPARE(b.at(2), '\x2'); // cursor
8495 QCOMPARE(b.at(4), '\x2'); // 2 images
8496 const bool hasCursorHotspotBug = m_qbsStderr.contains(
8497 "does not support setting the hotspot for cursor files with multiple images");
8498 if (hasCursorHotspotBug) {
8499 QCOMPARE(b.at(10), '\0');
8500 QCOMPARE(b.at(12), '\0');
8501 QCOMPARE(b.at(26), '\0');
8502 QCOMPARE(b.at(28), '\0');
8503 QWARN("this version of icoutil does not support setting the hotspot "
8504 "for cursor files with multiple images");
8505 } else {
8506 QCOMPARE(b.at(10), '\x8');
8507 QCOMPARE(b.at(12), '\x9');
8508 QCOMPARE(b.at(26), '\x10');
8509 QCOMPARE(b.at(28), '\x11');
8510 }
8511 }
8512
8513 QVERIFY(QFileInfo::exists(relativeProductBuildDir("cursor-hotspot-single")
8514 + "/cursor-hotspot-single.cur"));
8515 {
8516 QFile f(relativeProductBuildDir("cursor-hotspot-single") + "/cursor-hotspot-single.cur");
8517 QVERIFY(f.open(QIODevice::ReadOnly));
8518 const auto b = f.readAll();
8519 QVERIFY(b.size() > 0);
8520 QCOMPARE(b.at(2), '\x2'); // cursor
8521 QCOMPARE(b.at(4), '\x1'); // 1 image
8522
8523 // No version check needed because the hotspot can always be set if there's only one image
8524 QCOMPARE(b.at(10), '\x8');
8525 QCOMPARE(b.at(12), '\x9');
8526 }
8527
8528 QVERIFY(QFileInfo::exists(relativeProductBuildDir("iconset") + "/dmg.ico"));
8529 {
8530 QFile f(relativeProductBuildDir("iconset") + "/dmg.ico");
8531 QVERIFY(f.open(QIODevice::ReadOnly));
8532 const auto b = f.readAll();
8533 QVERIFY(b.size() > 0);
8534 QCOMPARE(b.at(2), '\x1'); // icon
8535 QCOMPARE(b.at(4), '\x5'); // 5 images
8536 }
8537
8538 QVERIFY(QFileInfo::exists(relativeProductBuildDir("iconset") + "/dmg.cur"));
8539 {
8540 QFile f(relativeProductBuildDir("iconset") + "/dmg.cur");
8541 QVERIFY(f.open(QIODevice::ReadOnly));
8542 const auto b = f.readAll();
8543 QVERIFY(b.size() > 0);
8544 QCOMPARE(b.at(2), '\x2'); // cursor
8545 QCOMPARE(b.at(4), '\x5'); // 5 images
8546 QCOMPARE(b.at(10), '\0');
8547 QCOMPARE(b.at(12), '\0');
8548 QCOMPARE(b.at(26), '\0');
8549 QCOMPARE(b.at(28), '\0');
8550 }
8551 }
8552
importAssignment()8553 void TestBlackbox::importAssignment()
8554 {
8555 QDir::setCurrent(testDataDir + "/import-assignment");
8556 QCOMPARE(runQbs(QStringList("project.qbsSearchPaths:" + QDir::currentPath())), 0);
8557 QVERIFY2(m_qbsStdout.contains("key 1 = value1") && m_qbsStdout.contains("key 2 = value2"),
8558 m_qbsStdout.constData());
8559 }
8560
importChangeTracking()8561 void TestBlackbox::importChangeTracking()
8562 {
8563 QDir::setCurrent(testDataDir + "/import-change-tracking");
8564 QCOMPARE(runQbs(QStringList({"-f", "import-change-tracking.qbs"})), 0);
8565 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8566 QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData());
8567 QVERIFY2(m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8568 QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8569 QVERIFY2(m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8570 QVERIFY2(m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8571 QVERIFY2(m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8572
8573 // Change in imported file that is not used in any rule or command.
8574 WAIT_FOR_NEW_TIMESTAMP();
8575 touch("irrelevant.js");
8576 QCOMPARE(runQbs(), 0);
8577 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8578 QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData());
8579 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8580 QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8581 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8582 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8583 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8584
8585 // Change in directly imported file only used by one prepare script.
8586 WAIT_FOR_NEW_TIMESTAMP();
8587 touch("custom1prepare1.js");
8588 QCOMPARE(runQbs(), 0);
8589 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8590 QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData());
8591 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8592 QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8593 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8594 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8595 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8596
8597 // Change in recursively imported file only used by one prepare script.
8598 WAIT_FOR_NEW_TIMESTAMP();
8599 touch("custom1prepare2.js");
8600 QCOMPARE(runQbs(), 0);
8601 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8602 QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData());
8603 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8604 QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8605 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8606 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8607 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8608
8609 // Change in imported file used only by one command.
8610 WAIT_FOR_NEW_TIMESTAMP();
8611 touch("custom1command.js");
8612 QCOMPARE(runQbs(), 0);
8613 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8614 QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData());
8615 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8616 QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8617 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8618 QVERIFY2(m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8619 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8620
8621 // Change in file only used by one prepare script, using directory import.
8622 WAIT_FOR_NEW_TIMESTAMP();
8623 touch("custom2prepare/custom2prepare2.js");
8624 QCOMPARE(runQbs(), 0);
8625 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8626 QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData());
8627 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8628 QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8629 QVERIFY2(m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8630 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8631 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8632
8633 // Change in file used only by one command, imported via search path.
8634 WAIT_FOR_NEW_TIMESTAMP();
8635 touch("imports/custom2command/custom2command1.js");
8636 QCOMPARE(runQbs(), 0);
8637 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8638 QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData());
8639 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8640 QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8641 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8642 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8643 QVERIFY2(m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8644
8645 // Change in directly imported file only used by one Probe
8646 WAIT_FOR_NEW_TIMESTAMP();
8647 touch("probe1.js");
8648 QCOMPARE(runQbs(), 0);
8649 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8650 QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData());
8651 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8652 QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8653 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8654 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8655 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8656
8657 // Change in indirectly imported file only used by one Probe
8658 WAIT_FOR_NEW_TIMESTAMP();
8659 touch("probe2.js");
8660 QCOMPARE(runQbs(), 0);
8661 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8662 QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData());
8663 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8664 QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8665 QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8666 QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8667 QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8668
8669 // Change everything at once.
8670 WAIT_FOR_NEW_TIMESTAMP();
8671 touch("irrelevant.js");
8672 touch("custom1prepare1.js");
8673 touch("custom1prepare2.js");
8674 touch("custom1command.js");
8675 touch("custom2prepare/custom2prepare1.js");
8676 touch("imports/custom2command/custom2command2.js");
8677 touch("probe2.js");
8678 QCOMPARE(runQbs(), 0);
8679 QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
8680 QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData());
8681 QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData());
8682 QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData());
8683 QVERIFY2(m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData());
8684 QVERIFY2(m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData());
8685 QVERIFY2(m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData());
8686 }
8687
probesInNestedModules()8688 void TestBlackbox::probesInNestedModules()
8689 {
8690 QDir::setCurrent(testDataDir + "/probes-in-nested-modules");
8691 QbsRunParameters params;
8692 QCOMPARE(runQbs(params), 0);
8693
8694 QCOMPARE(m_qbsStdout.count("running probe a"), 1);
8695 QCOMPARE(m_qbsStdout.count("running probe b"), 1);
8696 QCOMPARE(m_qbsStdout.count("running probe c"), 1);
8697 QCOMPARE(m_qbsStdout.count("running second probe a"), 1);
8698
8699 QVERIFY(m_qbsStdout.contains("product a, outer.somethingElse = goodbye"));
8700 QVERIFY(m_qbsStdout.contains("product b, inner.something = hahaha"));
8701 QVERIFY(m_qbsStdout.contains("product c, inner.something = hello"));
8702
8703 QVERIFY(m_qbsStdout.contains("product a, inner.something = hahaha"));
8704 QVERIFY(m_qbsStdout.contains("product a, outer.something = hahaha"));
8705 }
8706
8707 QTEST_MAIN(TestBlackbox)
8708