feat: install LOAD failures get the compatibility-test analysis (v3.6.53)

ramShowInstallResult appends ramCompatDetailHtml(data, capMem) to the
install modal (the modal content is HTML now - the step lines are escaped
and ramOpModalFinish takes the asHtml mode), so a failed LOAD / INSTALL
[for load] shows the same block analysis as the compatibility test:

- the failing block's CAP section (load-file byte range and components, and
  what completes there), with the JC VM spec 4.5.2 note - or 'the import
  list already passed' when the failure is later in the file;
- the remote status word with its name;
- the import list at the CAP's own versions (a real install runs no probe);
  the closing hint points at the compatibility test ('Set all 0.0' first,
  then bisect), while a probe run keeps the lower-the-versions wording.

ramInstallFailHint stays as the short inline hint; INSTALL [for install] and
/api/ram-install-app failures show no block (the LOAD guard in the helper).

sw cache simple-v326; tests: 550 Python / 704 Node.
This commit is contained in:
2026-09-30 09:27:42 +03:00
parent ee747003f2
commit 4906c91463
5 changed files with 37 additions and 7 deletions
+18 -4
View File
@@ -1749,7 +1749,7 @@
// ===== Version ===== // ===== Version =====
// Single source of truth for the PWA version: shown in the header and used // Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect(). // by the server version check in pysimConnect().
const SIMPLE_VERSION = '3.6.52'; const SIMPLE_VERSION = '3.6.53';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching ===== // ===== Tab switching =====
@@ -9830,10 +9830,15 @@ function ramCompatFailureHtml(data, mem) {
html += ramCompatRowsHtml(rows); html += ramCompatRowsHtml(rows);
const capRows = rows.filter(r => r.state === 'cap' || r.state === 'lowered'); const capRows = rows.filter(r => r.state === 'cap' || r.state === 'lowered');
const atCap = capRows.filter(r => r.state === 'cap').length; const atCap = capRows.filter(r => r.state === 'cap').length;
const hasProbe = !!((data.probe_imports && data.probe_imports.length) ||
(data.probe_unmatched && data.probe_unmatched.length));
if (capRows.length && !atCap) { if (capRows.length && !atCap) {
html += '<div>' + esc(t('Every import was tested at 0.0 \u2014 a package the card does not have (or another load-file check, e.g. memory) is the cause.')) + '</div>'; html += '<div>' + esc(t('Every import was tested at 0.0 \u2014 a package the card does not have (or another load-file check, e.g. memory) is the cause.')) + '</div>';
} else if (capRows.length) { } else if (capRows.length && hasProbe) {
html += '<div>' + esc(t('The card checks the whole list at that point \u2014 lower the versions (Set all 0.0) to test whether the list is the cause, then restore one line at a time to find the culprit.')) + '</div>'; html += '<div>' + esc(t('The card checks the whole list at that point \u2014 lower the versions (Set all 0.0) to test whether the list is the cause, then restore one line at a time to find the culprit.')) + '</div>';
} else if (capRows.length) {
// a real install runs the CAP's own versions: point at the test
html += '<div>' + esc(t('The card checks the whole list at that point \u2014 the CAP compatibility test can confirm it: Set all 0.0 first, then restore one line at a time to find the package and version the card lacks.')) + '</div>';
} }
} }
html += '</div>'; html += '</div>';
@@ -10032,7 +10037,10 @@ function ramNvLine(data) {
} }
// Shared result rendering for /api/ram-install and /api/ram-install-app: the // Shared result rendering for /api/ram-install and /api/ram-install-app: the
// per-step lines, the counter the card consumed, and the verdict. // per-step lines, the counter the card consumed, and the verdict. A failure
// at a LOAD / INSTALL [for load] step also gets the same block analysis as
// the compatibility test (which CAP section the block covers, what the card
// checks there, the import list and the status word) in the modal.
function ramShowInstallResult(data, okText, capMem) { function ramShowInstallResult(data, okText, capMem) {
const resultEl = document.getElementById('ram-result'); const resultEl = document.getElementById('ram-result');
const stepsEl = document.getElementById('ram-steps'); const stepsEl = document.getElementById('ram-steps');
@@ -10064,7 +10072,12 @@ function ramShowInstallResult(data, okText, capMem) {
probeNote + (hint ? ' \u2014 ' + t('Hint:') + ' ' + hint : ''); probeNote + (hint ? ' \u2014 ' + t('Hint:') + ' ' + hint : '');
resultEl.classList.remove('hidden', 'text-green-600'); resultEl.classList.add('text-red-600'); resultEl.classList.remove('hidden', 'text-green-600'); resultEl.classList.add('text-red-600');
} }
ramOpModalFinish(resultEl.textContent, !!data.success, txt); // The modal carries the step lines (escaped) plus the LOAD-failure
// analysis when there is one (ramCompatDetailHtml returns '' for a
// successful run and for failures that are not LOAD/INSTALL [for load]).
let modal = esc(txt);
modal += ramCompatDetailHtml(data, capMem || null);
ramOpModalFinish(resultEl.textContent, !!data.success, modal, true);
} }
async function ramExecute() { async function ramExecute() {
@@ -17906,6 +17919,7 @@ const LANG_RU = {
'The import list already passed \u2014 the Import component completed at': 'Список импортов уже пройден \u2014 компонент Import завершён на', 'The import list already passed \u2014 the Import component completed at': 'Список импортов уже пройден \u2014 компонент Import завершён на',
'Every import was tested at 0.0 \u2014 a package the card does not have (or another load-file check, e.g. memory) is the cause.': 'Все импорты проверены с версией 0.0 \u2014 причина в пакете, которого нет на карте (или в другой проверке load-файла, например памяти).', 'Every import was tested at 0.0 \u2014 a package the card does not have (or another load-file check, e.g. memory) is the cause.': 'Все импорты проверены с версией 0.0 \u2014 причина в пакете, которого нет на карте (или в другой проверке load-файла, например памяти).',
'The card checks the whole list at that point \u2014 lower the versions (Set all 0.0) to test whether the list is the cause, then restore one line at a time to find the culprit.': 'Карта проверяет весь список в этой точке \u2014 понизьте версии («Все 0.0»), чтобы проверить, в списке ли причина, затем возвращайте по одной строке, чтобы найти виновника.', 'The card checks the whole list at that point \u2014 lower the versions (Set all 0.0) to test whether the list is the cause, then restore one line at a time to find the culprit.': 'Карта проверяет весь список в этой точке \u2014 понизьте версии («Все 0.0»), чтобы проверить, в списке ли причина, затем возвращайте по одной строке, чтобы найти виновника.',
'The card checks the whole list at that point \u2014 the CAP compatibility test can confirm it: Set all 0.0 first, then restore one line at a time to find the package and version the card lacks.': 'Карта проверяет весь список в этой точке \u2014 это можно подтвердить проверкой совместимости CAP: сначала «Все 0.0», затем возвращайте по одной строке, чтобы найти пакет и версию, которых не хватает карте.',
'completes': 'завершает', 'completes': 'завершает',
'inside': 'внутри', 'inside': 'внутри',
'the failed step completes': 'неудавшийся шаг завершает', 'the failed step completes': 'неудавшийся шаг завершает',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v325'; const CACHE = 'simple-v326';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+16
View File
@@ -1113,6 +1113,13 @@ test('ramCompatFailureHtml names the failing CAP section and the import list', (
assert.ok(out3.includes('The import list already passed \u2014 the Import component completed at LOAD (1/2).'), out3); assert.ok(out3.includes('The import list already passed \u2014 the Import component completed at LOAD (1/2).'), out3);
assert.ok(out3.includes('remote SW 6581'), out3); assert.ok(out3.includes('remote SW 6581'), out3);
assert.ok(!out3.includes('4.5.2'), out3); assert.ok(!out3.includes('4.5.2'), out3);
// a real install run (no probe): the hint points at the compatibility test
const install = { success: false, failed_step: 3, boundary_block: 1, total_blocks: 2,
load_block_size: 240, error: 'LOAD (1/2): remote SW 6985', steps: data.steps };
const out4 = ramCompatFailureHtml(install, mem);
assert.ok(out4.includes('the CAP compatibility test can confirm it'), out4);
assert.ok(!out4.includes('lower the versions (Set all 0.0)'), out4);
assert.ok(out4.includes('Import list under test (4):'), out4);
delete globalThis.t; delete globalThis.t;
// not a LOAD failure at all: no block analysis // not a LOAD failure at all: no block analysis
assert.strictEqual(ramCompatFailureHtml(Object.assign({}, data, { assert.strictEqual(ramCompatFailureHtml(Object.assign({}, data, {
@@ -1142,6 +1149,15 @@ test('the compatibility test runs in the modal like the install', () => {
assert.ok(!src.includes('ramShowProgress'), 'the inline progress bar is replaced by the modal'); assert.ok(!src.includes('ramShowProgress'), 'the inline progress bar is replaced by the modal');
}); });
test('a failed install carries the LOAD failure analysis into the modal', () => {
const src = extractFunc(html, 'ramShowInstallResult');
assert.ok(src.includes('ramCompatDetailHtml(data, capMem'), 'the analysis must reach the modal');
assert.ok(/ramOpModalFinish\(resultEl\.textContent, !!data\.success, modal, true\)/.test(src),
'the modal content is HTML now');
assert.ok(src.includes('ramInstallFailHint'), 'the short inline hint stays');
assert.ok(src.includes('ramNvLine'), 'the NV footprint line stays');
});
test('ramExecute dispatches the compatibility test op', () => { test('ramExecute dispatches the compatibility test op', () => {
const src = extractFunc(html, 'ramExecute'); const src = extractFunc(html, 'ramExecute');
assert.ok(/op === 'compat'/.test(src), 'the compat op must be dispatched'); assert.ok(/op === 'compat'/.test(src), 'the compat op must be dispatched');
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-simple-server" name = "pysim-simple-server"
version = "3.6.52" version = "3.6.53"
description = "HTTP REST server wrapping pysim for the SIMple PWA" description = "HTTP REST server wrapping pysim for the SIMple PWA"
requires-python = ">=3.8" requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
+1 -1
View File
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad from osmocom.utils import rpad
VERSION = '3.6.52' VERSION = '3.6.53'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE