mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
cfg.c default_number, which cfg_int and cfg_double use for a set value that does not parse, read cfg_meta(name)->def without a check, so a key without a row whose value was set and unparsable crashed there (the verifier's probe: a segmentation fault inside default_number). gfx_present.c gfx_cfg_window_size did the same for WINDOW_SIZE. Neither is reachable today: every unguarded value read has a row (tests/cfg_rows_check.py holds it so), the three guarded ones use cfg_bool, and WINDOW_SIZE has a row. default_number gives 0 for a key without a row; gfx_cfg_window_size falls back to 1920x1080 when the row or its default is missing. Taking the device profile's default there (1280x800 on a Steam Deck) would change what players get and is left for approval. Reproduced before the change with the new case in tests/cfg_parse_check.py (cfg_int and cfg_double of an unset row's key set to "abc" crashed the program); with the change both give 0 and the check passes, as do the cfg rows checks and the compile checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
6.1 KiB
Python
141 lines
6.1 KiB
Python
"""Build and run tests/cfg_parse_check.c: runtime/cfg.c's reading of nx2navi.ini and the environment, against stand-ins
|
|
for what it links with. The values stay as written; what is checked is what the log says about them:
|
|
|
|
- values outside their rows' sets (cfg.c check_value): a 0|1 switch with another value, a number outside its range, a
|
|
word that is none of the menu's choices, and a ';' or '#' comment kept in a value, each named with its line; none
|
|
for a file whose values fit, nor for a list with numbers in it (the menu's presets); at most 16 lines, then a count;
|
|
- a full settings table (cfg.c CFG_MAX_KEYS): one line at load naming the first key not read, one at the first menu
|
|
change not kept;
|
|
- a number read of a key without a row whose set value does not parse gives 0 (cfg.c default_number), not a crash.
|
|
|
|
No game files.
|
|
|
|
Usage: cfg_parse_check.py
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
|
|
|
|
|
def compiler():
|
|
if os.environ.get('CC'):
|
|
return os.environ['CC']
|
|
if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists():
|
|
return str(MSYS_BIN / 'clang.exe')
|
|
return shutil.which('clang') or 'clang'
|
|
|
|
|
|
CLEAN = '''[frame rate]
|
|
pace_smooth = 1
|
|
fps_limit = 144
|
|
; a comment line
|
|
# another
|
|
[display]
|
|
sharpness = 80
|
|
color_gamma = 1.1
|
|
vk_present = FIFO
|
|
render_scale = 300
|
|
dlss_preset = k
|
|
pad_layout = xbox_prompts
|
|
monitor = 2
|
|
'''
|
|
|
|
BAD = '''[frame rate]
|
|
pace_smooth = 0 ; off
|
|
fullscreen = false
|
|
sharpness = 250
|
|
color_gamma = 2
|
|
vk_present = vsync
|
|
fps_limit = 144 # my monitor
|
|
sharpness_typo = 3
|
|
'''
|
|
|
|
|
|
def run(exe, env, case, text, work, extra_env=None):
|
|
ini = work / f'{case}_{len(text)}.ini'
|
|
ini.write_text(text, encoding='utf-8')
|
|
e = dict(env)
|
|
for k in list(e):
|
|
if k.upper().startswith('NX2NAVI_'):
|
|
del e[k]
|
|
e.update(extra_env or {})
|
|
p = subprocess.run([str(exe), case, str(ini)], env=e, capture_output=True, text=True, errors='replace', timeout=60)
|
|
if p.returncode:
|
|
print(p.stdout, p.stderr)
|
|
return p.returncode, p.stdout
|
|
|
|
|
|
def main():
|
|
work = ROOT / 'build' / 'test' / 'cfg_parse'
|
|
work.mkdir(parents=True, exist_ok=True)
|
|
exe = work / ('cfg_parse_check.exe' if os.name == 'nt' else 'cfg_parse_check')
|
|
env = dict(os.environ)
|
|
if os.name == 'nt' and MSYS_BIN.exists():
|
|
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
|
|
rt = ROOT / 'runtime'
|
|
subprocess.check_call([compiler(), '-std=gnu11', '-O1', '-g', '-Wall', '-Wno-unused-function', '-I', str(rt),
|
|
str(ROOT / 'tests' / 'cfg_parse_check.c'), str(rt / 'cfg.c'), '-o', str(exe)], env=env)
|
|
bad = 0
|
|
|
|
def check(ok, what, out=''):
|
|
nonlocal bad
|
|
print(f'{what}: {"ok" if ok else "FAIL"}', flush=True)
|
|
if not ok:
|
|
bad += 1
|
|
print(out)
|
|
|
|
# values that fit: no line about them
|
|
rc, out = run(exe, env, 'load', CLEAN, work)
|
|
lines = [l for l in out.splitlines() if l.startswith('log: cfg:') and '=' in l and 'line' in l]
|
|
check(rc == 0 and not lines, 'a file whose values fit logs nothing about them', out)
|
|
|
|
# values that do not: one line each, with the line number
|
|
rc, out = run(exe, env, 'load', BAD, work, {'NX2NAVI_SHARPNESS': '500', 'NX2NAVI_VK_PRESENT': 'fifo'})
|
|
want = [
|
|
"line 2: pace_smooth = '0 ; off' is not 0 or 1; a ';' or '#' after the value is part of it",
|
|
"line 3: fullscreen = 'false' is not 0 or 1",
|
|
"line 4: sharpness = '250' is not a number in 0..100",
|
|
"line 5: color_gamma = '2' is not a number in 0.8..1.2",
|
|
"line 6: vk_present = 'vsync' is none of the menu's choices mailbox|fifo|immediate",
|
|
"line 7: fps_limit = '144 # my monitor' a ';' or '#' after the value is part of it",
|
|
"the environment's NX2NAVI_SHARPNESS = '500' is not a number in 0..100",
|
|
]
|
|
for w in want:
|
|
check(rc == 0 and w in out, f'logged: {w}', out if w not in out else '')
|
|
check('NX2NAVI_VK_PRESENT' not in out, 'an environment value that fits is not logged', out)
|
|
check("pace_smooth '0 ; off'" in out, 'the value itself is read as written', out)
|
|
|
|
# at most 16 lines, then a count
|
|
many = '\n'.join(f'sharpness = {200 + i}' if i % 2 else f'fullscreen = no{i}' for i in range(2)) + '\n'
|
|
many = ''.join(f'[s{i}]\nsharpness = {200 + i}\n' for i in range(1)) + ''.join(
|
|
f'{k} = 7\n' for k in ('fullscreen', 'pace_smooth', 'vk_present', 'sharpness', 'color_gamma', 'perf_overlay',
|
|
'upscaler', 'standin_filter', 'dlss', 'dlss_preset', 'framegen', 'aa', 'aspect',
|
|
'texture_detail', 'color_preset', 'device_profile', 'mods_rebuild', 'operation_mode',
|
|
'text_speed', 'sprint_mode'))
|
|
rc, out = run(exe, env, 'load', many, work)
|
|
listed = sum(1 for l in out.splitlines() if l.startswith('log: cfg: nx2navi.ini line') and "= '7'" in l)
|
|
check(rc == 0 and listed == 16 and 'more values outside their settings' in out,
|
|
f'20 values outside their sets: {listed} lines and a count', out)
|
|
# more keys than the table holds: one line at load, one for the menu's changes
|
|
full = ''.join(f'cheat_{0x1000 + i:08x} = 1\n' for i in range(600)) + 'low_latency = 1\n'
|
|
rc, out = run(exe, env, 'full', full, work)
|
|
load_lines = [l for l in out.splitlines() if 'has more than 512 keys' in l]
|
|
set_lines = [l for l in out.splitlines() if 'settings table is full' in l]
|
|
check(rc == 0 and len(load_lines) == 1 and 'line 513 (CHEAT_' in load_lines[0] and 'got: low_latency (unset)' in out,
|
|
'a file with 601 keys: one line at load from line 513 on', out)
|
|
check(rc == 0 and len(set_lines) == 1 and 'VK_PRESENT is not kept' in set_lines[0],
|
|
'two menu changes past a full table: one line', out)
|
|
rc, out = run(exe, env, 'norow', CLEAN, work, {'NX2NAVI_NO_ROW_CHECK': 'abc'})
|
|
check(rc == 0 and 'got: cfg_int 0 cfg_double 0' in out, 'cfg_int of a set, unparsable key without a row: 0', out)
|
|
print(f'cfg_parse_check: {"FAILED" if bad else "ok"}')
|
|
return 1 if bad else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|