from pathlib import Path
import re,subprocess
p=Path(__file__).resolve().parent;t=p/'firmware/固件/lamp_target';out=p/'pins_host_test';out.mkdir(exist_ok=True)
s=(t/'Device/Include/stm32g030xx.h').read_text();s=re.sub(r'^#include "(?:core_cm0plus|system_stm32g0xx)\.h"[^\n]*','',s,flags=re.M)
(out/'registers.h').write_text('#include <stdint.h>\n#define __IO volatile\n#define __I volatile const\n#define __O volatile\n'+s)
(out/'stm32g0xx.h').write_text('#include "registers.h"\n#undef GPIOB\n#undef RCC\n#undef I2C1\nextern GPIO_TypeDef b;extern RCC_TypeDef r;extern I2C_TypeDef i;\n#define GPIOB (&b)\n#define RCC (&r)\n#define I2C1 (&i)\n#define __DSB() ((void)0)\n')
test=r'''#include "stm32g0xx.h"
#include "lamp_i2c_pins.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
GPIO_TypeDef b;RCC_TypeDef r;I2C_TypeDef i;
static void seed(unsigned ones){
 memset(&b,ones?255:0,sizeof b);memset(&r,0,sizeof r);memset(&i,0,sizeof i);b.LCKR=0;
}
int main(void){
 for(unsigned ones=0;ones<2;ones++){
 seed(ones);GPIO_TypeDef old=b;assert(lamp_i2c_pins_init());assert(lamp_i2c_pins_available());
 assert(i.CR1==0 && i.CR2==0 && i.TIMINGR==0);
 assert((b.MODER&~0x3ffc0u)==(old.MODER&~0x3ffc0u));
 assert((b.PUPDR&~0x3ffc0u)==(old.PUPDR&~0x3ffc0u));
 assert((b.OTYPER&~0xc0u)==(old.OTYPER&~0xc0u));
 assert(b.BSRR==0xc0u);
 assert((b.AFR[1]&~15u)==(old.AFR[1]&~15u));
 /* GPIOB9 reset and PB0 key untouched, including latches. */
 assert(b.ODR==old.ODR);
 }
 for(unsigned pin=3;pin<=8;pin++){
 seed(0);b.LCKR=(1u<<16)|(1u<<pin);GPIO_TypeDef old=b;
 assert(!lamp_i2c_pins_init());assert(!lamp_i2c_pins_available());
 assert(memcmp(&b,&old,sizeof b)==0);
 }
 seed(0);i.CR1=I2C_CR1_PE;GPIO_TypeDef old=b;
 assert(!lamp_i2c_pins_init());assert(memcmp(&b,&old,sizeof b)==0);assert(i.CR1==I2C_CR1_PE);
 for(unsigned pin=3;pin<=8;pin++)for(unsigned mode=0;mode<4;mode++){
 seed(0);assert(lamp_i2c_pins_init());
 b.MODER=(b.MODER&~(3u<<(pin*2)))|(mode<<(pin*2));
 assert(lamp_i2c_pins_available()==(mode==((pin==6 || pin==7)?2u:3u)));
 }
 for(unsigned pin=3;pin<=8;pin++){
 seed(0);assert(lamp_i2c_pins_init());b.PUPDR|=1u<<(pin*2);assert(!lamp_i2c_pins_available());
 seed(0);assert(lamp_i2c_pins_init());b.AFR[pin/8]^=1u<<((pin%8)*4);assert(!lamp_i2c_pins_available());
 }
 for(unsigned pin=6;pin<=7;pin++){
 seed(0);assert(lamp_i2c_pins_init());b.OTYPER&=~(1u<<pin);assert(!lamp_i2c_pins_available());
 }
 seed(0);assert(lamp_i2c_pins_init());r.IOPENR=0;assert(!lamp_i2c_pins_available());
 seed(0);assert(lamp_i2c_pins_init());r.APBENR1=0;assert(!lamp_i2c_pins_available());
 puts("PASS actual pin initializer/runtime guard: AF6 mapping, alias modes, open-drain, active peripheral rejection, six locks, unrelated pins preserved, no PE/START/TIMING writes");
}
'''
(out/'test.c').write_text(test)
for name,source in [('actual',(t/'src/lamp_i2c_pins.c').read_text()),('wrong_AF',(t/'src/lamp_i2c_pins.c').read_text().replace('0x66000000u','0x55000000u'))]:
    # Independent assertion of selected AF bits also rejects a consistently wrong mapping.
    f=out/(name+'.c');f.write_text(source)
    tf=out/'test.c';tf.write_text(test.replace('assert(b.BSRR==0xc0u);','assert(b.BSRR==0xc0u);assert((b.AFR[0]&0xff000000u)==0x66000000u);'))
    exe=out/name
    subprocess.run(['cc','-std=c11','-Wall','-Wextra','-Werror','-fsanitize=address,undefined','-I'+str(out),'-I'+str(t/'include'),str(f),str(tf),'-o',str(exe)],check=True)
    r=subprocess.run([str(exe)],capture_output=True,text=True)
    (p/(name+'_i2c_pins.log')).write_text(r.stdout+r.stderr)
    assert (r.returncode==0) if name=='actual' else (r.returncode!=0)
    print(name,'PASS' if name=='actual' else 'REJECTED',r.stdout.strip())
