1 parent
ebf8ff7869
commit
41599fa82f
67 files changed
+8775
-4
No files matched your search
@@ -0,0 +1,66 @@
|
||||
# Дымовая проверка установленного дерева (результат cmake --install) на Windows.
|
||||
#
|
||||
# powershell -File tests\scripts\smoke_installed.ps1 -Prefix C:\r\bl-smoke-stage
|
||||
#
|
||||
# Проверяет, что windeployqt положил всё нужное (DLL Qt, плагины платформ и QML),
|
||||
# лаунчер запускается без окна и через 10 секунд ещё жив, а в логе нет ошибок
|
||||
# загрузки модулей.
|
||||
#
|
||||
# На Windows лаунчер берёт папку профиля у системы, а не из переменных окружения,
|
||||
# поэтому работает в настоящей %APPDATA%\BiLauncher учётной записи раннера. Если
|
||||
# её до запуска не было, скрипт убирает её за собой.
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Prefix,
|
||||
[int]$RunSeconds = 10
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
function Fail($message) { Write-Host "SMOKE FAIL: $message"; exit 1 }
|
||||
|
||||
$bin = Join-Path $Prefix "bin\BiLauncher.exe"
|
||||
if (-not (Test-Path $bin)) { Fail "нет $bin" }
|
||||
foreach ($required in @("bin\Qt6Core.dll", "bin\Qt6Quick.dll", "bin\Qt6Network.dll",
|
||||
"bin\platforms\qwindows.dll", "bin\qml\QtQuick\Controls")) {
|
||||
if (-not (Test-Path (Join-Path $Prefix $required))) { Fail "нет $required" }
|
||||
}
|
||||
if (-not (Get-ChildItem (Join-Path $Prefix "bin\tls") -Filter "*.dll" -ErrorAction SilentlyContinue)) {
|
||||
Fail "нет TLS-плагина (bin\tls) — лаунчер не сможет ходить по https"
|
||||
}
|
||||
|
||||
$profileRoot = Join-Path $env:APPDATA "BiLauncher"
|
||||
$existedBefore = Test-Path $profileRoot
|
||||
|
||||
$log = Join-Path $env:TEMP ("bilauncher-smoke-" + [guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Force $log | Out-Null
|
||||
$stdout = Join-Path $log "stdout.log"
|
||||
$stderr = Join-Path $log "stderr.log"
|
||||
|
||||
$env:QT_QPA_PLATFORM = "offscreen"
|
||||
$env:QT_FORCE_STDERR_LOGGING = "1"
|
||||
$env:QTWEBENGINE_CHROMIUM_FLAGS = "--disable-gpu"
|
||||
|
||||
try {
|
||||
$process = Start-Process -FilePath $bin -PassThru -NoNewWindow `
|
||||
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
||||
Start-Sleep -Seconds $RunSeconds
|
||||
$alive = -not $process.HasExited
|
||||
if ($alive) { Stop-Process -Id $process.Id -Force; $process.WaitForExit(5000) | Out-Null }
|
||||
|
||||
$text = ""
|
||||
if (Test-Path $stderr) { $text += Get-Content $stderr -Raw }
|
||||
if (Test-Path $stdout) { $text += Get-Content $stdout -Raw }
|
||||
Write-Host "----- лог запуска -----"
|
||||
Write-Host $text
|
||||
|
||||
if (-not $alive) { Fail "лаунчер завершился раньше чем через $RunSeconds с (код $($process.ExitCode))" }
|
||||
if ($text -match "is not installed|Cannot load library|could not find the Qt platform plugin|QQmlApplicationEngine failed") {
|
||||
Fail "в логе ошибки загрузки модулей"
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $profileRoot "settings.json"))) { Fail "лаунчер не создал $profileRoot\settings.json" }
|
||||
Write-Host "SMOKE OK: $bin"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Recurse -Force $log -ErrorAction SilentlyContinue
|
||||
if (-not $existedBefore) { Remove-Item -Recurse -Force $profileRoot -ErrorAction SilentlyContinue }
|
||||
}
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Дымовая проверка установленного дерева (результат cmake --install) на Linux и macOS.
|
||||
#
|
||||
# tests/scripts/smoke_installed.sh <prefix>
|
||||
#
|
||||
# Linux: <prefix>/bin/BiLauncher (+ lib/BiLauncher/libz.so.1, плагины Qt рядом)
|
||||
# macOS: <prefix>/BiLauncher.app
|
||||
#
|
||||
# Проверяет, что деплой Qt положил всё нужное: бинарник не ссылается на
|
||||
# библиотеки вне дерева (ldd/otool), запускается без окна и через 10 секунд
|
||||
# ещё жив, а в логе нет «module is not installed», «Cannot load library» и т. п.
|
||||
# Запуск идёт с подменённым HOME — настоящие данные лаунчера на машине не трогаются.
|
||||
set -euo pipefail
|
||||
|
||||
PREFIX="$(cd "${1:?нужен путь к установленному дереву}" && pwd)"
|
||||
RUN_SECONDS="${RUN_SECONDS:-10}"
|
||||
fail() { echo "SMOKE FAIL: $*" >&2; exit 1; }
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
APP="$PREFIX/BiLauncher.app"
|
||||
BIN="$APP/Contents/MacOS/BiLauncher"
|
||||
[ -x "$BIN" ] || fail "нет $BIN"
|
||||
[ -f "$APP/Contents/Resources/icon.icns" ] || fail "нет иконки в бандле"
|
||||
/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist" >/dev/null \
|
||||
|| fail "в Info.plist нет версии"
|
||||
# Ссылки на Qt должны вести внутрь бандла, а не в папку Qt сборочной машины.
|
||||
if otool -L "$BIN" | tail -n +2 | grep -E '/Qt/|/Users/|/home/' ; then
|
||||
fail "бинарник ссылается на библиотеки вне бандла"
|
||||
fi
|
||||
[ -d "$APP/Contents/Frameworks/QtQuick.framework" ] || fail "нет QtQuick.framework в бандле"
|
||||
[ -d "$APP/Contents/PlugIns/platforms" ] || fail "нет плагинов платформ"
|
||||
# Деплой идёт с -hardened-runtime: без подписи dyld откажется грузить фреймворки
|
||||
# («different Team IDs»). Подписываем ad-hoc ровно как release.yml перед упаковкой.
|
||||
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 || fail "ad-hoc подпись не удалась"
|
||||
codesign --verify --deep --strict "$APP" || fail "подпись бандла не проходит проверку"
|
||||
;;
|
||||
Linux)
|
||||
BIN="$PREFIX/bin/BiLauncher"
|
||||
[ -x "$BIN" ] || fail "нет $BIN"
|
||||
[ -f "$PREFIX/lib/BiLauncher/libz.so.1" ] || fail "нет эталонного zlib (lib/BiLauncher/libz.so.1)"
|
||||
[ -f "$PREFIX/share/applications/bilauncher.desktop" ] || fail "нет .desktop-файла"
|
||||
[ -f "$PREFIX/share/icons/hicolor/256x256/apps/bilauncher.png" ] || fail "нет иконки hicolor"
|
||||
[ -f "$PREFIX/share/doc/BiLauncher/LICENSE" ] || fail "нет LICENSE в share/doc"
|
||||
# Все разделяемые библиотеки — бинарника, Qt и плагинов — должны находиться.
|
||||
missing=0
|
||||
while IFS= read -r -d '' file; do
|
||||
if ldd "$file" 2>/dev/null | grep -q 'not found'; then
|
||||
echo "не хватает библиотек для $file:" >&2
|
||||
ldd "$file" | grep 'not found' >&2
|
||||
missing=1
|
||||
fi
|
||||
done < <(find "$PREFIX" \( -name '*.so*' -o -path '*/bin/BiLauncher' \) -type f -print0)
|
||||
[ "$missing" = 0 ] || fail "ldd: есть ненайденные зависимости"
|
||||
;;
|
||||
*) fail "неизвестная система $(uname -s)" ;;
|
||||
esac
|
||||
|
||||
# Платформа Qt. Проверяем то, что реально задеплоено: macdeployqt кладёт только
|
||||
# cocoa — на macOS окно на 10 секунд действительно откроется (раннеру нужна
|
||||
# GUI-сессия). На Linux берём offscreen, если деплой его положил, иначе xcb
|
||||
# под xvfb-run (пакет xvfb, см. ci/setup-runner-linux.sh).
|
||||
LAUNCH=()
|
||||
PLATFORM=""
|
||||
if [ "$(uname -s)" = Linux ]; then
|
||||
if find "$PREFIX" -name 'libqoffscreen.so' | grep -q .; then
|
||||
PLATFORM=offscreen
|
||||
elif command -v xvfb-run >/dev/null; then
|
||||
LAUNCH=(xvfb-run -a)
|
||||
elif [ -z "${DISPLAY:-}" ]; then
|
||||
fail "нет ни плагина offscreen в деплое, ни xvfb-run, ни DISPLAY"
|
||||
fi
|
||||
fi
|
||||
|
||||
SANDBOX="$(mktemp -d)"
|
||||
LOG="$SANDBOX/stderr.log"
|
||||
trap 'rm -rf "$SANDBOX"' EXIT
|
||||
|
||||
env HOME="$SANDBOX" \
|
||||
${PLATFORM:+QT_QPA_PLATFORM=$PLATFORM} \
|
||||
QT_FORCE_STDERR_LOGGING=1 \
|
||||
QTWEBENGINE_DISABLE_SANDBOX=1 \
|
||||
QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" \
|
||||
${LAUNCH[@]+"${LAUNCH[@]}"} "$BIN" >"$LOG" 2>&1 &
|
||||
PID=$!
|
||||
|
||||
sleep "$RUN_SECONDS"
|
||||
if ! kill -0 "$PID" 2>/dev/null; then
|
||||
wait "$PID" || true
|
||||
echo "----- лог -----" >&2; cat "$LOG" >&2
|
||||
fail "лаунчер завершился раньше чем через ${RUN_SECONDS} с"
|
||||
fi
|
||||
kill "$PID" 2>/dev/null || true
|
||||
wait "$PID" 2>/dev/null || true
|
||||
|
||||
echo "----- лог запуска -----"; cat "$LOG"
|
||||
if grep -E 'is not installed|Cannot load library|could not find the Qt platform plugin|QQmlApplicationEngine failed|module ".*" plugin ".*" not found|Каталог переводов' "$LOG"; then
|
||||
fail "в логе ошибки загрузки модулей"
|
||||
fi
|
||||
|
||||
# Лаунчер создал свою папку в подменённом HOME, а не где-то ещё.
|
||||
if [ "$(uname -s)" = Darwin ]; then ROOT="$SANDBOX/Library/Application Support/BiLauncher"; else ROOT="$SANDBOX/BiLauncher"; fi
|
||||
[ -f "$ROOT/settings.json" ] || fail "лаунчер не создал $ROOT/settings.json"
|
||||
|
||||
echo "SMOKE OK: $BIN"
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Статические проверки без запуска лаунчера. Ненулевой код возврата при ошибке.
|
||||
|
||||
Запуск: python3 tests/scripts/static_checks.py <папка сборки с BILAUNCHER_BUILD_TESTS=ON>
|
||||
|
||||
1. tools/check_translations.py — каталог переводов цел и совпадает с кодом.
|
||||
2. qmllint по модулю BiLauncher (цель BiLauncher_qmllint, её создаёт qt_add_qml_module).
|
||||
В коде уже есть предупреждения, а код ради тестов не правится, поэтому
|
||||
проверка работает «храповиком»: падает, только если предупреждений стало
|
||||
больше, чем записано в tests/qmllint-baseline.txt. Стало меньше — просит
|
||||
опустить планку, чтобы достигнутое не откатилось.
|
||||
3. CHANGELOG.md содержит раздел для версии из project(BiLauncher VERSION …):
|
||||
без него release.yml создаст релиз с пустым описанием (только предупреждение).
|
||||
"""
|
||||
import os, re, subprocess, sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
BASELINE = os.path.join(ROOT, "tests", "qmllint-baseline.txt")
|
||||
|
||||
|
||||
def step(title):
|
||||
print(f"\n=== {title}", flush=True)
|
||||
|
||||
|
||||
def check_translations():
|
||||
step("Каталог переводов")
|
||||
return subprocess.call([sys.executable, os.path.join(ROOT, "tools", "check_translations.py")]) == 0
|
||||
|
||||
|
||||
def check_qmllint(build_dir):
|
||||
step("qmllint (BiLauncher_qmllint)")
|
||||
cmake = os.environ.get("CMAKE", "cmake")
|
||||
proc = subprocess.run([cmake, "--build", build_dir, "--target", "BiLauncher_qmllint"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
|
||||
encoding="utf-8", errors="replace")
|
||||
output = proc.stdout
|
||||
with open(os.path.join(build_dir, "qmllint.log"), "w", encoding="utf-8") as log:
|
||||
log.write(output)
|
||||
warnings = [line for line in output.splitlines() if line.startswith("Warning:")]
|
||||
errors = [line for line in output.splitlines() if line.startswith("Error:")]
|
||||
for line in warnings + errors:
|
||||
print(line)
|
||||
|
||||
baseline = int(open(BASELINE, encoding="utf-8").read().split()[0])
|
||||
print(f"\nпредупреждений: {len(warnings)}, ошибок: {len(errors)}, планка: {baseline}"
|
||||
f" (полный вывод — {os.path.join(build_dir, 'qmllint.log')})")
|
||||
if proc.returncode != 0 and not warnings and not errors:
|
||||
print("qmllint не запустился — см. лог")
|
||||
return False
|
||||
if errors:
|
||||
print("ОШИБКА: qmllint нашёл ошибки")
|
||||
return False
|
||||
if len(warnings) > baseline:
|
||||
print(f"ОШИБКА: предупреждений стало больше ({len(warnings)} > {baseline}). "
|
||||
"Исправьте новые или, если они осознанные, поднимите tests/qmllint-baseline.txt.")
|
||||
return False
|
||||
if len(warnings) < baseline:
|
||||
print(f"Хорошо: предупреждений меньше планки — опустите tests/qmllint-baseline.txt до {len(warnings)}.")
|
||||
return True
|
||||
|
||||
|
||||
def check_changelog():
|
||||
step("CHANGELOG.md и версия проекта")
|
||||
cmake_text = open(os.path.join(ROOT, "CMakeLists.txt"), encoding="utf-8").read()
|
||||
match = re.search(r"^project\(BiLauncher VERSION ([0-9]+\.[0-9]+\.[0-9]+)", cmake_text, re.M)
|
||||
if not match:
|
||||
print("ОШИБКА: не найден project(BiLauncher VERSION x.y.z)")
|
||||
return False
|
||||
version = match.group(1)
|
||||
changelog = open(os.path.join(ROOT, "CHANGELOG.md"), encoding="utf-8").read()
|
||||
if re.search(r"^## \[" + re.escape(version) + r"\]", changelog, re.M):
|
||||
print(f"версия {version}: раздел в CHANGELOG.md есть")
|
||||
else:
|
||||
print(f"ПРЕДУПРЕЖДЕНИЕ: в CHANGELOG.md нет раздела ## [{version}] — описание релиза будет пустым")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print(__doc__)
|
||||
return 2
|
||||
build_dir = os.path.abspath(sys.argv[1])
|
||||
results = [check_translations(), check_qmllint(build_dir), check_changelog()]
|
||||
print("\nИТОГ:", "OK" if all(results) else "ЕСТЬ ОШИБКИ")
|
||||
return 0 if all(results) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user