149 lines
9.3 KiB
Python
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
|