Files
BiLauncher/tests/net/tst_javaruntimeservice.cpp
galeon 41599fa82f
tests / linux (push) Failing after 2m18s
tests / windows (push) Skipped
tests / macos (push) Skipped
tests / package-smoke-linux (push) Skipped
tests / package-smoke-windows (push) Skipped
tests / package-smoke-macos (push) Skipped
new unit tests, hehe 67
Signed-off-by: galeon <dertiellis@gmail.com>
2026-10-12 13:14:08 +03:00

320 lines
13 KiB
C++

#include "testenv.h"
#include "fixtures.h"
#include "nettest.h"
#include "javaruntime.h"
#include "javaruntimeservice.h"
#include "launcherpaths.h"
#include "localization.h"
#include <QJsonArray>
#include <QSignalSpy>
#include <QSysInfo>
namespace {
const QString kReleases = QStringLiteral("https://api.adoptium.net/v3/info/available_releases");
const QString kFeature = QStringLiteral("https://api.adoptium.net/v3/assets/feature_releases/*");
const QString kMojang = QStringLiteral(
"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json");
bool VersionLoaderOsIsWindows()
{
#if defined(Q_OS_WIN)
return true;
#else
return false;
#endif
}
QString hostAdoptiumArch()
{
const QString arch = QSysInfo::currentCpuArchitecture();
if (arch == QLatin1String("arm64"))
return QStringLiteral("aarch64");
if (arch == QLatin1String("x86_64"))
return QStringLiteral("x64");
return arch;
}
// Один релиз Temurin в формате Adoptium API.
QJsonObject release(int major, const QString &arch, const QString &openjdk, int security,
const QString &timestamp, const QString &os)
{
const QString ext = os == QLatin1String("windows") ? QStringLiteral("zip") : QStringLiteral("tar.gz");
QJsonArray binaries;
for (const QString type : {QStringLiteral("jdk"), QStringLiteral("jre"), QStringLiteral("testimage")}) {
const QString name = QStringLiteral("OpenJDK%1U-%2_%3_%4_hotspot.%5").arg(major).arg(type, arch, os, ext);
binaries << QJsonObject{
{"architecture", arch}, {"image_type", type}, {"os", os},
{"package", QJsonObject{{"name", name},
{"link", "https://github.com/adoptium/temurin/releases/download/x/" + name},
{"checksum", QString(64, u'c')},
{"size", 190000000}}}};
}
return QJsonObject{
{"release_name", QStringLiteral("jdk-%1").arg(openjdk)},
{"timestamp", timestamp},
{"binaries", binaries},
{"version_data", QJsonObject{{"major", major}, {"security", security},
{"openjdk_version", openjdk + "-LTS"}, {"semver", openjdk}}}};
}
QByteArray mojangAll()
{
const QJsonObject components{
{"java-runtime-delta", QJsonArray{QJsonObject{
{"manifest", QJsonObject{{"url", "https://piston-meta.mojang.com/v1/packages/d/manifest.json"},
{"sha1", "dddd"}, {"size", 1000}}},
{"version", QJsonObject{{"name", "21.0.7"}, {"released", "2025-04-15T00:00:00+00:00"}}}}}},
{"jre-legacy", QJsonArray{QJsonObject{
{"manifest", QJsonObject{{"url", "https://piston-meta.mojang.com/v1/packages/l/manifest.json"},
{"sha1", "llll"}, {"size", 1000}}},
{"version", QJsonObject{{"name", "8u51"}, {"released", "2015-07-15T00:00:00+00:00"}}}}}},
{"java-runtime-alpha", QJsonArray{}},
{"minecraft-java-exe", QJsonArray{QJsonObject{
{"manifest", QJsonObject{{"url", "https://piston-meta.mojang.com/v1/packages/e/manifest.json"}}},
{"version", QJsonObject{{"name", "7.0"}}}}}},
{"insecure", QJsonArray{QJsonObject{
{"manifest", QJsonObject{{"url", "http://piston-meta.mojang.com/x.json"}}},
{"version", QJsonObject{{"name", "17.0.1"}}}}}}};
QJsonObject root;
for (const char *platform : {"gamecore", "linux", "linux-i386", "mac-os", "mac-os-arm64",
"windows-arm64", "windows-x64", "windows-x86"})
root.insert(QLatin1String(platform), components);
return QJsonDocument(root).toJson(QJsonDocument::Compact);
}
} // namespace
class TestJavaRuntimeService : public QObject
{
Q_OBJECT
FakeInternet m_net;
// Отвечает релизами на запрошенную архитектуру/ОС; без «missingForArch» — для всех.
void routeAdoptium(const QString &missingForArch = QString())
{
m_net.route(kReleases, FakeResponse::json(QJsonObject{
{"available_lts_releases", QJsonArray{8, 17, 21}},
{"available_releases", QJsonArray{8, 17, 21, 24}}}));
m_net.route(kFeature, [missingForArch](const FakeRequest &request) {
const int major = request.path.section(u'/', -2, -2).toInt();
const QString arch = request.queryItems().queryItemValue("architecture");
const QString os = request.queryItems().queryItemValue("os");
if (arch == missingForArch)
return FakeResponse::json("[]");
QJsonArray list;
if (major == 8) {
list << release(8, arch, "1.8.0_452-b09", 452, "2025-04-20T00:00:00Z", os);
} else {
list << release(major, arch, QStringLiteral("%1.0.2+13").arg(major), 2, "2025-01-20T00:00:00Z", os)
<< release(major, arch, QStringLiteral("%1.0.1+12").arg(major), 1, "2024-10-20T00:00:00Z", os);
}
return FakeResponse::json(QJsonDocument(list).toJson());
});
}
static bool load(JavaRuntimeService &service, CallResult &r, bool force = false)
{
service.ensureLoaded(r.callback(), force);
return QTest::qWaitFor([&] { return r.calls > 0; }, 20000);
}
private slots:
void initTestCase() { BILAUNCHER_START_FAKE_INTERNET(m_net); }
void init()
{
TestEnv::resetLauncherRoot();
m_net.reset();
}
void mergesAdoptiumAndMojang()
{
routeAdoptium();
m_net.route(kMojang, FakeResponse::json(mojangAll()));
JavaRuntimeService service;
QSignalSpy changed(&service, &JavaRuntimeService::catalogChanged);
CallResult r;
QVERIFY(load(service, r));
QVERIFY2(r.ok, qPrintable(r.warning));
QVERIFY(r.warning.isEmpty());
QCOMPARE(changed.count(), 1);
QVERIFY(!service.isRefreshing());
// Запросы к Adoptium — с параметрами текущей машины.
const QList<FakeRequest> requests = m_net.requests();
int featureRequests = 0;
for (const FakeRequest &request : requests) {
if (!request.path.startsWith("/v3/assets/feature_releases/"))
continue;
++featureRequests;
QCOMPARE(request.queryItems().queryItemValue("architecture"), hostAdoptiumArch());
QCOMPARE(request.queryItems().queryItemValue("image_type"), QString());
QCOMPARE(request.queryItems().queryItemValue("vendor"), QStringLiteral("eclipse"));
}
QCOMPARE(featureRequests, 4);
const QList<JavaRuntimeEntry> jdks = service.entries(JavaRuntimeKind::Jdk);
const QList<JavaRuntimeEntry> jres = service.entries(JavaRuntimeKind::Jre);
QCOMPARE(jdks.size(), 7); // 8 — один релиз, 17/21/24 — по два
QCOMPARE(jres.size(), 7);
// Свежие major первыми, внутри — по дате.
QCOMPARE(jdks.first().major, 24);
QCOMPARE(jdks.first().version, QStringLiteral("24.0.2+13"));
QCOMPARE(jdks.last().major, 8);
QCOMPARE(jdks.last().version, QStringLiteral("8u452"));
const JavaRuntimeEntry jdk21 = jdks.at(2);
QCOMPARE(jdk21.major, 21);
QCOMPARE(jdk21.version, QStringLiteral("21.0.2+13")); // «-LTS» отрезан
QVERIFY(jdk21.lts);
QVERIFY(!jdks.first().lts); // 24 — не LTS
QCOMPARE(jdk21.architecture, hostAdoptiumArch());
QCOMPARE(jdk21.size, qint64(190000000));
QCOMPARE(jdk21.archive, VersionLoaderOsIsWindows() ? QStringLiteral("zip") : QStringLiteral("tar.gz"));
QCOMPARE(jdk21.id, JavaRuntimeStore::sanitizeId("temurin-jdk-21.0.2+13-" + hostAdoptiumArch()));
QVERIFY(service.find(jdk21.id).has_value());
// Mojang: java-runtime-delta (21) и jre-legacy (8); пустые, exe и http — выброшены.
const QList<JavaRuntimeEntry> mojang = service.entries(JavaRuntimeKind::Mojang);
QCOMPARE(mojang.size(), 2);
const auto delta = service.find("mojang-java-runtime-delta");
QVERIFY(delta.has_value());
QCOMPARE(delta->major, 21);
QCOMPARE(delta->component, QStringLiteral("java-runtime-delta"));
QCOMPARE(delta->archive, QStringLiteral("mojang"));
QCOMPARE(delta->checksum, QStringLiteral("dddd"));
QCOMPARE(service.find("mojang-jre-legacy")->major, 8);
QVERIFY(QFile::exists(LauncherPaths::javaCatalogFile()));
}
void bestForPrefersSmallestSuitableMajorThenJdk()
{
routeAdoptium();
m_net.route(kMojang, FakeResponse::json(mojangAll()));
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
QVERIFY(!service.bestFor(0).has_value());
QVERIFY(!service.bestFor(99).has_value());
const auto for17 = service.bestFor(17);
QVERIFY(for17.has_value());
QCOMPARE(for17->major, 17);
QCOMPARE(for17->kind, JavaRuntimeKind::Jdk);
QCOMPARE(for17->version, QStringLiteral("17.0.2+13")); // самый свежий релиз
QCOMPARE(service.bestFor(18)->major, 21);
QCOMPARE(service.bestFor(8)->major, 8);
}
void partialFailureStillProducesCatalog()
{
routeAdoptium();
m_net.route(kMojang, FakeResponse::error(500));
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
QVERIFY(r.ok);
QVERIFY(r.warning.startsWith(Loc::text("java.error.mojangRuntimesFailed").section(u'%', 0, 0)));
QVERIFY(service.entries(JavaRuntimeKind::Mojang).isEmpty());
QVERIFY(!service.entries(JavaRuntimeKind::Jdk).isEmpty());
}
void adoptiumDownMojangUp()
{
m_net.route(kReleases, FakeResponse::error(503));
m_net.route(kMojang, FakeResponse::json(mojangAll()));
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
QVERIFY(r.ok);
QVERIFY(!r.warning.isEmpty());
QCOMPARE(service.entries(JavaRuntimeKind::Mojang).size(), 2);
QVERIFY(service.entries(JavaRuntimeKind::Jdk).isEmpty());
}
void everythingDownWithoutCacheFails()
{
m_net.route(kReleases, FakeResponse::error(503));
m_net.route(kMojang, FakeResponse::error(503));
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
QVERIFY(!r.ok);
QVERIFY(r.warning.contains("; ")); // оба предупреждения склеены
QVERIFY(!service.hasData());
}
void everythingDownWithCacheUsesCache()
{
routeAdoptium();
m_net.route(kMojang, FakeResponse::json(mojangAll()));
{
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
}
m_net.reset();
m_net.route(kReleases, FakeResponse::error(503));
m_net.route(kMojang, FakeResponse::error(503));
JavaRuntimeService service;
CallResult cached;
QVERIFY(load(service, cached));
QVERIFY(cached.ok);
QVERIFY(m_net.requests().isEmpty()); // кеш свежий — сети нет
QCOMPARE(service.entries(JavaRuntimeKind::Jdk).size(), 7);
CallResult forced;
QVERIFY(load(service, forced, true));
QVERIFY(forced.ok);
QVERIFY(!forced.warning.isEmpty());
QCOMPARE(service.entries(JavaRuntimeKind::Jdk).size(), 7);
}
void cacheForOtherPlatformIsIgnored()
{
QJsonObject entry;
entry["id"] = "x";
entry["kind"] = "jdk";
entry["major"] = 17;
entry["url"] = "https://x/y.zip";
QJsonObject cache;
cache["platform"] = "amiga-os";
cache["fetchedAt"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
cache["entries"] = QJsonArray{entry};
Fixtures::writeJson(LauncherPaths::javaCatalogFile(), cache);
m_net.route(kReleases, FakeResponse::error(503));
m_net.route(kMojang, FakeResponse::error(503));
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
QVERIFY(!service.hasData());
QVERIFY(!r.ok);
}
void aarch64FallsBackToX64()
{
if (hostAdoptiumArch() != QLatin1String("aarch64"))
QSKIP("запасной x64 запрашивается только на ARM-машинах");
routeAdoptium(QStringLiteral("aarch64"));
m_net.route(kMojang, FakeResponse::json(mojangAll()));
JavaRuntimeService service;
CallResult r;
QVERIFY(load(service, r));
QVERIFY(r.ok);
const QList<JavaRuntimeEntry> jdks = service.entries(JavaRuntimeKind::Jdk);
QVERIFY(!jdks.isEmpty());
for (const JavaRuntimeEntry &entry : jdks)
QCOMPARE(entry.architecture, QStringLiteral("x64"));
}
};
BILAUNCHER_TEST_MAIN(TestJavaRuntimeService)
#include "tst_javaruntimeservice.moc"