#!/usr/bin/env python3 """抽签实际 C 解码/几何、五种资源、分配失败/取消/释放主机回归,非设备 FPS。""" from pathlib import Path import re import subprocess import tempfile ROOT=Path(__file__).resolve().parents[1] SRC=(ROOT/"components/platform/src/lcd_direct.c").read_text() def extract(name): match=re.search(r"static [^;{}]+\b"+name+r"\([^;{}]*\)\s*\{",SRC) assert match,name start=match.start();end=SRC.index("{",start)+1;depth=1 while depth: if SRC[end]=="{":depth+=1 if SRC[end]=="}":depth-=1 end+=1 return SRC[start:end] PREFIX=r''' #include #include #include #include #include "lcd_emote_fortune.h" #define ESP_OK 0 #define ESP_ERR_INVALID_STATE 1 #define ESP_ERR_NO_MEM 2 #define ESP_ERR_INVALID_SIZE 3 #define ESP_ERR_NOT_SUPPORTED 4 #define ESP_ERR_TIMEOUT 5 #define PLATFORM_LCD_PRESENTATION_NO_ICON 255 #define MALLOC_CAP_SPIRAM 1 #define MALLOC_CAP_8BIT 2 #define GFX_COLOR_FORMAT_RGB565 0 #define GFX_COLOR_FORMAT_RGB565A8 1 #define C_ARRAY_HEADER_MAGIC 0 #define ESP_LOGI(...) ((void)0) #define ESP_LOGE(...) ((void)0) #define portENTER_CRITICAL(x) ((void)0) #define portEXIT_CRITICAL(x) ((void)0) typedef int esp_err_t; typedef struct {const char *asset_dir;unsigned frame_index,icon_id,delay_ms;} platform_lcd_presentation_step_t; typedef struct {size_t step_count;platform_lcd_presentation_step_t steps[6];} lcd_direct_request_t; typedef struct { struct {int cf,magic,w,h;} header;size_t data_size;const uint8_t *data;} gfx_image_dsc_t; typedef struct {bool visible;int x,y;const gfx_image_dsc_t *src;} gfx_obj_t; static void *s_emote_handle=(void*)1,*s_emote_disp=(void*)1; static gfx_obj_t mesh,closed,cache,listening,bg,scene_obj; static gfx_obj_t *s_emote_eyes_open=&mesh,*s_emote_closed_eyes=&closed,*s_emote_cached_eyes=&cache; static gfx_obj_t *s_listening_eyes=&listening,*s_emote_background=&bg; static bool s_emote_active,s_emote_transfer_fault,stop; static int lock_depth,allocs,live,fail_alloc,fail_obj,fail_lock,fail_unlock,fail_delete; static int completions,waits,cancel_wait,refreshes,fail_refresh; static int64_t now,deadlines[100]; static int64_t esp_timer_get_time(void){return now;} static bool lcd_direct_should_stop_idle(void){return stop;} static void *heap_caps_malloc(size_t n,int cap){assert(cap==3);if(++allocs==fail_alloc)return NULL;void *p=malloc(n);if(p)++live;return p;} static void *heap_caps_calloc(size_t n,size_t s,int cap){void *p=heap_caps_malloc(n*s,cap);if(p)memset(p,0,n*s);return p;} static void test_free(void *p){if(p)--live;free(p);} #define free test_free static int gfx_emote_lock(void *h){if(fail_lock){--fail_lock;return 5;}assert(!lock_depth);++lock_depth;return 0;} static int gfx_emote_unlock(void *h){assert(lock_depth==1);--lock_depth;if(fail_unlock){--fail_unlock;return 5;}return 0;} static gfx_obj_t *gfx_img_create(void *d){assert(lock_depth==1);return fail_obj?NULL:&scene_obj;} static int gfx_obj_delete(gfx_obj_t *o){assert(lock_depth==1);if(fail_delete)return 5;o->src=NULL;return 0;} static int gfx_img_set_src(gfx_obj_t *o,void *src){assert(lock_depth==1);o->src=src;return 0;} static int gfx_obj_set_pos(gfx_obj_t *o,int x,int y){assert(lock_depth==1);o->x=x;o->y=y;return 0;} static int gfx_obj_set_visible(gfx_obj_t *o,bool v){assert(lock_depth==1);o->visible=v;return 0;} static int lcd_direct_emote_update_hud(bool force,bool *change){assert(lock_depth==1);return 0;} static int lcd_direct_emote_select_face(bool face){assert(lock_depth==1);bg.src=NULL;return 0;} static void gfx_disp_refresh_all(void *d){assert(lock_depth==1);} static int lcd_direct_emote_refresh(void){assert(lock_depth==1 && s_emote_active);++refreshes;now+=13000;return refreshes==fail_refresh?5:0;} static int lcd_direct_emote_wait_until(int64_t deadline){assert(!lock_depth && waits<100);deadlines[waits++]=deadline; if(waits==cancel_wait){stop=true;return 1;} if(nowshared,&s->text,ms)); assert(guard[0]==0xa5 && guard[bytes+1]==0xa5); } assert(!lcd_emote_fortune_build_frame(guard+1,bytes-1,s->shared,&s->text,0)); assert(!lcd_emote_fortune_build_frame(NULL,bytes,s->shared,&s->text,0)); assert(!lcd_emote_fortune_build_frame(guard+1,bytes,NULL,&s->text,0)); lcd_emote_fortune_build_frame(guard+1,bytes,s->shared,&s->text,4000); lcd_emote_fortune_build_frame(s->frame,bytes,s->shared,&s->text,2500); assert(!memcmp(guard+1,s->frame,bytes)); /* 最终签文输出供回归查看;五种结果不能全都变成同一张。 */ char path[100];snprintf(path,sizeof(path),"/tmp/lucky-fortune-%zu.rgb565",r); FILE *f=fopen(path,"wb");assert(f);fwrite(s->frame,1,bytes,f);fclose(f); /* 截断输入和不足的解压空间不允许写越界。 */ for(size_t i=0;i<6;++i){const lcd_fortune_asset_t *a=&lcd_fortune_shared[i];size_t n=(size_t)a->width*a->height*3; uint8_t *bad=malloc(n+2);bad[0]=bad[n+1]=0xa5; assert(!lcd_emote_fortune_decode(a->data,a->compressed_size-1,bad+1,n)); assert(!lcd_emote_fortune_decode(a->data,a->compressed_size,bad+1,n-1)); assert(bad[0]==0xa5 && bad[n+1]==0xa5); /* 普通测试内存不计入设备分配追踪。 */ ++live;free(bad); } ++live;free(guard);lcd_direct_fortune_free(s);assert(live==0); lcd_direct_request_t req={.step_count=6}; for(size_t i=0;i<6;++i)req.steps[i]=(platform_lcd_presentation_step_t){i<3?"chouqian_qianzhi":lcd_fortune_result_dirs[r],i%3,255,i==5?1500:500}; assert(lcd_direct_fortune_sequence_index(&req)==r); req.steps[4].delay_ms=100;assert(lcd_direct_fortune_sequence_index(&req)==-1); req.steps[4].delay_ms=500;req.steps[3].asset_dir=NULL;assert(lcd_direct_fortune_sequence_index(&req)==-1); reset();assert(lcd_direct_play_fortune(r)==0 && live==0 && completions==1 && !s_emote_active); assert(waits==75 && refreshes==75 && now==4013000 && deadlines[74]==4013000); for(int i=0;i<73;++i)assert(deadlines[i]==(i+1)*34000); assert(deadlines[73]==2500000); } for(int n=1;n<=9;++n){reset();fail_alloc=n;assert(lcd_direct_play_fortune(0)==4 && !live && !completions);} reset();fail_obj=1;assert(lcd_direct_play_fortune(0)==4 && !live); reset();fail_lock=1;assert(lcd_direct_play_fortune(0)==5 && !live); for(int n=1;n<=75;++n){reset();cancel_wait=n;assert(lcd_direct_play_fortune(0)==1 && !live && !completions && !s_emote_active);} reset();fail_refresh=3;assert(lcd_direct_play_fortune(0)==5 && !live && !completions); reset();fail_delete=1;assert(lcd_direct_play_fortune(0)==5 && s_fortune_orphan && live==9 && !completions); assert(lcd_direct_play_fortune(0)==1);fail_delete=0; assert(!lcd_direct_fortune_detach(s_fortune_orphan));lcd_direct_fortune_free(s_fortune_orphan);s_fortune_orphan=NULL; reset();s_emote_transfer_fault=true;assert(lcd_direct_play_fortune(0)==1 && !live); puts("PASS: five assets/results, LZ4 corruption/bounds, 1180 geometry frames, nine allocation failures, 75 cancellation points, anchored 4s playback, cleanup/transfer fault"); } ''' start=SRC.index("typedef struct {\n lcd_fortune_texture_t shared") end=SRC.index("static int lcd_direct_fortune_sequence_index",start) names=["lcd_direct_fortune_sequence_index","lcd_direct_fortune_free","lcd_direct_fortune_prepare", "lcd_direct_fortune_detach","lcd_direct_play_fortune"] with tempfile.TemporaryDirectory(prefix="fortune-test-") as tmp: path=Path(tmp);c=path/"test.c";exe=path/"test" c.write_text(PREFIX+SRC[start:end]+"\n"+"\n".join(extract(n) for n in names)+TEST) for profile in (0,1): subprocess.run(["clang","-std=c11","-O1","-g","-fsanitize=address,undefined", f"-DCONFIG_TQ_LCD_EMOTE_FPS_LOG={profile}","-I",str(ROOT/"components/platform/internal"), str(c),str(ROOT/"components/platform/src/lcd_emote_fortune.c"), str(ROOT/"components/platform/src/generated/emote_gfx/fortune_assets.c"),"-o",str(exe)],check=True) subprocess.run([str(exe)],check=True) assert "DOMAIN_EXPRESSION_" not in (ROOT/"components/platform/src/lcd_emote_fortune.c").read_text() assert "if (err != ESP_ERR_NOT_SUPPORTED) return err;" in SRC