Windows 드라이버나 UEFI 애플리케이션을 테스트하다 보면 반복적으로 마주치는 일이 있습니다. VM을 켜고, 부팅을 기다리고, 파일을 복사하고, 명령을 실행하고, 결과물을 다시 가져오고, 필요하면 스크린샷이나 메모리 덤프까지 남겨야 합니다. 실패가 발생하면 다시 깨끗한 상태로 되돌려 재현해야 하고, 때로는 부팅 전에 EFI 파티션을 직접 패치해야 할 수도 있습니다.
이런 작업은 한두 번이면 수동으로 처리할 수 있지만, 테스트 케이스가 늘어나고 재현성이 중요해지는 순간부터는 자동화가 필요해집니다.
처음엔 Vagrant을 써보려고 하다가 한계에 다달아서 만들기 시작한 도구가 test-foundry입니다.
test-foundry는 QEMU 기반의 Windows 게스트 자동화 테스트 도구입니다. VM setup, snapshot 생성, 테스트 실행, 파일 업로드/다운로드, 스크린샷 캡처, 그리고 wait-panic, wait-reset 같은 QMP 이벤트 기반 대기를 하나의 테스트 흐름 안에서 처리할 수 있도록 설계했습니다.
왜 만들었나
처음 목표는 단순했습니다. Windows 게스트에서 반복적으로 실행해야 하는 테스트를 사람이 매번 손으로 하지 않게 만드는 것이었습니다.
예를 들어 다음과 같은 흐름입니다.
- VMWare 로 Windows 을 부팅한다.
- 테스트용 파일(드라이버)을 게스트로 업로드한다.
- 게스트 안에서 명령을 실행한다.
- 결과 파일을 호스트로 다운로드한다.
- 앗! 버그발생! WinDbg 을 연결해 디버깅한다.
- VM을 종료하고, 스냅샷을 돌린다.
test-foundry 은 이러한 과정을 자동화 합니다. 먼저 베이스 이미지를 준비하고, vm-setup으로 VM context와 스냅샷을 만든 다음, test 명령으로 스냅샷을 복원해 테스트 단계를 실행합니다.
# VM Setup (OOBE 이미지 셋업을 기다리고, 공통 설정 (bcdedit 등))
$ test-foundry --vm-name="win11" vm-setup --image ./examples/images/windows-11.yaml
# 테스트 실행 (항상 VM Setup 이 끝난 시점의 스냅샷 사용)
$ test-foundry --vm-name="win11" test --output ./temp --test ./examples/tests/01-hello-world/test.yaml
GitHub Actions처럼 테스트를 YAML로 정의하기
test-foundry에서 테스트는 YAML 파일로 정의합니다. GitHub Actions의 workflow처럼 step 기반으로 작성할 수 있도록 했습니다. 아래 예제에서는 wait-boot, file-upload, exec, file-download, screenshot, shutdown 같은 액션을 순서대로 실행합니다.
예를 들어 hello-world 테스트는 게스트가 부팅될 때까지 기다린 뒤, bat 파일을 업로드하고, 실행 결과를 파일로 저장한 다음, 그 파일을 다시 다운로드하고 스크린샷을 캡처한 뒤 종료합니다.
name: "hello-world"
description: "Upload and run a hello-world bat file, capture stdout and screenshot"
steps:
- action: wait-boot
timeout: 120s
params:
retry_interval: 5s
- action: file-upload
timeout: 30s
params:
src: "${{ test.dir }}/hello.bat"
dst: "C:\\Temp\\hello.bat"
- action: exec
timeout: 30s
params:
cmd: "cmd.exe"
args: ["/c", "C:\\Temp\\hello.bat > C:\\Temp\\hello-output.txt 2>&1"]
expect_exit_code: 0
- action: file-download
timeout: 30s
params:
src: "C:\\Temp\\hello-output.txt"
dst: "./output/01/hello-output.txt"
- action: screenshot
timeout: 10s
params:
output: "./output/01/screenshot.png"
- action: shutdown
timeout: 120s
이 방식의 장점은 테스트 절차가 코드처럼 남는다는 점입니다. “VM을 켠 뒤 어떤 파일을 넣고 어떤 명령을 실행했는지”가 YAML 안에 그대로 남기 때문에, 나중에 같은 테스트를 다시 실행하거나 CI에 연결하기 쉬워집니다.
이미지 정의와 테스트 정의를 분리하기
test-foundry는 VM 이미지 설정과 테스트 시나리오를 분리합니다.
이미지 정의 YAML은 재사용 가능한 VM 베이스 이미지와 접속 방법을 설명합니다. vm-setup 단계에서는 이 정의를 사용해 VM을 부팅하고 setup step을 실행한 뒤, 이후 테스트 실행에 사용할 스냅샷을 만듭니다.
이미지 정의에는 qcow2 이미지 경로, OVMF/UEFI firmware, 메모리, CPU, QEMU 추가 인자, SSH/WinRM 접속 정보 등이 들어갑니다. exec_method와 file_method는 각각 ssh 또는 winrm을 사용할 수 있고, 실행과 파일 전송 방식을 분리할 수도 있습니다.
name: "windows-11-24h2"
os: "windows"
qemu:
image: "./images/windows-11.qcow2"
firmware: "/usr/share/OVMF/OVMF_CODE_4M.fd"
firmware_vars: "/usr/share/OVMF/OVMF_VARS_4M.fd"
memory: "4G"
cpus: 2
cpu: host
connection:
exec_method: "winrm"
file_method: "ssh"
username: "vagrant"
password: "vagrant"
ssh_port: 22
winrm_port: 5985
setup:
steps:
- action: wait-boot
timeout: 10m
params:
retry_interval: 5s
- action: shutdown
timeout: 120s
반면 테스트 정의 YAML은 이미 만들어진 VM 스냅샷 위에서 어떤 작업을 실행할지 설명합니다. 테스트 정의는 steps 외에도 preboot, panic 같은 섹션을 가질 수 있습니다.
이 분리는 꽤 중요합니다. 베이스 이미지 준비는 느리고 무거운 작업일 수 있지만, 개별 테스트는 스냅샷을 복원해서 빠르게 반복할 수 있어야 하기 때문입니다.
부팅 전 디스크 패치: preboot.steps
Windows 드라이버나 UEFI 쪽 테스트를 하다 보면, 게스트 OS가 부팅되기 전에 디스크를 수정해야 하는 경우가 있습니다. 예를 들어 EFI System Partition 안에 특정 EFI 바이너리를 넣거나, 부팅 경로를 바꾸고 싶은 경우입니다.
이를 위해 test-foundry는 preboot.steps를 지원합니다. preboot.steps는 QEMU를 부팅하기 전에 qcow2 디스크를 오프라인으로 수정하는 단계입니다. 현재 efi-add-file, efi-get-file 액션을 제공합니다.
preboot:
steps:
- action: efi-add-file
params:
src: "${{ test.dir }}/shellx64.efi"
dst: "/EFI/Boot/bootx64.efi"
예제 03-patch-efi는 shellx64.efi를 EFI 부트 경로에 넣고, VM을 잠깐 실행한 뒤 스크린샷을 찍고 poweroff로 종료합니다.
이 기능은 특히 UEFI 애플리케이션이나 부트 단계에서 동작하는 구성요소를 테스트할 때 유용합니다. OS가 완전히 올라온 뒤 실행하는 테스트와 달리, 부팅 경로 자체를 테스트 흐름에 포함할 수 있기 때문입니다.
Panic 감지 및 덤프
Windows 커널/드라이버 테스트에서 중요한 것은 실패 자체보다 실패를 재현하고 분석할 수 있는 정보를 남기는 일입니다.
test-foundry의 테스트 정의는 panic.steps를 지원합니다. 게스트가 pvpanic 이벤트를 발생시키고 테스트 러너가 crash를 감지하면, panic 단계에서 스크린샷을 찍거나 QMP를 통해 메모리 덤프를 남길 수 있습니다.
panic:
steps:
- action: screenshot
timeout: 10s
params:
output: "./output/bsod-screenshot.png"
- action: dump
timeout: 300s
params:
format: "win-dmp"
output: "./output/memory.dump"
테스트가 실패했을 때 “실패했다”는 로그만 남는 것이 아니라, 화면 상태와 덤프 같은 분석 가능한 산출물을 자동으로 남기는 것이 목표입니다.
참고로 qemu 의 win-dmp 는 모든 메모리 영역을 덤프하기 때문에 일반적인 minidump 보다 훨신 큽니다. 아쉽...
yaml 에서 Expression 지원
테스트 YAML 안에서는 ${{ ... }} 형태의 expression을 사용할 수 있습니다. 예를 들어 현재 테스트 YAML이 위치한 디렉터리인 ${{ test.dir }}, 환경 변수인 ${{ env.NAME }}, 런타임 VM 설정 값인 ${{ vmconfig.<key> }}를 참조할 수 있습니다.
params:
src: "${{ test.dir }}/hello.bat"
name: "${{ vmconfig.machine_name }}"
ssh_port: "${{ vmconfig.ssh_host_port }}"
이 기능은 테스트 파일과 테스트 리소스를 같은 디렉터리에 두고 함께 이동하거나, VM마다 달라지는 포트·소켓·머신 이름 같은 값을 하드코딩하지 않기 위해 넣었습니다.
현재 상태와 앞으로
아직 linux 은 제대로 지원하지 않습니다. 이후 linux guest 지원도 예정입니다.
host 는 Linux & Windows 모두 가능합니다.
마무리
test-foundry는 “VM을 켜고 명령을 실행하는 도구”라기보다, VM 기반 테스트를 반복 가능하고 추적 가능한 형태로 정리하기 위한 도구입니다.
특히 다음과 같은 상황에서 유용합니다.
- Windows 드라이버 테스트를 반복해야 하는 경우
- UEFI 애플리케이션이나 EFI 부트 경로를 자동으로 검증하고 싶은 경우
- QEMU snapshot 기반으로 깨끗한 테스트 환경을 계속 재사용하고 싶은 경우
- 실패 시 스크린샷, 메모리 덤프, 출력 파일 같은 분석 산출물을 자동으로 남기고 싶은 경우
- 테스트 절차를 YAML로 남겨 CI나 다른 자동화 흐름에 연결하고 싶은 경우
아직 초기 버전이지만, 제가 직접 필요해서 만든 만큼 실제 테스트 흐름에서 마주치는 불편함을 하나씩 줄여가는 방향으로 발전시키고 있습니다.
GitHub - jc-lab/test-foundry
Contribute to jc-lab/test-foundry development by creating an account on GitHub.
github.com
'내가만드는것_만든것 > 프로그램-공개' 카테고리의 다른 글
| Online Utils (0) | 2025.10.22 |
|---|---|
| Har to markdown (0) | 2025.09.22 |
| G2B 입찰 공고 검색/알림 서비스 (0) | 2025.01.28 |
| ping-watcher (0) | 2024.09.15 |
| local-tls-proxy: 자동으로 모든 포트를 HTTPS으로 (0) | 2024.09.05 |
댓글