Files
lucky-cat/tools/test_lcd_fortune_gfx.py

149 lines
9.3 KiB
Python

#!/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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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(now<deadline)now=deadline;return stop?1:0;}
static void lcd_direct_signal_completion(void){++completions;}
static uint64_t lcd_direct_emote_fps_x100(uint32_t n,uint64_t t){return t?n*100000000ULL/t:0;}
static const char *esp_err_to_name(int e){return "mock";}
'''
TEST=r'''
static void reset(void){assert(live==0 && !lock_depth && !s_fortune_orphan);
allocs=fail_alloc=fail_obj=fail_lock=fail_unlock=fail_delete=waits=refreshes=cancel_wait=fail_refresh=completions=0;
now=0;stop=s_emote_active=s_emote_transfer_fault=false;}
int main(void){
uint8_t out[64];
assert(!lcd_emote_fortune_decode(NULL,1,out,1));
const uint8_t invalid[][5]={{0x10},{0x00,0,0},{0x00,1,0},{0xf0,255,255},{0x11,5}};
for(size_t i=0;i<5;++i)assert(!lcd_emote_fortune_decode(invalid[i],sizeof(invalid[i]),out,sizeof(out)));
for(size_t r=0;r<5;++r){
reset();lcd_direct_fortune_scene_t *s=lcd_direct_fortune_prepare(r);assert(s && live==9);
const size_t bytes=LCD_FORTUNE_WIDTH*LCD_FORTUNE_HEIGHT*2;
uint8_t *guard=malloc(bytes+2);guard[0]=guard[bytes+1]=0xa5;
for(unsigned ms=0;ms<=4000;ms+=17){
assert(lcd_emote_fortune_build_frame(guard+1,bytes,s->shared,&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