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
29 #include "tst_blackboxjava.h"
30
31 #include "../shared.h"
32 #include <tools/hostosinfo.h>
33 #include <tools/profile.h>
34 #include <tools/qttools.h>
35
36 #include <QtCore/qjsondocument.h>
37 #include <QtCore/qtemporarydir.h>
38
39 using qbs::Internal::HostOsInfo;
40 using qbs::Profile;
41
TestBlackboxJava()42 TestBlackboxJava::TestBlackboxJava() : TestBlackboxBase (SRCDIR "/testdata-java", "blackbox-java")
43 {
44 }
45
processEnvironmentWithCurrentDirectoryInLibraryPath()46 static QProcessEnvironment processEnvironmentWithCurrentDirectoryInLibraryPath()
47 {
48 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
49 env.insert(HostOsInfo::libraryPathEnvironmentVariable(),
50 (QStringList() << env.value(HostOsInfo::libraryPathEnvironmentVariable()) << ".")
51 .join(HostOsInfo::pathListSeparator()));
52 return env;
53 }
54
java()55 void TestBlackboxJava::java()
56 {
57 #if defined(Q_OS_WIN32) && !defined(Q_OS_WIN64)
58 QSKIP("QTBUG-3845");
59 #endif
60
61 const SettingsPtr s = settings();
62 Profile p(profileName(), s.get());
63
64 int status;
65 const auto jdkTools = findJdkTools(&status);
66 QCOMPARE(status, 0);
67
68 QDir::setCurrent(testDataDir + "/java");
69
70 status = runQbs();
71 if (p.value("java.jdkPath").toString().isEmpty()
72 && status != 0 && m_qbsStderr.contains("jdkPath")) {
73 QSKIP("java.jdkPath not set and automatic detection failed");
74 }
75
76 if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
77 QSKIP("Skip test in cross-compiled build");
78
79 QCOMPARE(status, 0);
80
81 const QStringList classFiles =
82 QStringList() << "Jet" << "Ship" << "Vehicles";
83 QStringList classFiles1 = QStringList(classFiles) << "io/qt/qbs/HelloWorld" << "NoPackage";
84 for (QString &classFile : classFiles1) {
85 classFile = relativeProductBuildDir("cc") + "/classes/" + classFile + ".class";
86 QVERIFY2(regularFileExists(classFile), qPrintable(classFile));
87 }
88
89 for (const QString &classFile : classFiles) {
90 const QString filePath = relativeProductBuildDir("jar_file") + "/classes/" + classFile
91 + ".class";
92 QVERIFY2(regularFileExists(filePath), qPrintable(filePath));
93 }
94 const QString jarFilePath = relativeProductBuildDir("jar_file") + '/' + "jar_file.jar";
95 QVERIFY2(regularFileExists(jarFilePath), qPrintable(jarFilePath));
96
97 // Now check whether we correctly predicted the class file output paths.
98 QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
99 for (const QString &classFile : qAsConst(classFiles1)) {
100 QVERIFY2(!regularFileExists(classFile), qPrintable(classFile));
101 }
102
103 // This tests various things: java.manifestClassPath, JNI, etc.
104 QDir::setCurrent(relativeBuildDir() + "/install-root");
105 QProcess process;
106 process.setProcessEnvironment(processEnvironmentWithCurrentDirectoryInLibraryPath());
107 process.start(HostOsInfo::appendExecutableSuffix(jdkTools["java"]),
108 QStringList() << "-jar" << "jar_file.jar");
109 if (process.waitForStarted()) {
110 QVERIFY2(process.waitForFinished(), qPrintable(process.errorString()));
111 QVERIFY2(process.exitCode() == 0, process.readAllStandardError().constData());
112 const QByteArray stdOut = process.readAllStandardOutput();
113 QVERIFY2(stdOut.contains("Driving!"), stdOut.constData());
114 QVERIFY2(stdOut.contains("Flying!"), stdOut.constData());
115 QVERIFY2(stdOut.contains("Flying (this is a space ship)!"), stdOut.constData());
116 QVERIFY2(stdOut.contains("Sailing!"), stdOut.constData());
117 QVERIFY2(stdOut.contains("Native code performing complex internal combustion process ("),
118 stdOut.constData());
119 }
120
121 process.start("unzip", QStringList() << "-p" << "jar_file.jar");
122 if (process.waitForStarted()) {
123 QVERIFY2(process.waitForFinished(), qPrintable(process.errorString()));
124 const QByteArray stdOut = process.readAllStandardOutput();
125 QVERIFY2(stdOut.contains("Class-Path: random_stuff.jar car_jar.jar"), stdOut.constData());
126 QVERIFY2(stdOut.contains("Main-Class: Vehicles"), stdOut.constData());
127 QVERIFY2(stdOut.contains("Some-Property: Some-Value"), stdOut.constData());
128 QVERIFY2(stdOut.contains("Additional-Property: Additional-Value"), stdOut.constData());
129 QVERIFY2(stdOut.contains("Extra-Property: Crazy-Value"), stdOut.constData());
130 }
131 }
132
dpkgArch(const QString & prefix=QString ())133 static QString dpkgArch(const QString &prefix = QString())
134 {
135 QProcess dpkg;
136 dpkg.start("/usr/bin/dpkg", QStringList() << "--print-architecture");
137 dpkg.waitForFinished();
138 if (dpkg.exitStatus() == QProcess::NormalExit && dpkg.exitCode() == 0)
139 return prefix + QString::fromLocal8Bit(dpkg.readAllStandardOutput().trimmed());
140 return {};
141 }
142
javaDependencyTracking()143 void TestBlackboxJava::javaDependencyTracking()
144 {
145 QFETCH(QString, jdkPath);
146 QFETCH(QString, javaVersion);
147
148 QDir::setCurrent(testDataDir + "/java");
149 QbsRunParameters rp;
150 rp.arguments.push_back("--check-outputs");
151 if (!jdkPath.isEmpty())
152 rp.arguments << ("modules.java.jdkPath:" + jdkPath);
153 if (!javaVersion.isEmpty())
154 rp.arguments << ("modules.java.languageVersion:'" + javaVersion + "'");
155 rmDirR(relativeBuildDir());
156 const bool defaultJdkPossiblyTooOld = jdkPath.isEmpty() && !javaVersion.isEmpty();
157 rp.expectFailure = defaultJdkPossiblyTooOld;
158 QVERIFY(runQbs(rp) == 0
159 || (defaultJdkPossiblyTooOld && m_qbsStderr.contains("invalid source release")));
160 }
161
javaDependencyTracking_data()162 void TestBlackboxJava::javaDependencyTracking_data()
163 {
164 QTest::addColumn<QString>("jdkPath");
165 QTest::addColumn<QString>("javaVersion");
166
167 const SettingsPtr s = settings();
168 Profile p(profileName(), s.get());
169
170 auto getSpecificJdkVersion = [](const QString &jdkVersion) -> QString {
171 if (HostOsInfo::isMacosHost()) {
172 QProcess java_home;
173 java_home.start("/usr/libexec/java_home", QStringList() << "--version" << jdkVersion);
174 java_home.waitForFinished();
175 if (java_home.exitStatus() == QProcess::NormalExit && java_home.exitCode() == 0)
176 return QString::fromLocal8Bit(java_home.readAllStandardOutput().trimmed());
177 } else if (HostOsInfo::isWindowsHost()) {
178 QSettings settings("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\"
179 + jdkVersion, QSettings::NativeFormat);
180 return settings.value("JavaHome").toString();
181 } else {
182 QString minorVersion = jdkVersion;
183 if (minorVersion.startsWith("1."))
184 minorVersion.remove(0, 2);
185
186 const QStringList searchPaths = {
187 "/usr/lib/jvm/java-" + minorVersion + "-openjdk" + dpkgArch("-"), // Debian
188 "/usr/lib/jvm/java-" + minorVersion + "-openjdk", // Arch
189 "/usr/lib/jvm/jre-1." + minorVersion + ".0-openjdk", // Fedora
190 "/usr/lib64/jvm/java-1." + minorVersion + ".0-openjdk", // OpenSuSE
191 };
192 for (const QString &searchPath : searchPaths) {
193 if (QFile::exists(searchPath + "/bin/javac"))
194 return searchPath;
195 }
196 }
197
198 return {};
199 };
200
201 static const auto knownJdkVersions = QStringList() << "1.7" << "1.8" << "1.9"
202 << QString(); // default JDK;
203 QStringList seenJdkVersions;
204 for (const auto &jdkVersion : knownJdkVersions) {
205 QString specificJdkPath = getSpecificJdkVersion(jdkVersion);
206 if (jdkVersion.isEmpty() || !specificJdkPath.isEmpty()) {
207 const auto jdkPath = jdkVersion.isEmpty() ? jdkVersion : specificJdkPath;
208
209 if (!jdkVersion.isEmpty())
210 seenJdkVersions << jdkVersion;
211
212 if (!seenJdkVersions.empty()) {
213 const auto javaVersions = QStringList()
214 << knownJdkVersions.mid(0, knownJdkVersions.indexOf(seenJdkVersions.last()) + 1)
215 << QString(); // also test with no explicitly specified source version
216
217 for (const auto ¤tJavaVersion : javaVersions) {
218 const QString rowName = (!jdkPath.isEmpty() ? jdkPath : "default JDK")
219 + QStringLiteral(", ")
220 + (!currentJavaVersion.isEmpty()
221 ? ("Java " + currentJavaVersion)
222 : "default Java version");
223 QTest::newRow(rowName.toLatin1().constData())
224 << jdkPath << currentJavaVersion;
225 }
226 }
227 }
228 }
229
230 if (seenJdkVersions.empty())
231 QSKIP("No JDKs installed");
232 }
233
javaDependencyTrackingInnerClass()234 void TestBlackboxJava::javaDependencyTrackingInnerClass()
235 {
236 const SettingsPtr s = settings();
237 Profile p(profileName(), s.get());
238
239 QDir::setCurrent(testDataDir + "/java/inner-class");
240 QbsRunParameters params;
241 int status = runQbs(params);
242 if (p.value("java.jdkPath").toString().isEmpty()
243 && status != 0 && m_qbsStderr.contains("jdkPath")) {
244 QSKIP("java.jdkPath not set and automatic detection failed");
245 }
246 QCOMPARE(status, 0);
247 }
248
249 QTEST_MAIN(TestBlackboxJava)
250