#!/usr/bin/expect -f
#
# AdrOS Automated Smoke Test via QEMU serial console
#
# Usage: expect tests/smoke_test.exp [smp_count] [timeout_sec]
# smp_count : number of CPUs (default: 4)
# timeout_sec: max seconds to wait for full boot (default: 30)
#
# Exit codes:
# 0 = all checks passed
# 1 = test failure (missing expected output or PANIC detected)
# 2 = timeout (boot did not complete in time)
set smp [lindex $argv 0]
if {$smp eq ""} { set smp 4 }
set timeout_sec [lindex $argv 1]
if {$timeout_sec eq ""} { set timeout_sec 60 }
set iso "adros-x86.iso"
set disk "disk.img"
set serial_log "serial.log"
# Ensure disk image exists
if {![file exists $disk]} {
exec dd if=/dev/zero of=$disk bs=1M count=4 2>/dev/null
}
# Remove old serial log
file delete -force $serial_log
# Start QEMU in background, serial to file
set qemu_pid [exec qemu-system-i386 \
-smp $smp -boot d -cdrom $iso -m 128M -display none \
-drive file=$disk,if=ide,format=raw \
-nic user,model=e1000 \
-serial file:$serial_log -monitor none \
-no-reboot -no-shutdown &]
# Wait for QEMU to start writing
after 1000
# ---- Test definitions ----
# Each test is {description pattern}
set tests {
{"Heap init" "\\[HEAP\\] 8MB Buddy Allocator Ready."}
{"PCI enumeration" "\\[PCI\\] Enumerated"}
{"ATA DMA init" "\\[ATA-DMA\\] Ch0 initialized"}
{"ATA DMA mode" "\\[ATA\\] Channel 0: DMA mode."}
{"SMP CPUs active" "CPU\\(s\\) active."}
{"User ring3 entry" "\\[USER\\] enter ring3"}
{"init.elf hello" "\\[test\\] hello from init.elf"}
{"open/read/close" "\\[test\\] open/read/close OK"}
{"overlay copy-up" "\\[test\\] overlay copy-up OK"}
{"lseek/stat/fstat" "\\[test\\] lseek/stat/fstat OK"}
{"dup2 restore" "\\[test\\] dup2 restore tty OK"}
{"kill SIGKILL" "\\[test\\] kill\\(SIGKILL\\) OK"}
{"poll pipe" "\\[test\\] poll\\(pipe\\) OK"}
{"select pipe" "\\[test\\] select\\(pipe\\) OK"}
{"ioctl tty" "\\[test\\] ioctl\\(/dev/tty\\) OK"}
{"job control" "\\[test\\] job control \\(SIGTTIN/SIGTTOU\\) OK"}
{"poll /dev/null" "\\[test\\] poll\\(/dev/null\\) OK"}
{"pty bidirectional" "\\[test\\] pty OK"}
{"setsid/setpgid" "\\[test\\] setsid/setpgid/getpgrp OK"}
{"sigaction SIGUSR1" "\\[test\\] sigaction/kill\\(SIGUSR1\\) OK"}
{"sigreturn" "\\[test\\] sigreturn OK"}
{"tmpfs/mount" "\\[test\\] tmpfs/mount OK"}
{"dev null" "\\[test\\] /dev/null OK"}
{"persist counter" "\\[test\\] /persist/counter="}
{"dev tty write" "\\[test\\] /dev/tty write OK"}
{"diskfs test" "\\[test\\] /disk/test prev="}
{"diskfs mkdir/unlink" "\\[test\\] diskfs mkdir/unlink OK"}
{"diskfs getdents" "\\[test\\] diskfs getdents OK"}
{"isatty" "\\[test\\] isatty OK"}
{"O_NONBLOCK" "\\[test\\] O_NONBLOCK OK"}
{"pipe2/dup3" "\\[test\\] pipe2/dup3 OK"}
{"chdir/getcwd" "\\[test\\] chdir/getcwd OK"}
{"*at syscalls" "\\[test\\] \\*at OK"}
{"rename/rmdir" "\\[test\\] rename/rmdir OK"}
{"getdents multi-fs" "\\[test\\] getdents multi-fs OK"}
{"brk heap" "\\[test\\] brk OK"}
{"mmap/munmap" "\\[test\\] mmap/munmap OK"}
{"clock_gettime" "\\[test\\] clock_gettime OK"}
{"dev zero" "\\[test\\] /dev/zero OK"}
{"dev random" "\\[test\\] /dev/random OK"}
{"procfs" "\\[test\\] procfs OK"}
{"pread/pwrite" "\\[test\\] pread/pwrite OK"}
{"ftruncate" "\\[test\\] ftruncate OK"}
{"symlink/readlink" "\\[test\\] symlink/readlink OK"}
{"access" "\\[test\\] access OK"}
{"sigprocmask" "\\[test\\] sigprocmask/sigpending OK"}
{"alarm SIGALRM" "\\[test\\] alarm/SIGALRM OK"}
{"shmget/shmat" "\\[test\\] shmget/shmat/shmdt OK"}
{"O_APPEND" "\\[test\\] O_APPEND OK"}
{"umask" "\\[test\\] umask OK"}
{"pipe capacity" "\\[test\\] pipe capacity OK"}
{"waitid" "\\[test\\] waitid OK"}
{"setitimer/getitimer" "\\[test\\] setitimer/getitimer OK"}
{"select regfile" "\\[test\\] select regfile OK"}
{"poll regfile" "\\[test\\] poll regfile OK"}
{"hard link" "\\[test\\] hard link OK"}
{"epoll" "\\[test\\] epoll OK"}
{"epollet" "\\[test\\] epollet OK"}
{"inotify" "\\[test\\] inotify OK"}
{"aio" "\\[test\\] aio OK"}
{"nanosleep" "\\[test\\] nanosleep OK"}
{"CLOCK_REALTIME" "\\[test\\] CLOCK_REALTIME OK"}
{"dev urandom" "\\[test\\] /dev/urandom OK"}
{"proc cmdline" "\\[test\\] /proc/cmdline OK"}
{"CoW fork" "\\[test\\] CoW fork OK"}
{"readv/writev" "\\[test\\] readv/writev OK"}
{"fsync" "\\[test\\] fsync OK"}
{"truncate path" "\\[test\\] truncate OK"}
{"getuid/getgid" "\\[test\\] getuid/getgid OK"}
{"chmod" "\\[test\\] chmod OK"}
{"flock" "\\[test\\] flock OK"}
{"times" "\\[test\\] times OK"}
{"gettid" "\\[test\\] gettid OK"}
{"posix_spawn" "\\[test\\] posix_spawn OK"}
{"clock_ns precision" "\\[test\\] clock_ns precision OK"}
{"getppid" "\\[test\\] getppid OK"}
{"waitpid WNOHANG" "\\[test\\] waitpid WNOHANG OK"}
{"SIGSEGV handler" "\\[test\\] SIGSEGV OK"}
{"waitpid 100 children" "\\[test\\] waitpid OK \\(100 children"}
{"lazy PLT" "\\[test\\] lazy PLT OK"}
{"PLT cached" "\\[test\\] PLT cached OK"}
{"PING network" "\\[PING\\] .*received.*network OK"}
{"echo execve" "\\[echo\\] hello from echo"}
{"setuid/setgid" "\\[test\\] setuid/setgid OK"}
{"fcntl F_GETFL/SETFL" "\\[test\\] fcntl F_GETFL/F_SETFL OK"}
{"fcntl FD_CLOEXEC" "\\[test\\] fcntl FD_CLOEXEC OK"}
{"sigsuspend" "\\[test\\] sigsuspend OK"}
{"orphan reparent" "\\[test\\] orphan reparent OK"}
{"proc PID cmdline" "\\[test\\] /proc/PID/cmdline OK"}
{"proc PID status" "\\[test\\] /proc/PID/status OK"}
{"dev console" "\\[test\\] /dev/console OK"}
{"multi-pty" "\\[test\\] multi-pty OK"}
{"dup standalone" "\\[test\\] dup OK"}
{"pipe EOF" "\\[test\\] pipe EOF OK"}
{"readdir /proc" "\\[test\\] readdir /proc OK"}
{"readdir /bin" "\\[test\\] readdir /bin OK"}
{"gettimeofday" "\\[test\\] gettimeofday OK"}
{"mprotect" "\\[test\\] mprotect OK"}
{"madvise" "\\[test\\] madvise OK"}
{"getrlimit/setrlimit" "\\[test\\] getrlimit/setrlimit OK"}
{"uname" "\\[test\\] uname OK"}
{"SMP parallel fork" "\\[test\\] SMP parallel fork OK"}
{"LZ4 Frame decomp" "\\[INITRD\\] LZ4"}
}
# ---- Poll serial.log for results ----
set start_time [clock seconds]
set passed 0
set failed 0
set panic 0
set total [llength $tests]
# Track which tests have passed
for {set i 0} {$i < $total} {incr i} {
set test_passed($i) 0
}
proc check_log {} {
global serial_log
if {[file exists $serial_log]} {
set fd [open $serial_log r]
set content [read $fd]
close $fd
return $content
}
return ""
}
# Poll loop
while {1} {
set elapsed [expr {[clock seconds] - $start_time}]
if {$elapsed > $timeout_sec} {
break
}
set log [check_log]
# Check for PANIC
if {[regexp {KERNEL PANIC} $log]} {
set panic 1
break
}
# Check for HEAP OOM
if {[regexp {\[HEAP\] OOM} $log]} {
set panic 1
break
}
# Check each test
set all_done 1
for {set i 0} {$i < $total} {incr i} {
if {$test_passed($i)} continue
set pattern [lindex [lindex $tests $i] 1]
if {[regexp $pattern $log]} {
set test_passed($i) 1
} else {
set all_done 0
}
}
if {$all_done} {
break
}
after 1000
}
# Kill QEMU
catch {exec pkill -f "qemu-system-i386.*$iso" 2>/dev/null}
after 500
# ---- Report results ----
puts ""
puts "========================================="
puts " AdrOS Smoke Test Results (SMP=$smp)"
puts "========================================="
if {$panic} {
set log [check_log]
puts ""
puts " *** KERNEL PANIC DETECTED ***"
if {[regexp {Exception Number: (\d+)} $log _ exc]} {
puts " Exception: $exc"
}
if {[regexp {EIP: (0x[0-9A-Fa-f]+)} $log _ eip]} {
puts " EIP: $eip"
}
if {[regexp {PAGE FAULT at address: (0x[0-9A-Fa-f]+)} $log _ addr]} {
puts " Fault address: $addr"
}
puts ""
}
for {set i 0} {$i < $total} {incr i} {
set desc [lindex [lindex $tests $i] 0]
if {$test_passed($i)} {
puts " PASS $desc"
incr passed
} else {
puts " FAIL $desc"
incr failed
}
}
set elapsed [expr {[clock seconds] - $start_time}]
puts ""
puts " $passed/$total passed, $failed failed ($elapsed sec)"
if {$panic} {
puts " RESULT: FAIL (PANIC)"
exit 1
}
if {$failed > 0} {
set elapsed_val [expr {[clock seconds] - $start_time}]
if {$elapsed_val >= $timeout_sec} {
puts " RESULT: FAIL (TIMEOUT after ${timeout_sec}s)"
exit 2
}
puts " RESULT: FAIL"
exit 1
}
puts " RESULT: PASS"
exit 0