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 &currentJavaVersion : 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