1 parent
ebf8ff7869
commit
41599fa82f
67 files changed
+8775
-4
No files matched your search
@@ -0,0 +1,319 @@
|
||||
#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 ×tamp, 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"
|
||||
Reference in new issue
Block a user