test: exercise every shipped scheduler locale and cover its CI inputs

This commit is contained in:
martino
2026-09-23 03:16:44 +02:00
parent dee18df711
commit 8297a5c935
3 changed files with 58 additions and 13 deletions
@@ -35,7 +35,8 @@ def consumers():
def fixture(directory, backend='pbs', consumer='attached', rows='', second=None,
locale='en', catalog=None, real_dialog=None):
directory = Path(directory)
(directory / 'xx.json').write_text(json.dumps(catalog or {}))
if catalog is not None:
(directory / f'{locale}.json').write_text(json.dumps(catalog), encoding='utf-8')
(directory / 'rows').write_text(rows)
(directory / 'second').write_text(rows if second is None else second)
bindir = directory / 'bin'
@@ -51,7 +52,7 @@ FIXTURE={shlex.quote(str(directory))}
BACKEND={backend}
HB_UI_MENU_H=20 HB_UI_MENU_W=84 HB_UI_MENU_LIST=10
command_not_found_handle() {{ printf 'FORBIDDEN:%s\\n' "$*" >> "$FIXTURE/errors"; return 99; }}
jq() {{ [[ "$#" == 6 && "$1" == -r && "$2" == --arg && "$3" == text && "$5" == '.[$text] // empty' && "$6" == "$FIXTURE/xx.json" ]] || {{ printf 'bad jq' >> "$FIXTURE/errors"; return 99; }}; command jq "$@"; }}
jq() {{ [[ "$#" == 6 && "$1" == -r && "$2" == --arg && "$3" == text && "$5" == '.[$text] // empty' && "$6" == "$FIXTURE/$LANGUAGE.json" ]] || {{ printf 'bad jq' >> "$FIXTURE/errors"; return 99; }}; command jq "$@"; }}
head() {{ [[ "$*" == '-1' ]] || return 99; local first; IFS= read -r first; printf '%s\\n' "$first"; while IFS= read -r first; do :; done; }}
hb_pve_list_vzdump_jobs() {{
local file="$FIXTURE/rows" line
@@ -107,20 +108,40 @@ class SchedulerMessages(unittest.TestCase):
self.assertEqual(result['status'], '1')
def test_lookup_cache_modes_at_both_consumers(self):
shipped = json.loads((ROOT / 'lang/it.json').read_text())
# Unconditional synthetic coverage, independent of cache completeness.
for backend, message in MESSAGES.items():
for consumer in ('attached', 'new'):
for locale, catalog, expected in (
('en', {message: 'ignored'}, message),
('missing', {}, message),
('xx', {'unrelated': 'unrelated'}, message),
('xx', shipped, shipped.get(message, message)),
('xx', {message: 'SYNTHETIC reordered backend: ' + backend},
'SYNTHETIC reordered backend: ' + backend),
for mode, locale, catalog, expected, hint in (
('english', 'en', {message: 'ignored', HINT: 'ignored'}, message, HINT),
('missing-file', 'missing', None, message, HINT),
('missing-key', 'xx', {'unrelated': 'unrelated'}, message, HINT),
('translated', 'xx', {message: 'SYNTHETIC reordered backend: ' + backend,
HINT: 'SYNTHETIC hint'},
'SYNTHETIC reordered backend: ' + backend, 'SYNTHETIC hint'),
):
with self.subTest(backend=backend, consumer=consumer, locale=locale, expected=expected):
with self.subTest(backend=backend, consumer=consumer, mode=mode):
result = run_case(backend=backend, consumer=consumer, locale=locale, catalog=catalog)
suffix = '\n\n' + catalog.get(HINT, HINT) if consumer == 'new' else ''
suffix = '\n\n' + hint if consumer == 'new' else ''
self.assertEqual(result['message'], expected + suffix)
self.assertEqual(result['status'], '1')
def test_all_shipped_caches_at_both_consumers(self):
catalogs = sorted((ROOT / 'lang').glob('*.json'))
self.assertTrue(catalogs, 'No shipped locale catalogs discovered')
for path in catalogs:
catalog = json.loads(path.read_text(encoding='utf-8'))
self.assertIsInstance(catalog, dict, path.name)
for backend, message in MESSAGES.items():
for consumer in ('attached', 'new'):
with self.subTest(locale=path.stem, backend=backend, consumer=consumer):
# Exercise the real translate function with the actual locale
# filename, not just a comparison of JSON values. Missing or
# empty translations are valid: runtime falls back to English.
result = run_case(backend=backend, consumer=consumer,
locale=path.stem, catalog=catalog)
expected = message if path.stem == 'en' else catalog.get(message) or message
hint = HINT if path.stem == 'en' else catalog.get(HINT) or HINT
suffix = '\n\n' + hint if consumer == 'new' else ''
self.assertEqual(result['message'], expected + suffix)
self.assertEqual(result['status'], '1')
+12
View File
@@ -10,6 +10,10 @@ on:
- 'AppImage/lib/i18n/**'
- 'AppImage/components/terminal-panel.tsx'
- 'CONTRIBUTING.md'
# Scheduler consumers and the offline extractor's .sh/.func inputs.
- 'scripts/**/*.sh'
- 'scripts/**/*.func'
- 'lang/*.json'
push:
branches: [main, develop]
paths:
@@ -20,6 +24,10 @@ on:
- 'AppImage/lib/i18n/**'
- 'AppImage/components/terminal-panel.tsx'
- 'CONTRIBUTING.md'
# Scheduler consumers and the offline extractor's .sh/.func inputs.
- 'scripts/**/*.sh'
- 'scripts/**/*.func'
- 'lang/*.json'
workflow_dispatch:
permissions:
@@ -39,6 +47,10 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: '22.14.0'
- name: Check scheduler test dependencies
run: |
test -x /bin/bash
test -x /usr/bin/jq
- name: Run complete offline script test suite
env:
PYTHONDONTWRITEBYTECODE: '1'
+13 -1
View File
@@ -826,7 +826,8 @@ From the repository root, run the complete script suite:
python3 -m unittest discover -s .github/scripts/tests -v
```
Prerequisites: Python 3.11+ (standard library only) and Node **22.14+**
Prerequisites: Python 3.11+ (Python standard library only), Node **22.14+**,
`/bin/bash`, and `/usr/bin/jq` for the isolated scheduler fixtures.
(Node 22 LTS is the CI baseline). Node 20 can still build the Monitor, but
cannot run this suite's native TypeScript consumer test. The test passes
`--experimental-strip-types` explicitly and executes the actual pure lookup
@@ -835,6 +836,17 @@ It does not launch React or require `npm install`, `pip install`, a Proxmox host
API keys, or a translation provider. Generator tests use isolated temporary
catalogs and fake/forbidden providers; repository catalogs are not regenerated.
Scheduler fixtures discover every shipped `lang/*.json` cache and run the actual
`translate` lookup at both empty-job consumers for PBS and local archive backends.
They accept English fallback for missing/empty entries; complete translations are
not required. Unconditional synthetic cases cover English bypass, missing files,
missing keys and translated messages/hints. Only bounded shell functions are
extracted: no administrative script is sourced or run in full. These scheduler
messages have no placeholders; named-token preservation is tested separately by
the existing cache-generator tests, not by scheduler/count-message assertions.
The workflow watches all `lang/*.json` catalogs plus `.sh` and `.func` inputs
under `scripts/`, and checks the Bash/jq prerequisites before running the suite.
If Node is not on `PATH`, select a local executable without a global install:
```bash