from pathlib import Path
import subprocess,shutil,json
R=Path(__file__).resolve().parent;D=R/'firmware/固件';T=D/'lamp_target';C=D/'lamp_control';O=R/'host_test';O.mkdir(exist_ok=True)
shutil.copy2(R/'test_support/registers.h',O/'registers.h')
(O/'stm32g0xx.h').write_text('''#include "registers.h"
#undef GPIOA
#undef GPIOB
extern GPIO_TypeDef a,b;
#define GPIOA (&a)
#define GPIOB (&b)
''')
(O/'test_board_inputs.c').write_text('''#include "stm32g0xx.h"
#include "lamp_board_inputs.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
GPIO_TypeDef a,b;
int main(void) {
 unsigned cases=0;
 for(unsigned bits=0;bits<128;bits++) {
  memset(&a,0,sizeof a);memset(&b,0,sizeof b);
  const unsigned pins[]={0,9,10,11,12,6};
  for(unsigned n=0;n<6;n++) if(bits&(1u<<n))a.IDR|=(1u<<pins[n]);
  b.IDR=(bits&64u)?1u:0u;
  const GPIO_TypeDef prior_a=a,prior_b=b;
  lamp_board_input_snapshot s=lamp_board_inputs_read();
  assert(s.thermal_contact_closed==((bits&16u)!=0));
  assert(s.hardware_interlock_closed==((bits&1u)!=0));
  assert(s.user_key_pressed==((bits&64u)==0));
  assert(s.guard_inputs.hardware_interlock_ok==s.hardware_interlock_closed);
  assert(!s.guard_inputs.temperature_ok && !s.guard_inputs.supply_ok);
  assert(!s.guard_inputs.led_driver_ok && !s.guard_inputs.infrared_hand_present);
  assert(memcmp(&a,&prior_a,sizeof a)==0 && memcmp(&b,&prior_b,sizeof b)==0);
  lg_guard g;const lg_config c={.channels=1,.max_run_ms=100,.ticket_lifetime_ms=20,.max_poll_gap_ms=10,.channel_input_mw={1},.input_budget_mw=1,.calibration_id=1};
  const lg_inputs good={true,true,true,true,true};lg_frame f;
  assert(lg_init(&g,&c,1,0));lg_poll(&g,0,good);assert(lg_arm(&g,LG_AI,50,0));
  assert(lg_issue_ticket(&g,0,&f));f.source_frame_seq=1;f.hand_present=true;f.pwm[0]=1;
  assert(lg_accept_ai(&g,&f,0) && g.output_enable);
  lg_poll(&g,1,s.guard_inputs);
  assert(g.mode==LG_FAULT && !g.output_enable && !g.pending_ticket && !g.armed);
  assert(!lg_issue_ticket(&g,1,&f));assert(!lg_accept_ai(&g,&f,1));
  cases++;
 }
 printf("PASS: %u GPIO combinations; PA12 vs PA10 alias isolation; read-only sampler; unbound IR/ADC/driver stays false; active AI output cancelled by real sampler/policy path. MMIO model only.\\n",cases);
}
''')
flags=['cc','-std=c11','-Wall','-Wextra','-Werror','-pedantic','-g','-fsanitize=address,undefined','-I'+str(C)]
helper=[p for p in C.glob('*.c') if not p.name.startswith('test_')]
logs=[];results={}
for n in ['lamp_guard','lamp_program','lamp_ir','lamp_ticket_dispatch','lamp_ui_dispatch_integrated','lamp_ui_wire_service','lamp_wire_pipeline','lamp_output_adapter']:
 exe=O/n;sources=[C/('test_'+n+'.c')]+helper
 subprocess.run(flags+[str(p) for p in sources]+['-o',str(exe)],check=True)
 x=subprocess.run([str(exe)],capture_output=True,text=True);assert x.returncode==0,x.stderr
 logs.append(x.stdout);results[n]='PASS_ASAN_UBSAN';print(x.stdout,end='')
exe=O/'board_inputs';subprocess.run(flags+['-I'+str(O),'-I'+str(T/'include'),str(O/'test_board_inputs.c'),str(T/'src/lamp_board_inputs.c'),str(C/'lamp_guard.c'),'-o',str(exe)],check=True)
x=subprocess.run([str(exe)],capture_output=True,text=True);assert x.returncode==0,x.stderr;logs.append(x.stdout);results['board_inputs']='128_CASES_PASS_ASAN_UBSAN';print(x.stdout,end='')
(R/'tests.log').write_text(''.join(logs));(R/'test_results.json').write_text(json.dumps(results,indent=2)+'\n')
# Re-run unchanged physical boot regression on A37's actual main source.
subprocess.run(['python3',str(R/'test_boot.py')],check=True)
