Use shorter CLI validation replies on the constrained Mini
This commit is contained in:
1 parent
c778abceab
commit
cea654cb47
15 files changed
+901
-163
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user