Use shorter CLI validation replies on the constrained Mini

This commit is contained in:
mikecarper committed 2026-10-28 04:15:04 -07:00
1 parent c778abceab
commit cea654cb47
15 files changed
+901 -163

No files matched your search

+598
View File
@@ -0,0 +1,598 @@
{
"schema": 1,
"profiles": [
"wio-e5-mini_repeater"
],
"messages": [
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, unsupported on this role",
"concise": "Error, unsupported role"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, retry configuration unsupported on this role",
"concise": "Error, retry unsupported for role"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "ERROR: dutycycle must be 1-100",
"concise": "ERROR: dutycycle 1-100"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, invalid airtime factor",
"concise": "Error, bad airtime factor"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "ERROR: telemetry.access must be all or acl",
"concise": "ERROR: telemetry.access all|acl"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error: interval must be 0 (off) or %d-240 minutes",
"concise": "Error: interval 0 (off) or %d-240 minutes"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0,1, or 2",
"concise": "Error, 0|1|2"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be on or off",
"concise": "Error, on|off"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0-20",
"concise": "Error, 0-20"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0-2",
"concise": "Error, 0-2"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0-40 dB",
"concise": "Error, 0-40 dB"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 1-15",
"concise": "Error, 1-15"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 10-5000 ms",
"concise": "Error, 10-5000 ms"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0-5000 ms",
"concise": "Error, 0-5000 ms"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be all or 1-7",
"concise": "Error, all|1-7"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0-15",
"concise": "Error, 0-15"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be 0-63 or off",
"concise": "Error, 0-63|off"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be infra, rooftop, or mobile",
"concise": "Error, infra|rooftop|mobile"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use up to %u comma-separated 3-byte hex prefixes",
"concise": "Error, max %u comma-separated 3-byte hex prefixes"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use on or off on this role",
"concise": "Error, on|off"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, SNR must fit -32.00..31.75 dB",
"concise": "Error, SNR -32.00..31.75 dB"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use CR4,CR5,CR7,CR8 SNRs or off",
"concise": "Error, CR4,CR5,CR7,CR8 SNRs|off"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, must be: off, minimal, moderate, or strict",
"concise": "Error, off|minimal|moderate|strict"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error: TX power range is %d to %d dBm",
"concise": "Error: TX power %d..%d dBm"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, invalid radio params",
"concise": "Error, bad radio params"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, invalid latitude",
"concise": "Error, bad latitude"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, invalid longitude",
"concise": "Error, bad longitude"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, table rejected prefix",
"concise": "Error, prefix rejected"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Err - unknown or ambiguous jump: %s",
"concise": "Err - unknown/ambiguous jump: %s"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "ERROR: Power management not supported",
"concise": "ERROR: Power management unsupported"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - repeat is now OFF",
"concise": "OK - repeat OFF"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - repeat is now ON",
"concise": "OK - repeat ON"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - clock set: %02d:%02d - %d/%d/%d UTC",
"concise": "OK - clock: %02d:%02d - %d/%d/%d UTC"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - interval rounded to %d",
"concise": "OK - rounded interval %d"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "radio settings could not be saved",
"concise": "radio settings not saved"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "unsupported on this platform",
"concise": "platform unsupported"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - LoRa FEM RX gain on",
"concise": "OK - FEM RX gain on"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - LoRa FEM RX gain off",
"concise": "OK - FEM RX gain off"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - LoRa FEM TX gain on",
"concise": "OK - FEM TX gain on"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "OK - LoRa FEM TX gain off",
"concise": "OK - FEM TX gain off"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "ERROR: range is %lu-%lu us",
"concise": "ERROR: range %lu-%lu us"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, argument too long",
"concise": "Error, arg too long"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, SNR must be numeric",
"concise": "Error, numeric SNR needed"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, invalid frequency",
"concise": "Error, bad frequency"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use: tempradio f,bw,sf,cr,minutes[,preamble]",
"concise": "Error, tempradio f,bw,sf,cr,minutes[,preamble]"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use: set radioat f,bw,sf,cr,start (UTC or +minutes)",
"concise": "Error, set radioat f,bw,sf,cr,start (UTC or +minutes)"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use: set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)",
"concise": "Error, set tempradioat f,bw,sf,cr,start,end (UTC or +minutes)"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use: set recent.repeater <hex> [snr_db]",
"concise": "Error, set recent.repeater <hex> [snr_db]"
},
{
"file": "src/helpers/CommonCLI.cpp",
"verbose": "Error, use: get recent.repeaters [page] | search <2|4|6 hex> [page]",
"concise": "Error, get recent.repeaters [page] | search <2|4|6 hex> [page]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - blacklist slot must be 1-%u",
"concise": "Err - blacklist slot: 1-%u"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - expected six-digit repeater ID",
"concise": "Err - repeater ID: six hex digits"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - use 1-%d unique six-digit hex IDs separated by commas",
"concise": "Err - use 1-%d unique six-digit hex IDs, comma-separated"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - next blacklist slot is %u",
"concise": "Err - next blacklist slot: %u"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - blacklist range exceeds slot %u",
"concise": "Err - blacklist range past slot %u"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - unable to save flood filter blacklist",
"concise": "Err - flood filter blacklist not saved"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - filter slot must be 1-%d",
"concise": "Err - filter slot: 1-%d"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - packet type must be name, any, 0-15, or 0x00-0x0F",
"concise": "Err - type: name|any|0-15|0x00-0x0F"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - tx timing must be fast or slow",
"concise": "Err - tx: fast|slow"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - scope must be a public name of at most 30 characters",
"concise": "Err - scope: public name, max 30 chars"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - unable to save flood filter",
"concise": "Err - flood filter not saved"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - too many flood filter parameters",
"concise": "Err - excess flood filter parameters"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - require=region requires scope=<name>",
"concise": "Err - require=region needs scope=<name>"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - tx timing requires scope=<name>",
"concise": "Err - tx needs scope=<name>"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - scope slot must be 1-%d",
"concise": "Err - scope slot: 1-%d"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - path bucket must be 1-6",
"concise": "Err - path bucket: 1-6"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - require slot must be 1-%d",
"concise": "Err - require slot: 1-%d"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - channel must be public, #name, or 128/256-bit key",
"concise": "Err - channel: public|#name|128/256-bit key"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - permissions must be 0-255",
"concise": "Err - permissions: 0-255"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - region names cannot contain spaces",
"concise": "Err - region name: no spaces"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - unknown or invalid alert region",
"concise": "Err - unknown/invalid alert region"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - define a usable region before enabling battery alerts",
"concise": "Err - battery alerts need a usable region first"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - multiple narrowest regions; specify one",
"concise": "Err - specify one of the narrowest regions"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - low must be greater than critical",
"concise": "Err - low must exceed critical"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - critical must be less than low",
"concise": "Err - critical must be below low"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "OK - all flood filters removed",
"concise": "OK - flood filters cleared"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - use get guest or set guest on|off",
"concise": "Err - get guest / set guest on|off"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - region load owned by admin/region manager",
"concise": "Err - region load: admin/region manager only"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error: start is in the past",
"concise": "Error: start in past"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error: end is in the past",
"concise": "Error: end in past"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error: end must be after start",
"concise": "Error: end must follow start"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - another remote command is still running",
"concise": "Err - remote command busy"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - remote command is too long",
"concise": "Err - remote command too long"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - filter parameters too long",
"concise": "Err - filter params too long"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - use: set flood.filter[.n] <type> [hops] [path=blacklist] [scope=<name>] [require=region] [tx=slow] [suspend=tempradio]",
"concise": "Err - set flood.filter[.n] <type> [hops] [path=blacklist] [scope=<name>] [require=region] [tx=slow] [suspend=tempradio]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error, use: get tempradioat [n]",
"concise": "Error, get tempradioat [n]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error, use: get radioat [n]",
"concise": "Error, get radioat [n]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error, use: del tempradioat [n]",
"concise": "Error, del tempradioat [n]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Error, use: del radioat [n]",
"concise": "Error, del radioat [n]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - use: del flood.filter.blacklist[.<1-%u>| all]",
"concise": "Err - del flood.filter.blacklist[.<1-%u>| all]"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - use: del flood.filter.<1-%d>|all",
"concise": "Err - del flood.filter.<1-%d>|all"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - usage: send text.flood <message>",
"concise": "Err - send text.flood <message>"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - usage: set rx.watchdog <on|off>",
"concise": "Err - set rx.watchdog <on|off>"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - usage: set battery.alert <on [region]|off>",
"concise": "Err - set battery.alert <on [region]|off>"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - usage: set battery.alert.low <1-100>",
"concise": "Err - set battery.alert.low <1-100>"
},
{
"file": "examples/simple_repeater/MyMesh.cpp",
"verbose": "Err - usage: set battery.alert.critical <0-99>",
"concise": "Err - set battery.alert.critical <0-99>"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "slot must be 1-4 or all",
"concise": "slot: 1-4|all"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "future UTC start/end required, within 24 days",
"concise": "future UTC start/end within 24 days"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "all four schedule slots occupied",
"concise": "all four slots full"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "settings could not be saved",
"concise": "settings not saved"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "invalid radio2 command",
"concise": "bad radio2 command"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "MediaMesh region unavailable or full",
"concise": "MediaMesh region unavailable/full"
},
{
"file": "src/helpers/RadioProfileCLI.cpp",
"verbose": "Error: use %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]",
"concise": "Error: %s f,bw,sf,cr,rx|rxtx%s[,preamble|auto]"
},
{
"file": "src/helpers/WirelessControl.h",
"verbose": "Error: requested wireless service unavailable in this build/boot",
"concise": "Error: wireless service unavailable this build/boot"
},
{
"file": "src/helpers/WirelessControl.h",
"verbose": "Error: 2.4ghz is off; use set 2.4ghz on first",
"concise": "Error: 2.4ghz off; set 2.4ghz on first"
},
{
"file": "src/helpers/WirelessControl.h",
"verbose": "Error: use set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]",
"concise": "Error: set wifi/espnow on|off [force]; set 2.4ghz on [all|force]|off [force]"
},
{
"file": "src/helpers/PowerSavingDiagnostics.h",
"verbose": "OK - power diagnostics %s (RAM only; sleep policy unchanged)",
"concise": "OK - power diag %s (RAM only; sleep policy unchanged)"
},
{
"file": "src/helpers/PowerSavingDiagnostics.h",
"verbose": "OK - power diagnostics cleared; sleep policy unchanged",
"concise": "OK - power diag cleared; sleep policy unchanged"
},
{
"file": "src/helpers/PowerSavingDiagnostics.h",
"verbose": "Error: use set powersaving.diag on|off",
"concise": "Error: set powersaving.diag on|off"
},
{
"file": "src/helpers/PowerSavingDiagnostics.h",
"verbose": "Error: use clear powersaving.diag",
"concise": "Error: clear powersaving.diag"
},
{
"file": "src/helpers/FloodAdvertCLI.h",
"verbose": "ERR: advert limiter unavailable on this role",
"concise": "ERR: advert limiter unavailable for role"
},
{
"file": "src/helpers/FloodAdvertCLI.h",
"verbose": "ERR: advert list index out of range",
"concise": "ERR: advert index out of range"
},
{
"file": "src/helpers/FloodAdvertCLI.h",
"verbose": "OK - all advert limit history cleared",
"concise": "OK - all advert history cleared"
},
{
"file": "src/helpers/FloodAdvertCLI.h",
"verbose": "OK - key advert limit history cleared",
"concise": "OK - key advert history cleared"
},
{
"file": "src/helpers/LedSettings.cpp",
"verbose": "Error: usage get leds",
"concise": "Error: get leds"
},
{
"file": "src/helpers/LedSettings.cpp",
"verbose": "Error: usage set leds on|off",
"concise": "Error: set leds on|off"
},
{
"file": "src/helpers/StorageLayout.cpp",
"verbose": "Error: use get storage.layout",
"concise": "Error: get storage.layout"
}
],
"noncompact_tokens_sha256": {
"src/helpers/CommonCLI.cpp": "0a3d2e2eabd3e161e352176c4a6a4c4baa184944c08c93b522d4f1ddb5a24edd",
"examples/simple_repeater/MyMesh.cpp": "8567eeef2f327100e397c40b0aaf5b675fcdfea254987437d1ca8d005ad3a6f0",
"src/helpers/RadioProfileCLI.cpp": "d641d16e872eba3e73b9b5a50f9718c10b0b7da72782f793706ac36b3724a624",
"src/helpers/WirelessControl.h": "bbde183f091a8afa7fd65c7b379783f8be2ac4e74ae6037a99a71fde3fb89f1e",
"src/helpers/PowerSavingDiagnostics.h": "18b78f5780046ea8c772c4ce6c337741ae0c027039d9399c94c5f4205d114000",
"src/helpers/FloodAdvertCLI.h": "0f378e4d0f6c3b9b79991068a4b4208139484d2144ee1d7c5094d39043463be4",
"src/helpers/LedSettings.cpp": "df0681234ca1337148ad59caa935857bc692f98bf18603ca6d099c0382028400",
"src/helpers/StorageLayout.cpp": "5dd1c8bcbe2902e1f22d14f9ab425465bdc38926fae23989987d850b484cbb18"
}
}
+2 -2
View File
@@ -106,7 +106,7 @@ static void exercise(const std::string& kind, size_t size, bool advanced, bool a
const auto input = command(kind, args);
cli.run(input.c_str(), reply.data());
if (size >= COMMON_CLI_TMP_LEN) {
assert(strcmp(reply.data(), "Error, argument too long") == 0);
assert(strcmp(reply.data(), MESH_CLI_REPLY_TEXT("Error, argument too long", "Error, arg too long")) == 0);
untouched(cli);
for (char value : cli.tmp) assert(value == char(0x55));
} else if (kind == "sensor" || kind == "gps") {
@@ -117,7 +117,7 @@ static void exercise(const std::string& kind, size_t size, bool advanced, bool a
} else if (kind == "retry") {
assert(cli.callbacks.support_calls == 1);
assert(cli.saves == (advanced ? 1U : 0U));
assert(strcmp(reply.data(), advanced ? "OK" : "Error, use on or off on this role") == 0);
assert(strcmp(reply.data(), advanced ? "OK" : MESH_CLI_REPLY_TEXT("Error, use on or off on this role", "Error, on|off")) == 0);
if (advanced) {
assert(cli.prefs.direct_retry_cr_enabled == 1);
assert(cli.prefs.direct_retry_cr4_snr_x4 == 0 && cli.prefs.direct_retry_cr5_snr_x4 == 0);
+110
View File
@@ -0,0 +1,110 @@
"""Keep constrained CLI prose separate from commands and protocol replies."""
from pathlib import Path
import configparser
import hashlib
import json
import os
import re
import shutil
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
FIXTURE = ROOT / 'test/fixtures/cli_reply_text.json'
SELECTION = re.compile(
r'MESH_CLI_REPLY_TEXT\(("(?:\\.|[^"\\])*"),\s*("(?:\\.|[^"\\])*")\)')
TOKENS = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|'
r'//[^\n]*|/\*.*?\*/|[A-Za-z_][A-Za-z_0-9]*|[^\s]', re.S)
def original_tokens(source):
source = re.sub(r'^#include [<"](?:helpers/)?CLIReplyText\.h[>"]\s*\n',
'', source, flags=re.M)
source = SELECTION.sub(lambda match: match.group(1), source)
return '\n'.join(token.group() for token in TOKENS.finditer(source)
if not token.group().startswith(('//', '/*'))).encode()
class CliReplyTextTests(unittest.TestCase):
def test_only_reviewed_human_literals_change_and_control_flow_stays_exact(self):
fixture = json.loads(FIXTURE.read_text())
self.assertEqual(fixture['schema'], 1)
expected = {}
for row in fixture['messages']:
expected.setdefault(row['file'], set()).add((row['verbose'], row['concise']))
old, new = row['verbose'], row['concise']
self.assertLess(len(new), len(old))
formats = r'%(?:[-+ #0]*[0-9.*]*[a-zA-Z]+|%)'
self.assertEqual(re.findall(formats, old), re.findall(formats, new))
category = r'^(Error|ERROR|ERR|Err|OK)(?=[: ,\-])'
self.assertEqual(re.findall(category, old), re.findall(category, new))
self.assertEqual(re.findall(r'\d+(?:\.\d+)?', old),
re.findall(r'\d+(?:\.\d+)?', new))
self.assertFalse(old.startswith(('> ', '{', 'KEYBACKUP ', 'ACK')))
self.assertNotRegex(old.lower(),
r'host|replay|password|login|save unconfirmed|reboot|'
r'retained|default|warning|applying in|restore scheduled')
if 'unchanged' in old:
self.assertIn('sleep policy unchanged', old)
self.assertIn('sleep policy unchanged', new)
for file, messages in expected.items():
source = (ROOT / file).read_text()
actual = {(json.loads(match.group(1)), json.loads(match.group(2)))
for match in SELECTION.finditer(source)}
self.assertEqual(actual, messages, file)
# Substituting each original literal recovers every original C++
# token, including command tests, branches, calls and arguments.
self.assertEqual(hashlib.sha256(original_tokens(source)).hexdigest(),
fixture['noncompact_tokens_sha256'][file], file)
def test_profile_activation_is_explicit_and_default_is_off(self):
fixture = json.loads(FIXTURE.read_text())
enabled = []
for path in [ROOT / 'platformio.ini', *sorted((ROOT / 'variants').rglob('*.ini'))]:
config = configparser.ConfigParser(interpolation=None, strict=False)
config.read(path)
for section in config.sections():
if re.search(r'-D\s*MESH_CLI_COMPACT_REPLIES=1\b',
config[section].get('build_flags', '')):
self.assertTrue(section.startswith('env:'), str(path))
enabled.append(section[4:])
self.assertEqual(sorted(enabled), sorted(fixture['profiles']))
header = (ROOT / 'src/helpers/CLIReplyText.h').read_text()
self.assertIn('#ifndef MESH_CLI_COMPACT_REPLIES\n'
'#define MESH_CLI_COMPACT_REPLIES 0', header)
def test_actual_header_selects_exact_text_without_a_runtime_lookup(self):
compiler = os.environ.get('CXX') or shutil.which('g++') or shutil.which('clang++')
if not compiler:
self.skipTest('host C++ compiler required')
messages = json.loads(FIXTURE.read_text())['messages']
selections = ',\n'.join('MESH_CLI_REPLY_TEXT(%s, %s)' %
(json.dumps(row['verbose']), json.dumps(row['concise']))
for row in messages)
source = ('#include <CLIReplyText.h>\n#include <cstdio>\n'
'int main(){const char* text[]={'+selections+'};'
'for(const char* line:text)puts(line);}\n')
with tempfile.TemporaryDirectory(prefix='cli-reply-text-') as folder:
work = Path(folder)
(work / 'test.cpp').write_text(source)
for flag, key in ((None, 'verbose'), (0, 'verbose'), (1, 'concise')):
flags = [] if flag is None else ['-DMESH_CLI_COMPACT_REPLIES='+str(flag)]
built = subprocess.run([compiler, '-std=c++11', '-Os', '-Wall', '-Wextra',
'-Werror', *flags, '-I'+str(ROOT / 'src/helpers'),
str(work / 'test.cpp'), '-o', str(work / 'test')], capture_output=True)
self.assertEqual(built.returncode, 0, built.stderr.decode(errors='replace'))
result = subprocess.run([str(work / 'test')], capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr.decode(errors='replace'))
self.assertEqual(result.stdout.decode().splitlines(),
[row[key] for row in messages])
if flag == 1:
image = (work / 'test').read_bytes()
selected = {row['concise'] for row in messages}
for row in messages:
if row['verbose'] not in selected:
self.assertNotIn(row['verbose'].encode()+b'\0', image)
if __name__ == '__main__':
unittest.main()
+5 -2
View File
@@ -54,12 +54,15 @@ class CommonCLIArgumentBoundsTests(unittest.TestCase):
cls.work = tempfile.TemporaryDirectory(prefix="meshcore-cli-argument-bounds-")
cls.binaries = {}
for name, defines in (("default", []), ("larger", ["-DCOMMON_CLI_TMP_LEN=192"]),
("compact", ["-DMESH_CLI_COMPACT_REPLIES=1"]),
("legacy", [])):
generated = source
if name == "legacy":
# Restore all three audited unchecked copies as a negative control.
error = (r'(?:"Error, argument too long"|MESH_CLI_REPLY_TEXT\('
r'"Error, argument too long", "Error, arg too long"\))')
pattern = (r'if \(!copyCLIArgument\(tmp, sizeof\(tmp\), (&(?:command|config)\[\d+\])\)\) '
r'\{\s*strcpy\(reply, "Error, argument too long"\);\s*return;\s*\}')
r'\{\s*strcpy\(reply, ' + error + r'\);\s*return;\s*\}')
generated, count = re.subn(pattern, r"strcpy(tmp, \1);", generated)
assert count == 3, "review the negative controls after branch changes"
directory = Path(cls.work.name)
@@ -86,7 +89,7 @@ class CommonCLIArgumentBoundsTests(unittest.TestCase):
env=dict(os.environ, ASAN_OPTIONS="detect_leaks=0:abort_on_error=0"))
def test_production_boundaries_and_rejection_without_effects(self):
for variant in ("default", "larger"):
for variant in ("default", "larger", "compact"):
with self.subTest(variant=variant):
result = self.run_case(variant, "production")
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)