mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
136 lines
7.4 KiB
Python
136 lines
7.4 KiB
Python
"""Wording contracts through the real metadata parser and exact-match formatter.
|
|
|
|
No post-install function or scanner is imported or executed. Node 22.6+ is
|
|
required, as in test_command_descriptions.py; no npm dependencies are needed.
|
|
"""
|
|
import ast
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
|
|
|
|
class OptimizationDescriptionWordingTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# Admit only the pure parser and its actual dependencies, not the scanner.
|
|
path = ROOT / "AppImage/scripts/post_install_versions.py"
|
|
functions = {"_read_text", "_version_tuple", "parse_post_install_script"}
|
|
constants = {"_FN_DEF_RE", "_VERSION_RE", "_DESC_RE", "_REGISTER_RE", "_HEREDOC_RE"}
|
|
nodes: list[ast.stmt] = [node for node in ast.parse(path.read_text(encoding="utf-8")).body
|
|
if (isinstance(node, ast.FunctionDef) and node.name in functions)
|
|
or (isinstance(node, ast.Assign) and any(
|
|
isinstance(target, ast.Name) and target.id in constants
|
|
for target in node.targets))]
|
|
if len(nodes) != len(functions) + len(constants):
|
|
raise AssertionError("Metadata parser seam changed: review its dependencies")
|
|
scope = {"re": re, "Path": Path}
|
|
exec(compile(ast.Module(body=nodes, type_ignores=[]), str(path), "exec"), scope)
|
|
cls.metadata = {
|
|
source: scope["parse_post_install_script"](ROOT / "scripts/post_install" / filename)
|
|
for source, filename in (("auto", "auto_post_install.sh"),
|
|
("custom", "customizable_post_install.sh"))
|
|
}
|
|
cls.catalogs = {
|
|
locale: json.loads((ROOT / f"AppImage/messages/{locale}/common.json").read_text(encoding="utf-8"))
|
|
for locale in ("en", "it")
|
|
}
|
|
node = os.environ.get("NODE_BINARY") or shutil.which("node")
|
|
if not node:
|
|
raise AssertionError("Install Node.js 22.6+ or set NODE_BINARY")
|
|
cls.node = node
|
|
version = subprocess.run([node, "--version"], capture_output=True, text=True, check=True).stdout
|
|
match = re.fullmatch(r"v(\d+)\.(\d+)\.(\d+)\s*", version)
|
|
if not match or tuple(map(int, match.groups())) < (22, 6, 0):
|
|
raise AssertionError(f"Node.js 22.6+ required; detected {version.strip()}")
|
|
# Replace only the JSON import; the formatter body executes unchanged.
|
|
formatter = (ROOT / "AppImage/lib/optimization-descriptions.ts").read_text(encoding="utf-8")
|
|
json_import = 'import enMessages from "../messages/en/common.json"'
|
|
if formatter.count(json_import) != 1:
|
|
raise AssertionError("Formatter import seam changed")
|
|
fixture = formatter.replace(json_import, "const enMessages = " + json.dumps(cls.catalogs["en"]))
|
|
fixture += '''
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
const [key, description, italian, catalogs] = JSON.parse(readFileSync(0, "utf8"));
|
|
for (const locale of ["en", "it"]) {
|
|
const calls: string[] = [];
|
|
const t = (path: string) => {
|
|
calls.push(path);
|
|
return path.split(".").reduce((value, part) => value[part], catalogs[locale]);
|
|
};
|
|
assert.equal(optimizationDescription(description, t), locale === "en" ? description : italian);
|
|
assert.deepEqual(calls, [`settings.optimizations.toolDescriptions.${key}`]);
|
|
calls.length = 0;
|
|
const unknown = description + " [new server wording]";
|
|
assert.equal(optimizationDescription(unknown, t), unknown);
|
|
assert.deepEqual(calls, []);
|
|
}
|
|
console.log("PASS actual exact lookup: EN/IT and unknown-description fallback");
|
|
'''
|
|
cls.directory = tempfile.TemporaryDirectory(prefix="optimization-wording-")
|
|
cls.addClassCleanup(cls.directory.cleanup)
|
|
cls.fixture = Path(cls.directory.name) / "formatter.mts"
|
|
cls.fixture.write_text(fixture, encoding="utf-8")
|
|
|
|
def assert_description(self, key, source, tool, function, english, italian):
|
|
for locale, expected in (("en", english), ("it", italian)):
|
|
with self.subTest(key=key, locale=locale):
|
|
self.assertEqual(self.catalogs[locale]["settings"]["optimizations"]["toolDescriptions"][key], expected)
|
|
metadata = self.metadata[source][tool]
|
|
self.assertEqual(metadata["function"], function)
|
|
self.assertEqual(metadata["description"], english)
|
|
result = subprocess.run(
|
|
[self.node, "--experimental-strip-types", str(self.fixture)],
|
|
input=json.dumps([key, metadata["description"], italian, self.catalogs]),
|
|
capture_output=True, text=True)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
self.assertIn("PASS actual exact lookup", result.stdout)
|
|
|
|
def test_log2ram_auto_fresh_install_detection_and_update_size(self):
|
|
self.assert_description(
|
|
"log2ramAuto", "auto", "log2ram", "install_log2ram_auto",
|
|
"Fresh installs size Log2RAM from host RAM and ask for confirmation if storage is not detected as SSD. Updates preserve the configured size.",
|
|
"Alla prima installazione, dimensiona Log2RAM in base alla RAM dell'host e chiede conferma se il dispositivo di archiviazione non viene rilevato come SSD. Gli aggiornamenti mantengono la dimensione configurata.")
|
|
|
|
def test_kexec_prepares_unit_and_alias_not_hotkey(self):
|
|
self.assert_description(
|
|
"kexec", "custom", "kexec", "enable_kexec",
|
|
"Install kexec-tools and prepare a systemd unit plus the reboot-quick alias for fast reboots that skip BIOS/POST.",
|
|
"Installa kexec-tools e prepara un'unità systemd e l'alias reboot-quick per riavvii rapidi che saltano BIOS/POST.")
|
|
|
|
def test_amd_only_claims_idle_parameter_and_kvm_module_options(self):
|
|
self.assert_description(
|
|
"amd", "custom", "amd_fixes", "apply_amd_fixes",
|
|
"Apply idle=nomwait and KVM module options on AMD EPYC/Ryzen hosts.",
|
|
"Applica idle=nomwait e opzioni del modulo KVM sugli host AMD EPYC/Ryzen.")
|
|
|
|
def test_guest_agent_only_lists_supported_environments_and_skip(self):
|
|
self.assert_description(
|
|
"guestAgent", "custom", "guest_agent", "install_guest_agent",
|
|
"Detect QEMU/KVM, VMware or VirtualBox and install the matching guest agent; skip other environments.",
|
|
"Rileva QEMU/KVM, VMware o VirtualBox e installa il guest agent corrispondente; salta gli altri ambienti.")
|
|
|
|
def test_backup_describes_global_defaults_without_speed_guarantee(self):
|
|
self.assert_description(
|
|
"backupLimits", "custom", "vzdump_speed", "optimize_vzdump",
|
|
"Set global vzdump defaults to bwlimit: 0 and ionice: 5. This does not guarantee faster backups.",
|
|
"Imposta i valori predefiniti globali di vzdump su bwlimit: 0 e ionice: 5. Questo non garantisce backup più veloci.")
|
|
|
|
def test_log2ram_custom_choices_and_update_preservation(self):
|
|
self.assert_description(
|
|
"log2ramCustom", "custom", "log2ram", "configure_log2ram",
|
|
"Install Log2RAM with a chosen size, sync interval and optional autosync. Updates preserve the size and autosync choice.",
|
|
"Installa Log2RAM con dimensione e intervallo di sincronizzazione a scelta e autosync facoltativo. Gli aggiornamenti mantengono la dimensione e la scelta sull'autosync.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|