"""实际 HUD 函数与生成资产的 ASan/UBSan 回归;不启动 LVGL/GFX 引擎。""" from pathlib import Path import subprocess import tempfile import re ROOT = Path(__file__).resolve().parents[1] src = (ROOT/'components/platform/src/lcd_direct.c').read_text() def function(name): matches = list(re.finditer(r'static [^;{}]+\b'+name+r'\([^;{}]*\)\s*\{', src)) assert len(matches) == 1, name start = matches[0].start(); p = src.index('{', start); depth = 1; end = p+1 while depth: if src[end] == '{': depth += 1 if src[end] == '}': depth -= 1 end += 1 return src[start:end] prefix = '''#include #include #include #include #include #include "lcd_status_assets.h" #define LCD_DIRECT_CLOCK_X 12U #define LCD_DIRECT_CLOCK_Y 14U #define LCD_DIRECT_CLOCK_TEXT_LENGTH 5U #define LCD_DIRECT_CLOCK_COLOR 0xffffU #define LCD_DIRECT_CLOCK_OUTLINE_COLOR 0U #define LCD_DIRECT_BATTERY_WIDTH 40U #define LCD_DIRECT_BATTERY_HEIGHT 20U #define LCD_DIRECT_BATTERY_X (LCD_DIRECT_WIDTH - 48U) #define LCD_DIRECT_BATTERY_Y 12U #define portENTER_CRITICAL(x) ((void)0) #define portEXIT_CRITICAL(x) ((void)0) typedef struct {bool low_battery_visible, charging_visible, battery_valid; uint8_t battery_percent;} platform_lcd_status_overlay_t; static platform_lcd_status_overlay_t s_status_overlay; #define PLATFORM_LCD_PRESENTATION_IDLE 0 typedef struct {int kind;} lcd_direct_request_t; static lcd_direct_request_t s_pending; static bool s_pending_valid, s_stop_requested; ''' test = '''int main(int argc, char **argv) { uint16_t full[LCD_DIRECT_WIDTH*240+2], blocks[LCD_DIRECT_WIDTH*240+2]; for (unsigned valid=0;valid<2;++valid) for (unsigned charge=0;charge<2;++charge) for (unsigned low=0;low<2;++low) for (unsigned percent=0;percent<=255;++percent) { s_status_overlay=(platform_lcd_status_overlay_t){low,charge,valid,percent}; for (unsigned i=0;i=LCD_DIRECT_BATTERY_X+40 || y<12 || y>=32) assert(full[1+y*LCD_DIRECT_WIDTH+x]==0x1234); } if(valid) { const lcd_status_icon_t *icon=&lcd_status_battery_icons[level+(charge?5:0)]; unsigned p=0; for(unsigned r=0;rrun_bytes;r+=2) { const uint8_t *value=icon->palette+icon->runs[r+1]*3; for(unsigned n=0;nruns[r];++n,++p) { unsigned index=1+(12+p/40)*LCD_DIRECT_WIDTH+LCD_DIRECT_BATTERY_X+p%40; uint16_t color=((uint16_t)value[0]<<8)|value[1]; assert(full[index]==lcd_direct_swap_color(lcd_direct_blend_rgb565(lcd_direct_swap_color(0x1234),color,value[2]))); } } assert(p==800); } } for(unsigned hour=0;hour<24;++hour) for(unsigned minute=0;minute<60;++minute) { char text[6]; snprintf(text,sizeof(text),"%02u:%02u",hour,minute); for(unsigned i=0;i=64 || y<14 || y>=31) assert(full[1+y*LCD_DIRECT_WIDTH+x]==0x1234); } lcd_direct_overlay_clock(full+1,0,240,"--:--"); lcd_direct_overlay_clock(NULL,0,240,"--:--"); lcd_direct_overlay_clock(full+1,0,240,NULL); lcd_direct_overlay_battery(NULL,0,240); for(unsigned pending=0;pending<2;++pending) for(unsigned stopping=0;stopping<2;++stopping) { s_pending=(lcd_direct_request_t){99}; s_pending_valid=pending; s_stop_requested=stopping; lcd_direct_queue_idle_if_available(); assert(s_pending.kind==((pending || stopping)?99:PLATFORM_LCD_PRESENTATION_IDLE)); assert(s_pending_valid==(pending || !stopping)); } if(argc==2) { memset(full,0,sizeof(full)); lcd_direct_overlay_clock(full,0,240,"12:34"); s_status_overlay=(platform_lcd_status_overlay_t){false,true,true,50}; lcd_direct_overlay_battery(full,0,240); FILE *f=fopen(argv[1],"wb"); assert(f); fwrite(full,2,LCD_DIRECT_WIDTH*240,f); fclose(f); } puts("PASS: 2048 battery states, all 1440 times, alpha/background, byte order, 8-row equivalence, bounds and idle guards"); }''' names = ['lcd_direct_swap_color', 'lcd_direct_rgb888_to_rgb565', 'lcd_direct_blend_rgb565', 'lcd_direct_clock_glyph_index', 'lcd_direct_hud_blend_pixel', 'lcd_direct_draw_clock_pass', 'lcd_direct_overlay_clock', 'lcd_direct_battery_icon_index', 'lcd_direct_overlay_battery', 'lcd_direct_queue_idle_if_available'] with tempfile.TemporaryDirectory(prefix='lucky-hud-') as tmp: c = Path(tmp)/'hud.c'; exe = Path(tmp)/'hud' c.write_text(prefix+'\n'+'\n'.join(function(n) for n in names)+'\n'+test) for width in (320, 240): for swap in (0, 1): subprocess.run(['clang','-std=c11','-fsanitize=address,undefined','-g', f'-DLCD_DIRECT_WIDTH={width}', f'-DCONFIG_TQ_SCREEN_SWAP_COLOR_BYTES={swap}', '-I'+str(ROOT/'components/platform/internal'),str(c), str(ROOT/'components/platform/src/generated/lcd_status_assets.c'), '-o',str(exe)],check=True) preview = ['/tmp/lucky-status-hud.rgb565'] if width == 320 and swap == 0 else [] subprocess.run([str(exe), *preview],check=True)