결정적 재생과 장시간 검증
100분 안팎
학습 목표
고정 seed 입력과 가상 24시간 이벤트를 재생하고 sanitizer·버퍼 한계를 검사합니다.
개념
결정적 입력은 실패를 다시 만나는 약속입니다
오랫동안 켜 놓았더니 한 번 실패했다는 보고만으로는 코드 수정을 검증하기 어렵습니다. 입력 순서와 시각을 고정하면 같은 결과를 반복해서 비교할 수 있습니다. 이번 레슨에서는 이미 완성된 누적 기록기에 재생 드라이버를 붙입니다. seed·시작 시각·사건 수·간격·소비 순서를 계약으로 기록하고 같은 seed의 CSV가 바이트 단위로 같은지 확인합니다. 무작위 입력이라는 말보다 생성 규칙을 다시 실행할 수 있다는 점이 핵심입니다.
release_next는 uint32_t x에 좌측 13비트 XOR shift, 우측 17비트 XOR shift, 좌측 5비트 XOR shift를 순서대로 적용합니다. 계산한 x를 seed가 가리키는 객체에 저장한 뒤 반환합니다. 부호 없는 32비트 연산의 순환을 사용하며 signed int로 바꾸지 않습니다. 이 생성기는 실습용 결정적 입력이며 암호나 공격 방어를 위한 난수가 아닙니다. seed 0은 계속 0이 되는 상태여서 replay CLI에서 거부합니다.
함수 테스트는 seed 1의 첫 값 270369와 다음 값 67634689를 직접 비교합니다. 단순히 같은 seed 두 실행이 같다는 검사는 상수 반환 함수도 통과시킬 수 있으므로 알려진 두 값 검사가 필요합니다. 반환값만 계산하고 seed를 갱신하지 않으면 두 번째 값이 달라지지 않아 실패합니다. starter에서 이 조건이 실패하면 연산 순서·자료형·포인터 갱신을 차례대로 봅니다. 기존 센서 로직을 고칠 필요는 없습니다.
하루의 사건 수와 시각 범위를 정의합니다
release_due는 origin에 index 곱하기 100을 더합니다. index는 0부터 시작하고 863999에서 끝납니다. 관찰 구간은 0 이상 86400000ms 미만이며 사건 수는 864000건입니다. 마지막 due는 86399900ms이고 처음 사건은 0ms입니다. 86400000ms에 사건 하나를 더 넣으면 다음 구간의 첫 사건까지 포함하는 오류가 됩니다. 시간 구간의 양 끝 포함 여부를 명세에 써서 반복문의 작은 차이가 보고서에 숨지 않게 합니다.
replay는 타이머 이벤트를 직접 넣고 due·due+1·due+2에 collect를 호출합니다. 첫 호출은 IDLE에서 측정 시작, 두 번째는 새 값을 획득, 세 번째는 큐에 발행하는 흐름입니다. 이어 due+2에서 dispatch를 한 번 호출합니다. 측정 timestamp는 due+1이고 완료 지연은 due부터 2ms입니다. 다음 사건으로 시각을 점프하므로 실제 하루를 기다리지 않습니다. 버튼과 timer_poll·ISR 동작은 앞 모듈 회귀에서 계속 검사합니다.
시작점을 4294967246ms로 바꾼 20건 재생은 uint32_t 순환을 건넙니다. origin과 index의 계산을 넓은 signed 자료형으로 바꾸었다가 범위 초과로 거부하지 않습니다. 모형은 짧은 순환 간격을 유지하며 이전 resource의 latency_add도 같은 계약을 따릅니다. 하루 시험의 많은 건수와 순환 시험은 서로 다른 결함을 겨냥합니다. 장시간이라는 이름으로 경계 검사를 생략하지 않습니다.
누적 소스에 시험 sink를 붙입니다
프로젝트의 저장 버퍼는 16건이고 SPI 모형은 256바이트입니다. 그 용량 그대로 하루 동안 무한 저장할 수는 없습니다. 재생의 시험 sink는 매 샘플에서 UART decode·기록 원시 ADC·측정 시각·SPI와 UART 바이트 일치를 검사한 뒤 저장 길이를 초기화합니다. 이는 성공 결과를 외부로 내보냈다고 가정하는 검증 장치입니다. 영속 파일 시스템이나 MCU 플래시 드라이버를 구현한 것으로 설명하지 않습니다.
공간을 반환하기 전에 프레임을 검사하는 순서가 중요합니다. 먼저 length를 0으로 만들면 저장 실패나 누락을 확인할 증거가 사라집니다. replay는 한 건과 13바이트가 만들어졌는지 확인한 뒤에만 공간을 돌려줍니다. filter·seq·sent·dropped·stale·cancelled는 하루 전체에 걸쳐 유지합니다. 매 사건마다 파이프라인 전체를 초기화하면 누적 상태 결함을 가리므로 sink가 소유한 저장 공간만 반환합니다.
정상 재생은 매 발행 뒤 즉시 소비하므로 큐 최고점 1·드롭 0을 기대합니다. 센서 word와 ADC는 seed의 비트에서 만들며 온도 값 변화는 필터를 통과합니다. 프로그램은 성공한 프레임마다 FNV-1a 방식의 32비트 digest를 누적합니다. digest는 빠른 변화 신호이며 충돌 가능성이 있어 보안 서명이나 값별 정답 증명이 아닙니다. 같은 seed CSV 전체 대조와 decode·기존 온도 변환 회귀를 함께 사용합니다.
관찰 파일과 실행 경로를 검증합니다
raw.csv는 시간당 36000건을 묶은 24행입니다. 각 행의 samples는 그 블록 수, max_latency_ms는 해당 시점까지의 최대값, digest는 누적값입니다. 개별 864000건 로그를 모두 보존한 파일은 아닙니다. summary는 samples를 더하되 마지막 누적 digest와 dropped를 읽습니다. 누적 열을 모든 행에서 더하면 같은 사건을 여러 번 세는 오류가 생깁니다. 집계 단위를 열별로 정의해 원시 관찰과 요약을 연결합니다.
make evidence는 현재 재생의 raw.csv·summary.json·environment.json을 생성합니다. make evidence-test는 재생 두 번의 일치·다른 seed 30건 차이·순환 20건·잘못된 인자 종료 2·저장된 증거 일치를 검사합니다. AssertionError raw evidence is stale이면 seed나 코드가 달라졌는지 먼저 확인합니다. 기대값을 새 출력으로 덮기 전에 unit과 기존 회귀를 통과시켜 결함을 정답으로 바꾸는 일을 피합니다.
make sanitize는 -fsanitize=undefined·-fno-sanitize-recover=all·-fno-omit-frame-pointer로 별도 실행 파일을 만들고 하루 재생 및 pipeline 경계를 검사합니다. 컴파일과 링크 모두 컴파일러 드라이버를 사용합니다. UBSan의 runtime error와 첫 사용자 소스 위치를 읽고 관련 fixture를 줄입니다. 일반 -Werror 빌드 통과와 런타임 검사 통과는 다른 근거이므로 둘 다 기록합니다.
메모리 진단 없이 종료 0이어도 모든 입력이나 데이터 경쟁의 부재를 증명하지 않습니다. 계측 빌드의 호스트 실행 시간을 MCU 마감으로 해석하지 않습니다. 새 소스에서 큐 객체를 close하고 destroy하는지 확인하며 오류 경로에도 같은 정리가 있습니다. sanitizer가 지원되지 않으면 unknown argument나 링크 메시지를 환경 문제로 기록하고 검사 성공으로 바꾸지 않습니다. 필요한 지원 환경에서 재실행할 과제로 남깁니다.
최종 수정은 release.c 두 함수와 인계 문서입니다. 기존 Makefile test 대상에서 회귀를 지우거나 테스트가 기대하는 seed 값을 바꾸는 방법으로 starter를 통과시키지 않습니다. UBSan 문서의 옵션 범위를 참고하고 도구의 보장 범위와 실습에서 실제로 실행한 경로를 구분합니다. 상세 디버거 사용법은 더 읽기로 이어갑니다.
따라하기
starter의 두 계약 실패 관찰
starter에서 실행합니다. 컴파일 뒤 기준 난수 값·주기·순환 조건 일부가 실패합니다. 뒤의 FAIL은 stderr를 함께 수집한 실제 진단입니다. 기존 구현을 지우는 대신 release.c의 두 TODO를 고칩니다.
make -s release-test실행 결과
release: 9 checks, 6 failures FAIL line=9 release_next(&seed)==270369 FAIL line=9 seed==270369 FAIL line=10 release_next(&seed)==67634689 FAIL line=11 release_due(0,1)==100 FAIL line=12 release_due(0,863999)==86399900 FAIL line=13 release_due(UINT32_MAX-49,1)==50 make: *** [release-test] Error 1
함수 경계 검사 완료
같은 테스트를 완성 코드에서 실행한 출력입니다. seed1의 연속값·index863999·순환·latency 경계를 모두 검사합니다.
make -s release-test실행 결과
release: 9 checks, 0 failures
재생과 증거 생성 및 대조
완성 함수로 증거를 생성합니다. evidence 문서5개도 작성한 뒤 대조 검사를 실행합니다. 동일 seed2회·다른 seed·순환과 잘못된 인자를 포함하며 아래는 완성 solution의 실제 출력입니다.
make -s evidence
make -s evidence-test실행 결과
exported evidence/raw.csv, summary.json, environment.json replay: same seed equal; different seed differs; rollover and invalid inputs pass virtual24h: samples=864000 errors=0 max_latency_ms=2 dropped=0 queue_high=1 evidence: raw.csv and summary.json match replay; 5 handoff documents present
누적 파이프라인에 UBSan 적용
별도 계측 실행 파일로 가상 하루와 pipeline 용량·신선도 경계를 실행한 출력입니다. 실제 장치의 시간이나 데이터 경쟁 전체를 검증한 결과로 확대하지 않습니다.
make -s sanitize실행 결과
pipeline: 55 checks, 0 failures; DROP_NEW=4 STALE=1 CANCEL=17 sent=2 sanitizer: replay864000 and pipeline boundaries passed
확인 문제
실습
release.c의 release_next와 release_due를 구현합니다. xorshift32 좌13·우17·좌5와 seed 갱신, origin+index*100의 unsigned 계산을 보존합니다. make -s release-test 후 make -s evidence를 실행하고 evidence 문서5개를 완성한 뒤 make test를 통과시킵니다. replay.c·테스트·기존 소스를 약화시키지 않습니다. 재생의 시험 sink와 제품 저장 정책의 차이, 블록 원시 로그의 범위, UBSan 검사의 한계를 기록합니다.
실행 명령
make test
기대 결과
release: 9 checks, 0 failures; virtual24h samples=864000 errors=0 max_latency_ms=2 dropped=0 queue_high=1; 동일 seed·순환·증거 대조·UBSan 통과, 전체 종료0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 센서 기록기의 상태 머신을 설명해 주시면 됩니다.