1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qbs.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 #ifndef QBS_TEST_SHARED_H
29 #define QBS_TEST_SHARED_H
30
31 #include <tools/hostosinfo.h>
32 #include <tools/profile.h>
33 #include <tools/settings.h>
34 #include <tools/toolchains.h>
35
36 #include <QtCore/qbytearray.h>
37 #include <QtCore/qcryptographichash.h>
38 #include <QtCore/qdatetime.h>
39 #include <QtCore/qdebug.h>
40 #include <QtCore/qdir.h>
41 #include <QtCore/qfile.h>
42 #include <QtCore/qfileinfo.h>
43 #include <QtCore/qstring.h>
44 #include <QtCore/qtemporaryfile.h>
45
46 #include <QtTest/qtest.h>
47
48 #include <memory>
49
50
51
52 #define REPLACE_IN_FILE(filePath, oldContent, newContent) \
53 do { \
54 QFile f((filePath)); \
55 QVERIFY2(f.open(QIODevice::ReadWrite), qPrintable(f.errorString())); \
56 QByteArray content = f.readAll(); \
57 const QByteArray savedContent = content; \
58 content.replace((oldContent), (newContent)); \
59 QVERIFY(content != savedContent); \
60 f.resize(0); \
61 f.write(content); \
62 } while (false)
63
testTimeoutInMsecs()64 inline int testTimeoutInMsecs()
65 {
66 bool ok;
67 int timeoutInSecs = qEnvironmentVariableIntValue("QBS_AUTOTEST_TIMEOUT", &ok);
68 if (!ok)
69 timeoutInSecs = 600;
70 return timeoutInSecs * 1000;
71 }
72
73 // On Windows, it appears that a lock is sometimes held on files for a short while even after
74 // they are closed. The likelihood for that seems to increase with the slowness of the machine.
waitForFileUnlock()75 inline void waitForFileUnlock()
76 {
77 bool ok;
78 int timeoutInSecs = qEnvironmentVariableIntValue("QBS_AUTOTEST_IO_GRACE_PERIOD", &ok);
79 if (!ok)
80 timeoutInSecs = qbs::Internal::HostOsInfo::isWindowsHost() ? 1 : 0;
81 if (timeoutInSecs > 0)
82 QTest::qWait(timeoutInSecs * 1000);
83 }
84
85 using SettingsPtr = std::unique_ptr<qbs::Settings>;
settings()86 inline SettingsPtr settings()
87 {
88 const QString settingsDir = QLatin1String(qgetenv("QBS_AUTOTEST_SETTINGS_DIR"));
89 return std::make_unique<qbs::Settings>(settingsDir);
90 }
91
profileName()92 inline QString profileName()
93 {
94 const QString suiteProfile = QLatin1String(
95 qgetenv("QBS_AUTOTEST_PROFILE_" QBS_TEST_SUITE_NAME));
96 if (!suiteProfile.isEmpty())
97 return suiteProfile;
98 const QString profile = QLatin1String(qgetenv("QBS_AUTOTEST_PROFILE"));
99 return !profile.isEmpty() ? profile : QLatin1String("none");
100 }
101
102 inline QString relativeBuildDir(const QString &configurationName = QString())
103 {
104 return !configurationName.isEmpty() ? configurationName : QLatin1String("default");
105 }
106
107 inline QString relativeBuildGraphFilePath(const QString &configName = QString()) {
108 return relativeBuildDir(configName) + QLatin1Char('/') + relativeBuildDir(configName)
109 + QLatin1String(".bg");
110 }
111
regularFileExists(const QString & filePath)112 inline bool regularFileExists(const QString &filePath)
113 {
114 const QFileInfo fi(filePath);
115 return fi.exists() && fi.isFile();
116 }
117
directoryExists(const QString & dirPath)118 inline bool directoryExists(const QString &dirPath)
119 {
120 const QFileInfo fi(dirPath);
121 return fi.exists() && fi.isDir();
122 }
123
124 struct ReadFileContentResult
125 {
126 QByteArray content;
127 QString errorString;
128 };
129
readFileContent(const QString & filePath)130 inline ReadFileContentResult readFileContent(const QString &filePath)
131 {
132 ReadFileContentResult result;
133 QFile file(filePath);
134 if (!file.open(QIODevice::ReadOnly)) {
135 result.errorString = file.errorString();
136 return result;
137 }
138 result.content = file.readAll();
139 return result;
140 }
141
diffText(const QByteArray & actual,const QByteArray & expected)142 inline QByteArray diffText(const QByteArray &actual, const QByteArray &expected)
143 {
144 QByteArray result;
145 QList<QByteArray> actualLines = actual.split('\n');
146 QList<QByteArray> expectedLines = expected.split('\n');
147 int n = 1;
148 while (!actualLines.isEmpty() && !expectedLines.isEmpty()) {
149 QByteArray actualLine = actualLines.takeFirst();
150 QByteArray expectedLine = expectedLines.takeFirst();
151 if (actualLine != expectedLine) {
152 result += QStringLiteral("%1: actual: %2\n%1:expected: %3\n")
153 .arg(n, 2)
154 .arg(QString::fromUtf8(actualLine))
155 .arg(QString::fromUtf8(expectedLine))
156 .toUtf8();
157 }
158 n++;
159 }
160 auto addLines = [&result, &n] (const QList<QByteArray> &lines) {
161 for (const QByteArray &line : qAsConst(lines)) {
162 result += QStringLiteral("%1: %2\n")
163 .arg(n)
164 .arg(QString::fromUtf8(line))
165 .toUtf8();
166 n++;
167 }
168 };
169 if (!actualLines.isEmpty()) {
170 result += "Extra unexpected lines:\n";
171 addLines(actualLines);
172 }
173 if (!expectedLines.isEmpty()) {
174 result += "Missing expected lines:\n";
175 addLines(expectedLines);
176 }
177 return result;
178 }
179
180 #define READ_TEXT_FILE(filePath, contentVariable) \
181 QByteArray contentVariable; \
182 { \
183 auto c = readFileContent(filePath); \
184 QVERIFY2(c.errorString.isEmpty(), \
185 qUtf8Printable(QStringLiteral("Cannot open file %1. %2") \
186 .arg(filePath, c.errorString))); \
187 contentVariable = std::move(c.content); \
188 }
189
190 #define TEXT_FILE_COMPARE(actualFilePath, expectedFilePath) \
191 { \
192 READ_TEXT_FILE(actualFilePath, ba1); \
193 READ_TEXT_FILE(expectedFilePath, ba2); \
194 if (ba1 != ba2) { \
195 QByteArray msg = "File contents differ:\n" + diffText(ba1, ba2); \
196 QFAIL(msg.constData()); \
197 } \
198 }
199
200 template <typename T>
prefixedIfNonEmpty(const T & prefix,const QString & str)201 inline QString prefixedIfNonEmpty(const T &prefix, const QString &str)
202 {
203 if (str.isEmpty())
204 return QString();
205 return prefix + str;
206 }
207
uniqueProductName(const QString & productName,const QString & multiplexConfigurationId)208 inline QString uniqueProductName(const QString &productName,
209 const QString &multiplexConfigurationId)
210 {
211 return productName + prefixedIfNonEmpty(QLatin1Char('.'), multiplexConfigurationId);
212 }
213
214 inline QString relativeProductBuildDir(const QString &productName,
215 const QString &configurationName = QString(),
216 const QString &multiplexConfigurationId = QString())
217 {
218 const QString fullName = uniqueProductName(productName, multiplexConfigurationId);
219 QString dirName = qbs::Internal::HostOsInfo::rfc1034Identifier(fullName);
220 const QByteArray hash = QCryptographicHash::hash(fullName.toUtf8(), QCryptographicHash::Sha1);
221 dirName.append('.').append(hash.toHex().left(8));
222 return relativeBuildDir(configurationName) + '/' + dirName;
223 }
224
225 inline QString relativeExecutableFilePath(const QString &productName,
226 const QString &configName = QString())
227 {
228 return relativeProductBuildDir(productName, configName) + '/'
229 + qbs::Internal::HostOsInfo::appendExecutableSuffix(productName);
230 }
231
waitForNewTimestamp(const QString & testDir)232 inline void waitForNewTimestamp(const QString &testDir)
233 {
234 // Waits for the time that corresponds to the host file system's time stamp granularity.
235 if (qbs::Internal::HostOsInfo::isWindowsHost()) {
236 QTest::qWait(1); // NTFS has 100 ns precision. Let's ignore exFAT.
237 } else {
238 const QString nameTemplate = testDir + "/XXXXXX";
239 QTemporaryFile f1(nameTemplate);
240 if (!f1.open())
241 qFatal("Failed to open temp file");
242 const QDateTime initialTime = QFileInfo(f1).lastModified();
243 int totalMsPassed = 0;
244 while (totalMsPassed <= 2000) {
245 static const int increment = 50;
246 QTest::qWait(increment);
247 totalMsPassed += increment;
248 QTemporaryFile f2(nameTemplate);
249 if (!f2.open())
250 qFatal("Failed to open temp file");
251 if (QFileInfo(f2).lastModified() > initialTime)
252 return;
253 }
254 qWarning("Got no new timestamp after two seconds, going ahead anyway. Subsequent "
255 "test failure might not be genuine.");
256 }
257 }
258
touch(const QString & fn)259 inline void touch(const QString &fn)
260 {
261 QFile f(fn);
262 int s = f.size();
263 if (!f.open(QFile::ReadWrite))
264 qFatal("cannot open file %s", qPrintable(fn));
265 f.resize(s+1);
266 f.resize(s);
267 }
268
copyFileAndUpdateTimestamp(const QString & source,const QString & target)269 inline void copyFileAndUpdateTimestamp(const QString &source, const QString &target)
270 {
271 QFile::remove(target);
272 if (!QFile::copy(source, target))
273 qFatal("Failed to copy '%s' to '%s'", qPrintable(source), qPrintable(target));
274 touch(target);
275 }
276
profileToolchain(const qbs::Profile & profile)277 inline QStringList profileToolchain(const qbs::Profile &profile)
278 {
279 const auto toolchainType = profile.value(QStringLiteral("qbs.toolchainType")).toString();
280 if (!toolchainType.isEmpty())
281 return qbs::canonicalToolchain(toolchainType);
282 return profile.value(QStringLiteral("qbs.toolchain")).toStringList();
283 }
284
objectFileName(const QString & baseName,const QString & profileName)285 inline QString objectFileName(const QString &baseName, const QString &profileName)
286 {
287 const SettingsPtr s = settings();
288 qbs::Profile profile(profileName, s.get());
289
290 const auto tcList = profileToolchain(profile);
291 const bool isMsvc = tcList.contains("msvc")
292 || (tcList.isEmpty() && qbs::Internal::HostOsInfo::isWindowsHost());
293 const QString suffix = isMsvc ? "obj" : "o";
294 return baseName + '.' + suffix;
295 }
296
inputDirHash(const QString & dir)297 inline QString inputDirHash(const QString &dir)
298 {
299 return QCryptographicHash::hash(dir.toLatin1(), QCryptographicHash::Sha1).toHex().left(16);
300 }
301
testDataSourceDir(const QString & dir)302 inline QString testDataSourceDir(const QString &dir)
303 {
304 QDir result;
305 QString testSourceRootDirFromEnv = QDir::fromNativeSeparators(qEnvironmentVariable("QBS_TEST_SOURCE_ROOT"));
306 if (testSourceRootDirFromEnv.isEmpty()) {
307 result.setPath(dir);
308 } else {
309 QDir testSourceRootDir(dir);
310 while (testSourceRootDir.dirName() != "tests")
311 testSourceRootDir = QFileInfo(testSourceRootDir, "").dir();
312
313 QString relativeDataPath = testSourceRootDir.relativeFilePath(dir);
314 QString absoluteDataPath = QDir(testSourceRootDirFromEnv).absoluteFilePath(relativeDataPath);
315 result.setPath(absoluteDataPath);
316 }
317
318 if (!result.exists())
319 qFatal("Expected data folder '%s' to be present, but it does not exist. You may set "
320 "QBS_TEST_SOURCE_ROOT to the 'tests' folder in your qbs repository to configure "
321 "a custom location.", qPrintable(result.absolutePath()));
322
323 return result.absolutePath();
324 }
325
testWorkDir(const QString & testName)326 inline QString testWorkDir(const QString &testName)
327 {
328 QString dir = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv("QBS_TEST_WORK_ROOT")));
329 if (dir.isEmpty()) {
330 dir = QCoreApplication::applicationDirPath() + QStringLiteral("/../tests/auto/");
331 } else {
332 if (!dir.endsWith(QLatin1Char('/')))
333 dir += QLatin1Char('/');
334 }
335 return QDir::cleanPath(dir + testName + "/testWorkDir");
336 }
337
copyDllExportHeader(const QString & srcDataDir,const QString & targetDataDir)338 inline bool copyDllExportHeader(const QString &srcDataDir, const QString &targetDataDir)
339 {
340 QFile sourceFile(srcDataDir + "/../../dllexport.h");
341 if (!sourceFile.exists())
342 return true;
343 const QString targetPath = targetDataDir + "/dllexport.h";
344 QFile::remove(targetPath);
345 return sourceFile.copy(targetPath);
346 }
347
targetOs()348 inline qbs::Internal::HostOsInfo::HostOs targetOs()
349 {
350 const SettingsPtr s = settings();
351 const qbs::Profile buildProfile(profileName(), s.get());
352 const QString targetPlatform = buildProfile.value("qbs.targetPlatform").toString();
353 if (!targetPlatform.isEmpty()) {
354 const std::vector<std::string> targetOS = qbs::Internal::HostOsInfo::canonicalOSIdentifiers(
355 targetPlatform.toStdString());
356 if (qbs::Internal::contains(targetOS, "windows"))
357 return qbs::Internal::HostOsInfo::HostOsWindows;
358 if (qbs::Internal::contains(targetOS, "linux"))
359 return qbs::Internal::HostOsInfo::HostOsLinux;
360 if (qbs::Internal::contains(targetOS, "macos"))
361 return qbs::Internal::HostOsInfo::HostOsMacos;
362 if (qbs::Internal::contains(targetOS, "unix"))
363 return qbs::Internal::HostOsInfo::HostOsOtherUnix;
364 return qbs::Internal::HostOsInfo::HostOsOther;
365 }
366 return qbs::Internal::HostOsInfo::hostOs();
367 }
368
369 #endif // Include guard.
370