1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2016 Christian Gagneraud.
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_clangdb.h"
31
32 #include "../shared.h"
33
34 #include <tools/hostosinfo.h>
35 #include <tools/installoptions.h>
36
37 #include <QtCore/qdir.h>
38 #include <QtCore/qfile.h>
39 #include <QtCore/qregularexpression.h>
40
41 #include <QtCore/qjsonarray.h>
42 #include <QtCore/qjsondocument.h>
43 #include <QtCore/qjsonobject.h>
44
45 #include <QtTest/qtest.h>
46
47 using qbs::InstallOptions;
48 using qbs::Internal::HostOsInfo;
49
runProcess(const QString & exec,const QStringList & args,QByteArray & stdErr,QByteArray & stdOut)50 int TestClangDb::runProcess(const QString &exec, const QStringList &args, QByteArray &stdErr,
51 QByteArray &stdOut)
52 {
53 QProcess process;
54 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
55 env.insert(processEnvironment);
56 process.setProcessEnvironment(env);
57
58 process.start(exec, args);
59 const int waitTime = 10 * 60000;
60 if (!process.waitForStarted() || !process.waitForFinished(waitTime)) {
61 stdErr = process.readAllStandardError();
62 return -1;
63 }
64
65 stdErr = process.readAllStandardError();
66 stdOut = process.readAllStandardOutput();
67 sanitizeOutput(&stdErr);
68 sanitizeOutput(&stdOut);
69
70 if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
71 if (!stdErr.isEmpty())
72 qDebug("%s", stdErr.constData());
73 if (!stdOut.isEmpty())
74 qDebug("%s", stdOut.constData());
75 }
76
77 return process.exitStatus() == QProcess::NormalExit ? process.exitCode() : -1;
78 }
79
clangVersion()80 qbs::Version TestClangDb::clangVersion()
81 {
82 QByteArray stdErr;
83 QByteArray stdOut;
84 if (runProcess("clang-check", QStringList("--version"), stdErr, stdOut) != 0)
85 return qbs::Version();
86 stdOut.remove(0, stdOut.indexOf("LLVM version ") + 13);
87 stdOut.truncate(stdOut.indexOf('\n'));
88 return qbs::Version::fromString(QString::fromLocal8Bit(stdOut));
89 }
90
91
TestClangDb()92 TestClangDb::TestClangDb() : TestBlackboxBase(SRCDIR "/testdata-clangdb", "blackbox-clangdb"),
93 projectDir(QDir::cleanPath(testDataDir + "/project1")),
94 projectFileName("project.qbs"),
95 buildDir(QDir::cleanPath(projectDir + "/" + relativeBuildDir())),
96 sourceFilePath(QDir::cleanPath(projectDir + "/i like spaces.cpp")),
97 dbFilePath(QDir::cleanPath(buildDir + "/compile_commands.json"))
98 {
99 }
100
initTestCase()101 void TestClangDb::initTestCase()
102 {
103 TestBlackboxBase::initTestCase();
104 QDir::setCurrent(projectDir);
105 }
106
ensureBuildTreeCreated()107 void TestClangDb::ensureBuildTreeCreated()
108 {
109 QCOMPARE(runQbs(), 0);
110 QVERIFY(QFile::exists(buildDir));
111
112 if (m_qbsStdout.contains("is msvc") || m_qbsStdout.contains("is mingw")) {
113 sanitizeOutput(&m_qbsStdout);
114 const auto lines = m_qbsStdout.split('\n');
115 for (const auto &line : lines) {
116 static const QByteArray includeEnv = "INCLUDE=";
117 static const QByteArray libEnv = "LIB=";
118 static const QByteArray pathEnv = "PATH=";
119 if (line.startsWith(includeEnv))
120 processEnvironment.insert("INCLUDE", line.mid(includeEnv.size()));
121 if (line.startsWith(libEnv))
122 processEnvironment.insert("LIB", line.mid(libEnv.size()));
123 if (line.startsWith(pathEnv))
124 processEnvironment.insert("PATH", line.mid(pathEnv.size()));
125 }
126 }
127 }
128
checkCanGenerateDb()129 void TestClangDb::checkCanGenerateDb()
130 {
131 QbsRunParameters params;
132 params.command = "generate";
133 params.arguments << "--generator" << "clangdb";
134 QCOMPARE(runQbs(params), 0);
135 QVERIFY(QFile::exists(dbFilePath));
136 }
137
checkDbIsValidJson()138 void TestClangDb::checkDbIsValidJson()
139 {
140 QFile file(dbFilePath);
141 QVERIFY(file.open(QFile::ReadOnly));
142 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
143 QVERIFY(!doc.isNull());
144 QVERIFY(doc.isArray());
145 }
146
checkDbIsConsistentWithProject()147 void TestClangDb::checkDbIsConsistentWithProject()
148 {
149 QFile file(dbFilePath);
150 QVERIFY(file.open(QFile::ReadOnly));
151 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
152
153 // We expect only one command for now
154 const QJsonArray array = doc.array();
155 QVERIFY(array.size() == 1);
156
157 // Validate the "command object"
158 const QJsonObject entry = array.at(0).toObject();
159 QVERIFY(entry.contains("directory"));
160 QVERIFY(entry.value("directory").isString());
161 QVERIFY(entry.contains("arguments"));
162 QVERIFY(entry.value("arguments").isArray());
163 QVERIFY(entry.value("arguments").toArray().size() >= 2);
164 QVERIFY(entry.contains("file"));
165 QVERIFY(entry.value("file").isString());
166 QVERIFY(entry.value("file").toString() == sourceFilePath);
167
168 // Validate the compile command itself, this requires a previous build since the command
169 // line contains 'deep' path that are created during Qbs build run
170 QByteArray stdErr;
171 QByteArray stdOut;
172 QStringList arguments;
173 const QJsonArray jsonArguments = entry.value("arguments").toArray();
174 QString executable = jsonArguments.at(0).toString();
175 for (int i=1; i<jsonArguments.size(); i++)
176 arguments.push_back(jsonArguments.at(i).toString());
177 QVERIFY(runProcess(executable, arguments, stdErr, stdOut) == 0);
178 }
179
180 // Run clang-check, should give 2 warnings:
181 // <...>/i like spaces.cpp:11:5: warning: Assigned value is garbage or undefined
182 // int unused = garbage;
183 // ^~~~~~~~~~ ~~~~~~~
184 // <...>/i like spaces.cpp:11:9: warning: Value stored to 'unused' during its initialization is never read
185 // int unused = garbage;
186 // ^~~~~~ ~~~~~~~
187 // 2 warnings generated.
checkClangDetectsSourceCodeProblems()188 void TestClangDb::checkClangDetectsSourceCodeProblems()
189 {
190 QByteArray stdErr;
191 QByteArray stdOut;
192 QStringList arguments;
193 const QString executable = findExecutable(QStringList("clang-check"));
194 if (executable.isEmpty())
195 QSKIP("No working clang-check executable found");
196
197 // Older clang versions do not support the "arguments" array in the compilation database.
198 // Should we really want to support them, we would have to fall back to "command" instead.
199 if (clangVersion() < qbs::Version(3, 7))
200 QSKIP("This test requires clang-check to be based on at least LLVM 3.7.0.");
201
202 // clang-check.exe does not understand MSVC command-line syntax
203 const SettingsPtr s = settings();
204 qbs::Profile profile(profileName(), s.get());
205 if (profileToolchain(profile).contains("msvc")) {
206 arguments << "-extra-arg-before=--driver-mode=cl";
207 } else if (profileToolchain(profile).contains("mingw")) {
208 arguments << "-extra-arg-before=--driver-mode=g++";
209 }
210
211 arguments << "-analyze" << "-p" << relativeBuildDir() << sourceFilePath;
212 QVERIFY(runProcess(executable, arguments, stdErr, stdOut) == 0);
213 const QString output = QString::fromLocal8Bit(stdErr);
214 QVERIFY(output.contains(QRegularExpression(QStringLiteral("warning.*undefined"),
215 QRegularExpression::CaseInsensitiveOption)));
216 QVERIFY(output.contains(QRegularExpression(QStringLiteral("warning.*never read"),
217 QRegularExpression::CaseInsensitiveOption)));
218 }
219
220 QTEST_MAIN(TestClangDb)
221