1 parent
41599fa82f
commit
0dd6bef715
12 files changed
+161
-64
No files matched your search
@@ -21,6 +21,19 @@
|
||||
|
||||
На Linux папка лаунчера получается `~/BiLauncher`, без точки, то есть видимая.
|
||||
|
||||
## BILAUNCHER_HOME
|
||||
|
||||
`homeOverride()` читает переменную окружения `BILAUNCHER_HOME`. Если в ней абсолютный путь, он
|
||||
заменяет домашнюю папку (`~` и `%USERPROFILE%` в таблице выше) для всех путей лаунчера, включая
|
||||
папку игры по умолчанию. Пустое или относительное значение не учитывается. Остальной Qt
|
||||
(`QDir::homePath()`, `QStandardPaths`) эту переменную не видит.
|
||||
|
||||
Переменная нужна тестам и дымовой проверке на Windows: там `QDir::homePath()` спрашивает профиль
|
||||
у системы и `USERPROFILE` игнорирует, так что иначе их нельзя увести из настоящего
|
||||
`%APPDATA%\BiLauncher`. Новых возможностей она не даёт: кто управляет окружением процесса, на
|
||||
Linux и macOS и так подменит `HOME`. Чтобы случайно оставленную переменную было видно, `main.cpp`
|
||||
при старте пишет в лог предупреждение с итоговой папкой лаунчера.
|
||||
|
||||
Внутри `rootDir()`:
|
||||
|
||||
```
|
||||
|
||||
+8
-7
@@ -70,13 +70,14 @@ CMake, тесты появятся в панели «Тесты».
|
||||
|
||||
## Как тесты изолированы
|
||||
|
||||
**Файлы.** `LauncherPaths` строит пути от `QDir::homePath()`. Общий `main` тестов
|
||||
(`tests/common/testenv.h`) до создания приложения подменяет `HOME` на временную папку, и каждый
|
||||
процесс работает в своей. Если путь лаунчера всё же оказался вне песочницы, процесс сразу
|
||||
завершается. На Windows `QDir::homePath()` переменные окружения не читает, поэтому там тесты идут
|
||||
в настоящей `%APPDATA%\BiLauncher` учётной записи раннера, строго по одному, а за собой папку
|
||||
стирают. Если папка уже есть и создана не тестами (нет метки `.bilauncher-test-sandbox`), тесты
|
||||
отказываются запускаться.
|
||||
**Файлы.** `LauncherPaths` строит пути от домашней папки. Общий `main` тестов
|
||||
(`tests/common/testenv.h`) до создания приложения направляет `BILAUNCHER_HOME` (а заодно `HOME`
|
||||
и `USERPROFILE`) на временную папку, и каждый процесс работает в своей, на всех ОС, параллельно.
|
||||
`BILAUNCHER_HOME` нужна ради Windows: там `QDir::homePath()` берёт профиль у системы и `HOME`
|
||||
с `USERPROFILE` не читает. Дочерние процессы наследуют переменную. Если папка лаунчера или папка
|
||||
игры по умолчанию всё же оказалась вне песочницы, процесс сразу завершается с кодом 2 и ничего не
|
||||
трогает. Дымовая проверка установленной сборки на Windows (`smoke_installed.ps1`) тоже запускает
|
||||
лаунчер с `BILAUNCHER_HOME` на временную папку.
|
||||
|
||||
**Сеть.** Адреса серверов в сервисах зашиты константами, а свои `QNetworkAccessManager` они
|
||||
создают сами. Зато прокси и SSL-настройки у Qt общие на процесс, и код лаунчера их не трогает.
|
||||
|
||||
+6
-1
@@ -8,6 +8,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QtCore/private/qzipreader_p.h>
|
||||
|
||||
namespace {
|
||||
@@ -46,7 +47,11 @@ bool isNativeLibraryFile(const QString &fileName)
|
||||
for (const QString &suffix : suffixes)
|
||||
if (fileName.endsWith(suffix, Qt::CaseInsensitive))
|
||||
return true;
|
||||
return fileName.contains(QStringLiteral(".so.")); // libfoo.so.1
|
||||
// Версионированные so (libfoo.so.1, libfoo.so.1.2.3). Хвост — строго цифры,
|
||||
// иначе в натив попадают служебные файлы LWJGL вроде liblwjgl.so.sha1 / .so.git.
|
||||
static const QRegularExpression versionedSo(QStringLiteral("\\.so(\\.\\d+)+$"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
return versionedSo.match(fileName).hasMatch();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+20
-3
@@ -3,21 +3,38 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace {
|
||||
|
||||
const QString kFolderName = QStringLiteral("BiLauncher");
|
||||
|
||||
// Домашняя папка, от которой строятся все пути (см. LauncherPaths::homeOverride()).
|
||||
QString homeBase()
|
||||
{
|
||||
const QString overridden = LauncherPaths::homeOverride();
|
||||
return overridden.isEmpty() ? QDir::homePath() : overridden;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString LauncherPaths::homeOverride()
|
||||
{
|
||||
// Относительный путь зависел бы от текущей папки процесса — такой не принимаем.
|
||||
const QString value = QDir::fromNativeSeparators(qEnvironmentVariable("BILAUNCHER_HOME"));
|
||||
if (value.isEmpty() || !QDir::isAbsolutePath(value))
|
||||
return QString();
|
||||
return QDir::cleanPath(value);
|
||||
}
|
||||
|
||||
QString LauncherPaths::containerDir()
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
return QDir::homePath() + QStringLiteral("/AppData/Roaming");
|
||||
return homeBase() + QStringLiteral("/AppData/Roaming");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return QDir::homePath() + QStringLiteral("/Library/Application Support");
|
||||
return homeBase() + QStringLiteral("/Library/Application Support");
|
||||
#else // Linux и прочие Unix-системы
|
||||
return QDir::homePath();
|
||||
return homeBase();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
|
||||
namespace LauncherPaths {
|
||||
|
||||
// Все пути строятся от домашней папки пользователя (QDir::homePath()). Переменная
|
||||
// окружения BILAUNCHER_HOME с абсолютным путём подменяет её — только для путей
|
||||
// лаунчера (папка лаунчера и папка игры по умолчанию), остальной Qt её не видит.
|
||||
// Нужна тестам и дымовой проверке на Windows: там QDir::homePath() спрашивает
|
||||
// профиль у системы и HOME/USERPROFILE игнорирует. Пустое или относительное
|
||||
// значение не учитывается. Возвращает подменённую папку или пустую строку.
|
||||
QString homeOverride();
|
||||
|
||||
QString containerDir();
|
||||
QString rootDir();
|
||||
QString defaultMinecraftDir();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "launcherpaths.h"
|
||||
#include "localization.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QGuiApplication>
|
||||
#include <QIcon>
|
||||
#include <QQmlApplicationEngine>
|
||||
@@ -33,6 +36,10 @@ int main(int argc, char *argv[])
|
||||
|
||||
QGuiApplication::setDesktopFileName(QStringLiteral("bilauncher"));
|
||||
|
||||
// Случайно оставленная BILAUNCHER_HOME увела бы данные в чужую папку — пусть это будет видно в логе.
|
||||
if (!LauncherPaths::homeOverride().isEmpty())
|
||||
qWarning().noquote() << "BILAUNCHER_HOME: данные лаунчера в" << QDir::toNativeSeparators(LauncherPaths::rootDir());
|
||||
|
||||
if (!Localization::instance().load())
|
||||
return -1;
|
||||
|
||||
|
||||
@@ -116,8 +116,6 @@ function(bilauncher_add_test name)
|
||||
TIMEOUT ${ARG_TIMEOUT}
|
||||
ENVIRONMENT "${BILAUNCHER_TEST_ENV}")
|
||||
if(WIN32)
|
||||
# На Windows все тесты делят настоящую %APPDATA%\BiLauncher (см. common/testenv.h).
|
||||
set_tests_properties(${name} PROPERTIES RESOURCE_LOCK launcher_profile)
|
||||
# DLL Qt — из установки Qt, без windeployqt для каждого теста.
|
||||
set_property(TEST ${name} APPEND PROPERTY ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:Qt6::Core>")
|
||||
|
||||
+47
-29
@@ -10,16 +10,33 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <qt_windows.h>
|
||||
#include <stdlib.h> // _wputenv_s
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
const char kMarker[] = ".bilauncher-test-sandbox";
|
||||
|
||||
QTemporaryDir *g_sandbox = nullptr;
|
||||
bool g_realProfile = false; // Windows: работаем в настоящем профиле под меткой
|
||||
|
||||
[[noreturn]] void abortSetup(const QString &message)
|
||||
{
|
||||
std::fprintf(stderr, "TestEnv: %s\n", qPrintable(message));
|
||||
const QString line = QStringLiteral("TestEnv: ") + message + u'\n';
|
||||
#if defined(Q_OS_WIN)
|
||||
// В живую консоль пишем UTF-16 напрямую: кодовая страница консоли тогда не важна.
|
||||
const HANDLE handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
DWORD mode = 0;
|
||||
if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &mode)) {
|
||||
DWORD written = 0;
|
||||
WriteConsoleW(handle, reinterpret_cast<const wchar_t *>(line.utf16()),
|
||||
DWORD(line.size()), &written, nullptr);
|
||||
std::exit(2);
|
||||
}
|
||||
#endif
|
||||
// Вывод в трубу (ctest, CI) — UTF-8, как его и читает ctest. Локальная 8-битная
|
||||
// кодировка Windows (cp1252/cp1251) превращала кириллицу в «???».
|
||||
std::fputs(line.toUtf8().constData(), stderr);
|
||||
std::fflush(stderr);
|
||||
std::exit(2);
|
||||
}
|
||||
|
||||
@@ -31,15 +48,16 @@ void TestEnv::prepareSandbox()
|
||||
if (!g_sandbox->isValid())
|
||||
abortSetup(QStringLiteral("не удалось создать временную папку"));
|
||||
|
||||
const QByteArray home = QDir::toNativeSeparators(g_sandbox->path()).toLocal8Bit();
|
||||
qputenv("HOME", home);
|
||||
qputenv("USERPROFILE", home);
|
||||
const QString home = QDir::toNativeSeparators(g_sandbox->path());
|
||||
setEnvPath("HOME", home);
|
||||
setEnvPath("USERPROFILE", home);
|
||||
// LauncherPaths: на Windows QDir::homePath() HOME/USERPROFILE не читает, а эту
|
||||
// переменную лаунчер читает везде. Дочерние процессы получают её по наследству.
|
||||
setEnvPath("BILAUNCHER_HOME", home);
|
||||
// Чужая Java из окружения машины не должна влиять на выбор Java в тестах.
|
||||
qunsetenv("JAVA_HOME");
|
||||
|
||||
std::atexit([] {
|
||||
if (g_realProfile)
|
||||
QDir(LauncherPaths::rootDir()).removeRecursively();
|
||||
delete g_sandbox;
|
||||
g_sandbox = nullptr;
|
||||
});
|
||||
@@ -52,19 +70,13 @@ void TestEnv::finishSetup()
|
||||
|
||||
const QString root = QDir::cleanPath(LauncherPaths::rootDir());
|
||||
const QString sandbox = QDir::cleanPath(g_sandbox->path());
|
||||
if (!root.startsWith(sandbox + u'/')) {
|
||||
#if defined(Q_OS_WIN)
|
||||
if (QDir(root).exists() && !QFile::exists(root + u'/' + QLatin1String(kMarker))) {
|
||||
abortSetup(QStringLiteral(
|
||||
"%1 уже существует и создана не тестами — отказываюсь её трогать. "
|
||||
"Запускайте тесты под отдельной учётной записью.")
|
||||
.arg(QDir::toNativeSeparators(root)));
|
||||
}
|
||||
g_realProfile = true;
|
||||
resetLauncherRoot();
|
||||
#else
|
||||
abortSetup(QStringLiteral("папка лаунчера %1 вне песочницы %2").arg(root, sandbox));
|
||||
#endif
|
||||
const QString game = QDir::cleanPath(LauncherPaths::defaultMinecraftDir());
|
||||
// Ни одна папка лаунчера не должна указывать на настоящий профиль: resetLauncherRoot()
|
||||
// стирает rootDir() целиком.
|
||||
for (const QString &path : {root, game}) {
|
||||
if (!path.startsWith(sandbox + u'/'))
|
||||
abortSetup(QStringLiteral("путь лаунчера %1 вне песочницы %2 — отказываюсь запускаться")
|
||||
.arg(QDir::toNativeSeparators(path), QDir::toNativeSeparators(sandbox)));
|
||||
}
|
||||
|
||||
if (!Localization::instance().load())
|
||||
@@ -72,6 +84,18 @@ void TestEnv::finishSetup()
|
||||
Localization::instance().setLanguage(QStringLiteral("en"));
|
||||
}
|
||||
|
||||
void TestEnv::setEnvPath(const char *name, const QString &path)
|
||||
{
|
||||
// На Windows qputenv() пишет в ANSI-кодировке и испортил бы, например,
|
||||
// кириллическое имя пользователя во временном пути.
|
||||
#if defined(Q_OS_WIN)
|
||||
_wputenv_s(reinterpret_cast<const wchar_t *>(QString::fromLatin1(name).utf16()),
|
||||
reinterpret_cast<const wchar_t *>(path.utf16()));
|
||||
#else
|
||||
qputenv(name, QFile::encodeName(path));
|
||||
#endif
|
||||
}
|
||||
|
||||
QString TestEnv::homeDir()
|
||||
{
|
||||
return g_sandbox->path();
|
||||
@@ -87,11 +111,5 @@ QString TestEnv::freshDir(const QString &name)
|
||||
|
||||
void TestEnv::resetLauncherRoot()
|
||||
{
|
||||
const QString root = LauncherPaths::rootDir();
|
||||
QDir(root).removeRecursively();
|
||||
if (g_realProfile) {
|
||||
QDir().mkpath(root);
|
||||
QFile marker(root + u'/' + QLatin1String(kMarker));
|
||||
marker.open(QIODevice::WriteOnly);
|
||||
}
|
||||
QDir(LauncherPaths::rootDir()).removeRecursively();
|
||||
}
|
||||
+13
-12
@@ -2,16 +2,14 @@
|
||||
|
||||
// Общая обвязка тестов: песочница для домашней папки и main для QtTest.
|
||||
//
|
||||
// LauncherPaths строит все пути от QDir::homePath(). На Linux и macOS он берёт
|
||||
// HOME, поэтому каждый тестовый процесс до создания QCoreApplication
|
||||
// подменяет HOME на свою временную папку — реальные ~/BiLauncher и .minecraft
|
||||
// машины, на которой гоняются тесты, не трогаются.
|
||||
//
|
||||
// На Windows QDir::homePath() спрашивает профиль у системы и переменные
|
||||
// окружения игнорирует. Там тесты работают в настоящей
|
||||
// %APPDATA%\BiLauncher, но только если её ещё нет либо она создана тестами
|
||||
// (в ней лежит метка .bilauncher-test-sandbox). Иначе процесс отказывается
|
||||
// запускаться, чтобы не испортить данные живого лаунчера.
|
||||
// LauncherPaths строит все пути от домашней папки. Каждый тестовый процесс до
|
||||
// создания QCoreApplication заводит свою временную папку и направляет туда
|
||||
// BILAUNCHER_HOME (её LauncherPaths читает на всех ОС), а заодно HOME и
|
||||
// USERPROFILE (их читает QDir::homePath() на Linux и macOS). На Windows
|
||||
// QDir::homePath() переменные окружения игнорирует, поэтому без BILAUNCHER_HOME
|
||||
// там не обойтись. Реальные папка лаунчера и .minecraft машины, на которой гоняются
|
||||
// тесты, не трогаются: если путь лаунчера всё же оказался вне песочницы,
|
||||
// процесс сразу завершается с кодом 2.
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
@@ -19,14 +17,17 @@
|
||||
|
||||
namespace TestEnv {
|
||||
|
||||
// До QCoreApplication: временная папка и подмена HOME/USERPROFILE.
|
||||
// До QCoreApplication: временная папка и подмена BILAUNCHER_HOME/HOME/USERPROFILE.
|
||||
void prepareSandbox();
|
||||
|
||||
// После QCoreApplication: проверка, что пути лаунчера ушли в песочницу,
|
||||
// загрузка переводов, английский язык (тексты ошибок не зависят от локали машины).
|
||||
void finishSetup();
|
||||
|
||||
// Корень песочницы — то, что сейчас возвращает QDir::homePath() в тестах.
|
||||
// Переменная окружения с путём, без потерь символов вне ANSI-кодировки на Windows.
|
||||
void setEnvPath(const char *name, const QString &path);
|
||||
|
||||
// Корень песочницы — домашняя папка, от которой LauncherPaths строит пути в тестах.
|
||||
QString homeDir();
|
||||
|
||||
// Свежая пустая папка внутри песочницы (для .minecraft, архивов и т. п.).
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
# лаунчер запускается без окна и через 10 секунд ещё жив, а в логе нет ошибок
|
||||
# загрузки модулей.
|
||||
#
|
||||
# На Windows лаунчер берёт папку профиля у системы, а не из переменных окружения,
|
||||
# поэтому работает в настоящей %APPDATA%\BiLauncher учётной записи раннера. Если
|
||||
# её до запуска не было, скрипт убирает её за собой.
|
||||
# QDir::homePath() на Windows берёт профиль у системы и USERPROFILE не читает,
|
||||
# поэтому лаунчер запускается с BILAUNCHER_HOME на временную папку (см.
|
||||
# launcherpaths.h) — настоящая %APPDATA%\BiLauncher раннера не трогается.
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Prefix,
|
||||
@@ -28,14 +28,17 @@ if (-not (Get-ChildItem (Join-Path $Prefix "bin\tls") -Filter "*.dll" -ErrorActi
|
||||
Fail "нет TLS-плагина (bin\tls) — лаунчер не сможет ходить по https"
|
||||
}
|
||||
|
||||
$profileRoot = Join-Path $env:APPDATA "BiLauncher"
|
||||
$existedBefore = Test-Path $profileRoot
|
||||
$realProfile = Join-Path $env:APPDATA "BiLauncher"
|
||||
$realExisted = Test-Path $realProfile
|
||||
|
||||
$log = Join-Path $env:TEMP ("bilauncher-smoke-" + [guid]::NewGuid().ToString("N"))
|
||||
$sandbox = Join-Path $env:TEMP ("bilauncher-smoke-" + [guid]::NewGuid().ToString("N"))
|
||||
$log = Join-Path $sandbox "log"
|
||||
New-Item -ItemType Directory -Force $log | Out-Null
|
||||
$stdout = Join-Path $log "stdout.log"
|
||||
$stderr = Join-Path $log "stderr.log"
|
||||
$profileRoot = Join-Path $sandbox "AppData\Roaming\BiLauncher"
|
||||
|
||||
$env:BILAUNCHER_HOME = $sandbox
|
||||
$env:QT_QPA_PLATFORM = "offscreen"
|
||||
$env:QT_FORCE_STDERR_LOGGING = "1"
|
||||
$env:QTWEBENGINE_CHROMIUM_FLAGS = "--disable-gpu"
|
||||
@@ -58,9 +61,9 @@ try {
|
||||
Fail "в логе ошибки загрузки модулей"
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $profileRoot "settings.json"))) { Fail "лаунчер не создал $profileRoot\settings.json" }
|
||||
if (-not $realExisted -and (Test-Path $realProfile)) { Fail "лаунчер создал настоящую $realProfile мимо BILAUNCHER_HOME" }
|
||||
Write-Host "SMOKE OK: $bin"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Recurse -Force $log -ErrorAction SilentlyContinue
|
||||
if (-not $existedBefore) { Remove-Item -Recurse -Force $profileRoot -ErrorAction SilentlyContinue }
|
||||
Remove-Item -Recurse -Force $sandbox -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -99,7 +99,11 @@ class TestGameLauncher : public QObject
|
||||
Fixtures::makeZip(m_game + "/libraries/org/lwjgl/lwjgl-3.3.1-natives.jar",
|
||||
{{QStringLiteral("META-INF/MANIFEST.MF"), "x"},
|
||||
{QStringLiteral("linux/x64/org/lwjgl/") + kNativeName, "native-bytes"},
|
||||
{QStringLiteral("skip/") + kNativeName + ".sha1", "hash"}});
|
||||
{QStringLiteral("skip/") + kNativeName + ".sha1", "hash"},
|
||||
// Служебные файлы LWJGL рядом с .so: не должны приниматься
|
||||
// за версионированную библиотеку (libfoo.so.1) ни на одной ОС.
|
||||
{QStringLiteral("skip/liblwjgl.so.sha1"), "hash"},
|
||||
{QStringLiteral("skip/liblwjgl.so.git"), "git"}});
|
||||
Fixtures::write(m_game + "/assets/indexes/5.json", "{}");
|
||||
QDir().mkpath(m_game + "/assets/objects");
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QScopeGuard>
|
||||
|
||||
class TestLauncherPaths : public QObject
|
||||
{
|
||||
@@ -22,7 +23,9 @@ private slots:
|
||||
|
||||
void containerPerPlatform()
|
||||
{
|
||||
const QString home = QDir::homePath();
|
||||
// Не QDir::homePath(): на Windows он смотрит в настоящий профиль, а пути
|
||||
// лаунчера в тестах идут от BILAUNCHER_HOME (песочницы).
|
||||
const QString home = QDir::cleanPath(TestEnv::homeDir());
|
||||
#if defined(Q_OS_WIN)
|
||||
QCOMPARE(LauncherPaths::containerDir(), home + QStringLiteral("/AppData/Roaming"));
|
||||
QCOMPARE(LauncherPaths::defaultMinecraftDir(), LauncherPaths::containerDir() + "/.minecraft");
|
||||
@@ -35,6 +38,25 @@ private slots:
|
||||
#endif
|
||||
}
|
||||
|
||||
void homeOverrideFromEnvironment()
|
||||
{
|
||||
const QString saved = qEnvironmentVariable("BILAUNCHER_HOME");
|
||||
const auto restore = qScopeGuard([&] { TestEnv::setEnvPath("BILAUNCHER_HOME", saved); });
|
||||
|
||||
QCOMPARE(LauncherPaths::homeOverride(), QDir::cleanPath(TestEnv::homeDir()));
|
||||
|
||||
const QString other = TestEnv::freshDir("other-home");
|
||||
TestEnv::setEnvPath("BILAUNCHER_HOME", QDir::toNativeSeparators(other));
|
||||
QCOMPARE(LauncherPaths::homeOverride(), QDir::cleanPath(other));
|
||||
QVERIFY(LauncherPaths::rootDir().startsWith(QDir::cleanPath(other) + u'/'));
|
||||
|
||||
// Относительный путь зависел бы от текущей папки — игнорируется, как и пустой.
|
||||
for (const char *ignored : {"relative/home", ""}) {
|
||||
qputenv("BILAUNCHER_HOME", ignored);
|
||||
QVERIFY2(LauncherPaths::homeOverride().isEmpty(), ignored);
|
||||
}
|
||||
}
|
||||
|
||||
void filesLayout_data()
|
||||
{
|
||||
QTest::addColumn<QString>("actual");
|
||||
|
||||
Reference in new issue
Block a user