from pathlib import Path
import re,shutil,subprocess,json
R=Path(__file__).resolve().parent;O=R/'host_test';O.mkdir(exist_ok=True)
D=R/'firmware/固件/vacuum_target';H=R/'firmware/固件/stm32_shared/Device/Include/stm32g030xx.h'
h=H.read_text();h=re.sub(r'^#include "(?:core_cm0plus|system_stm32g0xx)\.h"[^\n]*','',h,flags=re.M)
(O/'registers.h').write_text('#include <stdint.h>\n#define __IO volatile\n#define __I volatile const\n#define __O volatile\n'+h)
prefix='''#include "registers.h"
#include <stdbool.h>
#include <stddef.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#undef ADC1
ADC_TypeDef adc;
#define ADC1 (&adc)
#define ADC_POLL_LIMIT 100000u
static bool g_adc_ready;
static unsigned ready_delay,convert_delay,selected,starts,channel_writes;
static bool converting,overrun;
static void host_w1c(uint32_t mask) {adc.ISR &= ~mask;}
static void host_channel(uint32_t mask) {
 adc.CHSELR=mask;selected=0;while((mask>>selected)!=1u)++selected;
 channel_writes++;adc.ISR &= ~ADC_ISR_CCRDY;
 if(!ready_delay)adc.ISR |= ADC_ISR_CCRDY;
}
static void complete(void) {
 adc.DR=100u+selected;adc.ISR |= ADC_ISR_EOC|ADC_ISR_EOS;
 if(overrun)adc.ISR |= ADC_ISR_OVR;else adc.CR &= ~ADC_CR_ADSTART;
 converting=false;
}
static void host_start(void) {
 starts++;
 /* Hardware ignores ADSTART before channel configuration acknowledgement. */
 if(!(adc.ISR&ADC_ISR_CCRDY)) return;
 adc.CR |= ADC_CR_ADSTART;converting=true;if(!convert_delay)complete();
}
static void host_cycle(void) {
 if(ready_delay && --ready_delay==0)adc.ISR |= ADC_ISR_CCRDY;
 if(converting && convert_delay && --convert_delay==0)complete();
}
'''
suffix='''
static void seed(unsigned rd,unsigned cd,bool ov) {
 memset(&adc,0,sizeof adc);g_adc_ready=true;ready_delay=rd;convert_delay=cd;
 selected=starts=channel_writes=0;converting=false;overrun=ov;
 adc.ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_OVR|ADC_ISR_CCRDY;adc.DR=4095;adc.CR=ADC_CR_ADEN;
}
int main(void) {
 unsigned cases=0;const unsigned channels[]={0,1,3,4,5,6,13,18};
 const unsigned rd[]={0,1,7},cd[]={0,1,9};
 for(unsigned ch=0;ch<8;ch++) for(unsigned i=0;i<3;i++) for(unsigned j=0;j<3;j++) {
  seed(rd[i],cd[j],false);uint16_t raw=555;
  assert(read_adc(channels[ch],&raw));assert(raw==100u+channels[ch]);
  assert(starts==1 && channel_writes==1 && g_adc_ready);cases++;
 }
 seed(ADC_POLL_LIMIT+1,0,false);uint16_t raw=555;assert(!read_adc(1,&raw));assert(raw==555 && starts==0 && !g_adc_ready);
 assert(!read_adc(5,&raw));assert(starts==0 && raw==555);
 seed(0,ADC_POLL_LIMIT+1,false);assert(!read_adc(1,&raw));assert(raw==555 && !g_adc_ready && (adc.CR&ADC_CR_ADSTP));
 seed(0,3,true);assert(!read_adc(1,&raw));assert(raw==555 && !g_adc_ready && (adc.CR&ADC_CR_ADSTP));
 seed(0,0,false);assert(!read_adc(19,&raw));assert(!read_adc(1,NULL));assert(channel_writes==0 && raw==555);
 adc.CR|=ADC_CR_ADSTART;assert(!read_adc(1,&raw));assert(channel_writes==0);
 printf("PASS: %u actual read_adc success cases; delayed CCRDY/EOC, stale flags/raw data, channel identity, timeout/overrun latch, invalid/null/busy rejection. MMIO event model only.\\n",cases);
}
'''
results={}
for name,src in [('fixed',D/'src/vacuum_board.c'),('original',R/'original_vacuum_board.c')]:
 s=src.read_text();s=s[s.index('static bool read_adc'):s.index('static void init_tach_capture')]
 # Instrument only MMIO side effects and hardware progression, retaining the
 # actual channel-selection/start/order/timeout implementation.
 s=re.sub(r'ADC1->ISR = ([^;]+);',r'host_w1c(\1);',s)
 s=s.replace('ADC1->CHSELR = 1u << channel;','host_channel(1u << channel);').replace('ADC1->CR |= ADC_CR_ADSTART;','host_start();').replace('--timeout;','--timeout;host_cycle();')
 p=O/(name+'.c');p.write_text(prefix+s+suffix);exe=O/name
 subprocess.run(['cc','-std=c11','-Wall','-Wextra','-Werror','-pedantic','-fsanitize=address,undefined','-I'+str(O),str(p),'-o',str(exe)],check=True)
 x=subprocess.run([str(exe)],capture_output=True,text=True);(R/(name+'_test.log')).write_text(x.stdout+x.stderr)
 if name=='fixed':assert x.returncode==0,x.stderr
 else:assert x.returncode!=0 and 'read_adc(channels[ch],&raw)' in x.stderr,x.stderr
 results[name]={'returncode':x.returncode,'expected':'PASS' if name=='fixed' else 'Original unacknowledged-channel conversion rejected'};print(name,x.returncode,x.stdout.strip())
(R/'ADC_test_results.json').write_text(json.dumps(results,indent=2)+'\n')
