1 parent
ebf8ff7869
commit
41599fa82f
67 files changed
+8775
-4
No files matched your search
@@ -0,0 +1,273 @@
|
||||
#include "testenv.h"
|
||||
#include "fakemojang.h"
|
||||
#include "fixtures.h"
|
||||
#include "nettest.h"
|
||||
|
||||
#include "launcherpaths.h"
|
||||
#include "localization.h"
|
||||
#include "minecraftversion.h"
|
||||
#include "modloaderinstaller.h"
|
||||
#include "modloaderversionservice.h"
|
||||
#include "versioninstaller.h"
|
||||
#include "versionmanifestservice.h"
|
||||
#include "zlibreference.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QSignalSpy>
|
||||
|
||||
namespace {
|
||||
|
||||
const QString kFabricList = QStringLiteral("https://meta.fabricmc.net/v2/versions/loader/1.20.1");
|
||||
const QString kFabricProfile = QStringLiteral("https://meta.fabricmc.net/v2/versions/loader/1.20.1/0.16.10/profile/json");
|
||||
const QString kForgeMeta = QStringLiteral("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml");
|
||||
const QString kForgePromos = QStringLiteral("https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json");
|
||||
const QString kForgeInstaller = QStringLiteral(
|
||||
"https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.1-47.4.0/forge-1.20.1-47.4.0-installer.jar");
|
||||
|
||||
QByteArray fabricProfile(const QString &inherits = QStringLiteral("1.20.1"))
|
||||
{
|
||||
// Профиль Fabric: дочерняя версия с собственной библиотекой из Maven Fabric.
|
||||
const QJsonObject json{
|
||||
{"id", "fabric-loader-0.16.10-1.20.1"},
|
||||
{"inheritsFrom", inherits},
|
||||
{"type", "release"},
|
||||
{"mainClass", "net.fabricmc.loader.impl.launch.knot.KnotClient"},
|
||||
{"arguments", QJsonObject{{"game", QJsonArray{}}, {"jvm", QJsonArray{"-DFabricMcEmu= net.minecraft.client.main.Main "}}}},
|
||||
{"libraries", QJsonArray{QJsonObject{{"name", "net.fabricmc:fabric-loader:0.16.10"},
|
||||
{"url", "https://maven.fabricmc.net/"}}}}};
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class TestModLoaderInstaller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
FakeInternet m_net;
|
||||
QString m_game;
|
||||
|
||||
struct Rig
|
||||
{
|
||||
VersionManifestService manifest;
|
||||
ModLoaderVersionService meta;
|
||||
VersionInstaller versions{&manifest};
|
||||
ModLoaderInstaller installer{&meta, &versions};
|
||||
QSignalSpy finished{&installer, &ModLoaderInstaller::finished};
|
||||
QSignalSpy failed{&installer, &ModLoaderInstaller::failed};
|
||||
QSignalSpy canceled{&installer, &ModLoaderInstaller::canceled};
|
||||
QSignalSpy log{&installer, &ModLoaderInstaller::log};
|
||||
|
||||
bool waitEnd()
|
||||
{
|
||||
return QTest::qWaitFor([&] { return finished.count() + failed.count() + canceled.count() > 0; }, 30000);
|
||||
}
|
||||
QString failure() const { return failed.isEmpty() ? QString() : failed.constFirst().at(1).toString(); }
|
||||
};
|
||||
|
||||
void routeForge()
|
||||
{
|
||||
m_net.route(kForgeMeta, FakeResponse::bytes(Fixtures::read("loaders/forge-maven-metadata.xml")));
|
||||
m_net.route(kForgePromos, FakeResponse::json(Fixtures::read("loaders/forge-promotions_slim.json")));
|
||||
m_net.route(kForgeInstaller, FakeResponse::bytes("PK fake forge installer"));
|
||||
}
|
||||
|
||||
// Подставная java, которая ведёт себя как установщик Forge: кладёт профиль версии.
|
||||
QString forgeLikeJava(const QStringList &extra = {})
|
||||
{
|
||||
const QString id = QStringLiteral("1.20.1-forge-47.4.0");
|
||||
const QString json = QStringLiteral(
|
||||
"{\"id\":\"%1\",\"inheritsFrom\":\"1.20.1\",\"mainClass\":\"cpw.mods.bootstraplauncher.BootstrapLauncher\"}").arg(id);
|
||||
QStringList config{"args_file=" + m_game + "/installer-args.txt",
|
||||
"dump_env=LD_PRELOAD",
|
||||
"stdout=Extracting json",
|
||||
"stdout=The client installed successfully"};
|
||||
config << "create=" + m_game + "/versions/" + id + u'/' + id + ".json\t" + json;
|
||||
config += extra;
|
||||
return Fixtures::installFakeJava(m_game + "/jdk17", "17.0.8", config);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase() { BILAUNCHER_START_FAKE_INTERNET(m_net); }
|
||||
|
||||
void init()
|
||||
{
|
||||
TestEnv::resetLauncherRoot();
|
||||
m_net.reset();
|
||||
m_game = TestEnv::freshDir(QStringLiteral("game"));
|
||||
FakeMojang mojang(m_net);
|
||||
mojang.publish({.id = "1.20.1"});
|
||||
m_net.route(kFabricList, FakeResponse::json(Fixtures::read("loaders/fabric-1.20.1.json")));
|
||||
m_net.route("https://maven.fabricmc.net/net/fabricmc/fabric-loader/0.16.10/fabric-loader-0.16.10.jar",
|
||||
FakeResponse::bytes("fabric-loader-jar"));
|
||||
}
|
||||
|
||||
void fabricInstallsBaseThenProfile()
|
||||
{
|
||||
m_net.route(kFabricProfile, FakeResponse::json(fabricProfile()));
|
||||
Rig rig;
|
||||
QSignalSpy started(&rig.installer, &ModLoaderInstaller::started);
|
||||
rig.installer.install(m_game, ModLoader::Fabric, "1.20.1", "0.16.10", QString());
|
||||
QVERIFY(rig.installer.isRunning());
|
||||
QCOMPARE(started.count(), 1);
|
||||
QVERIFY(rig.waitEnd());
|
||||
QVERIFY2(rig.failed.isEmpty(), qPrintable(rig.failure()));
|
||||
const QList<QVariant> done = rig.finished.constFirst();
|
||||
QCOMPARE(done.at(0).toString(), QStringLiteral("fabric"));
|
||||
QCOMPARE(done.at(1).toString(), QStringLiteral("1.20.1"));
|
||||
QCOMPARE(done.at(2).toString(), QStringLiteral("0.16.10"));
|
||||
QCOMPARE(done.at(3).toString(), QStringLiteral("fabric-loader-0.16.10-1.20.1"));
|
||||
QVERIFY(!rig.installer.isRunning());
|
||||
|
||||
// Ваниль докачана, профиль сохранён, библиотека загрузчика на месте.
|
||||
QVERIFY(QFile::exists(m_game + "/versions/1.20.1/1.20.1.jar"));
|
||||
QVERIFY(QFile::exists(m_game + "/versions/fabric-loader-0.16.10-1.20.1/fabric-loader-0.16.10-1.20.1.json"));
|
||||
QCOMPARE(Fixtures::readFile(m_game + "/libraries/net/fabricmc/fabric-loader/0.16.10/fabric-loader-0.16.10.jar"),
|
||||
QByteArray("fabric-loader-jar"));
|
||||
const MinecraftVersion v = VersionLoader::load(m_game, "fabric-loader-0.16.10-1.20.1", {}, nullptr);
|
||||
QVERIFY(v.isValid());
|
||||
}
|
||||
|
||||
void fabricProfileForWrongGameIsRejected()
|
||||
{
|
||||
m_net.route(kFabricProfile, FakeResponse::json(fabricProfile("1.19.4")));
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Fabric, "1.20.1", "0.16.10", QString());
|
||||
QVERIFY(rig.waitEnd());
|
||||
QCOMPARE(rig.failure(), Loc::text("loader.error.descriptionWrongMinecraft")
|
||||
.arg(loaderTitle(ModLoader::Fabric), "1.19.4", "1.20.1"));
|
||||
QVERIFY(!QDir(m_game + "/versions/fabric-loader-0.16.10-1.20.1").exists());
|
||||
}
|
||||
|
||||
void fabricProfileWithUnsafeIdIsRejected()
|
||||
{
|
||||
m_net.route(kFabricProfile, FakeResponse::json(QJsonObject{{"id", "../evil"}, {"inheritsFrom", "1.20.1"}}));
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Fabric, "1.20.1", "0.16.10", QString());
|
||||
QVERIFY(rig.waitEnd());
|
||||
QCOMPARE(rig.failure(), Loc::text("loader.error.descriptionBadId").arg(loaderTitle(ModLoader::Fabric)));
|
||||
}
|
||||
|
||||
void validationErrors()
|
||||
{
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Fabric, "1.20.1", " ", QString());
|
||||
QCOMPARE(rig.failure(), Loc::text("loader.error.noVersionSelected").arg(loaderTitle(ModLoader::Fabric)));
|
||||
QVERIFY(!rig.installer.isRunning());
|
||||
}
|
||||
|
||||
void secondInstallWhileBusy()
|
||||
{
|
||||
FakeResponse slow = FakeResponse::json(fabricProfile());
|
||||
slow.delayMs = 500;
|
||||
m_net.route(kFabricProfile, slow);
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Fabric, "1.20.1", "0.16.10", QString());
|
||||
rig.installer.install(m_game, ModLoader::Quilt, "1.20.1", "0.26.3", QString());
|
||||
QCOMPARE(rig.failed.count(), 1);
|
||||
QCOMPARE(rig.failure(), Loc::text("loader.error.installInProgress"));
|
||||
QTRY_COMPARE_WITH_TIMEOUT(rig.finished.count(), 1, 30000);
|
||||
}
|
||||
|
||||
void forgeRunsInstallerWithJava()
|
||||
{
|
||||
routeForge();
|
||||
const QString java = forgeLikeJava();
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Forge, "1.20.1", "47.4.0", java);
|
||||
QVERIFY(rig.waitEnd());
|
||||
QVERIFY2(rig.failed.isEmpty(), qPrintable(rig.failure()));
|
||||
QCOMPARE(rig.finished.constFirst().at(3).toString(), QStringLiteral("1.20.1-forge-47.4.0"));
|
||||
|
||||
// Установщик запущен как java -jar <installer> --installClient <папка игры>.
|
||||
const QStringList args = QString::fromLocal8Bit(Fixtures::readFile(m_game + "/installer-args.txt"))
|
||||
.split(u'\n', Qt::SkipEmptyParts);
|
||||
QCOMPARE(args.value(0), QStringLiteral("-jar"));
|
||||
QCOMPARE(QFileInfo(args.value(1)).fileName(), QStringLiteral("forge-47.4.0-installer.jar"));
|
||||
QCOMPARE(args.value(2), QStringLiteral("--installClient"));
|
||||
QCOMPARE(args.value(3), QDir::toNativeSeparators(m_game));
|
||||
// Заглушка launcher_profiles.json, без которой установщик Forge отказывается работать.
|
||||
QVERIFY(Fixtures::readJsonFile(m_game + "/launcher_profiles.json").contains("profiles"));
|
||||
// Вывод установщика идёт в журнал.
|
||||
QStringList lines;
|
||||
for (const auto &call : rig.log)
|
||||
lines << call.constFirst().toString();
|
||||
QVERIFY(lines.contains("The client installed successfully"));
|
||||
QVERIFY(QFile::exists(LauncherPaths::loaderDownloadDir() + "/forge-47.4.0-install.log"));
|
||||
}
|
||||
|
||||
void forgeInstallerFailureCleansUp()
|
||||
{
|
||||
routeForge();
|
||||
const QString java = forgeLikeJava({"exit=1", "stdout=Processor failed, invalid outputs"});
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Forge, "1.20.1", "47.4.0", java);
|
||||
QVERIFY(rig.waitEnd());
|
||||
const QString failure = rig.failure();
|
||||
QVERIFY(failure.startsWith(Loc::text("loader.error.installerExitCode").arg(loaderTitle(ModLoader::Forge)).arg(1)));
|
||||
QVERIFY(failure.contains("Processor failed")); // хвост лога в сообщении
|
||||
// Версию, которую успел создать упавший установщик, убираем.
|
||||
QVERIFY(!QDir(m_game + "/versions/1.20.1-forge-47.4.0").exists());
|
||||
QVERIFY(QDir(m_game + "/versions/1.20.1").exists());
|
||||
}
|
||||
|
||||
void forgeInstallerProducingNothing()
|
||||
{
|
||||
routeForge();
|
||||
const QString java = Fixtures::installFakeJava(m_game + "/jdk17-empty", "17.0.8", {"stdout=did nothing"});
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Forge, "1.20.1", "47.4.0", java);
|
||||
QVERIFY(rig.waitEnd());
|
||||
QVERIFY(rig.failure().startsWith(Loc::text("loader.error.noVersionProduced").section(u'%', 0, 0)));
|
||||
}
|
||||
|
||||
void forgeUnknownVersionIsIncompatible()
|
||||
{
|
||||
routeForge();
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Forge, "1.20.1", "1.0.0", QString());
|
||||
QVERIFY(rig.waitEnd());
|
||||
QCOMPARE(rig.failure(), Loc::text("loader.error.incompatible").arg(loaderTitle(ModLoader::Forge), "1.0.0", "1.20.1"));
|
||||
}
|
||||
|
||||
void zlibNgEnvironmentOnLinux()
|
||||
{
|
||||
#if !defined(Q_OS_LINUX)
|
||||
QSKIP("LD_PRELOAD с эталонным zlib — только Linux");
|
||||
#else
|
||||
routeForge();
|
||||
const QString java = forgeLikeJava();
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Forge, "1.20.1", "47.4.0", java);
|
||||
QVERIFY(rig.waitEnd());
|
||||
const QStringList args = QString::fromLocal8Bit(Fixtures::readFile(m_game + "/installer-args.txt"))
|
||||
.split(u'\n', Qt::SkipEmptyParts);
|
||||
const QString preload = args.filter("env:LD_PRELOAD=").value(0).mid(int(sizeof("env:LD_PRELOAD=") - 1));
|
||||
if (ZlibReference::systemIsZlibNg() && !ZlibReference::bundledPath().isEmpty())
|
||||
QVERIFY(preload.startsWith(ZlibReference::bundledPath()));
|
||||
else
|
||||
QCOMPARE(preload, qEnvironmentVariable("LD_PRELOAD"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cancelDuringInstallerDownload()
|
||||
{
|
||||
routeForge();
|
||||
FakeResponse hang;
|
||||
hang.hang = true;
|
||||
m_net.route(kForgeInstaller, hang);
|
||||
Rig rig;
|
||||
rig.installer.install(m_game, ModLoader::Forge, "1.20.1", "47.4.0", QString());
|
||||
QTRY_COMPARE_WITH_TIMEOUT(m_net.hits(kForgeInstaller), 1, 30000);
|
||||
rig.installer.cancel();
|
||||
QVERIFY(rig.waitEnd());
|
||||
QCOMPARE(rig.canceled.count(), 1);
|
||||
QVERIFY(rig.failed.isEmpty());
|
||||
QVERIFY(!rig.installer.isRunning());
|
||||
}
|
||||
};
|
||||
|
||||
BILAUNCHER_TEST_MAIN(TestModLoaderInstaller)
|
||||
#include "tst_modloaderinstaller.moc"
|
||||
Reference in new issue
Block a user