diff --git a/.github/workflows/github-ci-clang.yml b/.github/workflows/github-ci-clang.yml index c7778d22f5..8a79abd337 100644 --- a/.github/workflows/github-ci-clang.yml +++ b/.github/workflows/github-ci-clang.yml @@ -81,11 +81,16 @@ jobs: shell: bash run: | env -i DEBIAN_FRONTEND=noninteractive sudo apt-get update - env -i DEBIAN_FRONTEND=noninteractive sudo apt-get install -y qemu-system-arm + env -i DEBIAN_FRONTEND=noninteractive sudo apt-get install -y qemu-system-arm expect + - name: uefi unittest + if: ${{ matrix.project == 'qemu-virt-arm64-test' }} + shell: bash + run: | + ./scripts/uefi_unittest.exp - name: unittest if: ${{ matrix.project == 'qemu-virt-arm64-test' }} shell: bash run: | - python3 scripts/unittest.py + ./scripts/unittest.exp # vim: ts=2 sw=2 expandtab diff --git a/.github/workflows/github-ci-rust.yml b/.github/workflows/github-ci-rust.yml index 6c2b98cb94..0ae5952652 100644 --- a/.github/workflows/github-ci-rust.yml +++ b/.github/workflows/github-ci-rust.yml @@ -77,11 +77,16 @@ jobs: shell: bash run: | env -i DEBIAN_FRONTEND=noninteractive sudo apt-get update - env -i DEBIAN_FRONTEND=noninteractive sudo apt-get install -y qemu-system-arm + env -i DEBIAN_FRONTEND=noninteractive sudo apt-get install -y qemu-system-arm expect + - name: uefi unittest + if: ${{ matrix.project == 'qemu-virt-arm64-test' }} + shell: bash + run: | + ./scripts/uefi_unittest.exp - name: unittest if: ${{ matrix.project == 'qemu-virt-arm64-test' }} shell: bash run: | - python3 scripts/unittest.py + ./scripts/unittest.exp # vim: ts=2 sw=2 expandtab diff --git a/scripts/uefi_unittest.exp b/scripts/uefi_unittest.exp new file mode 100755 index 0000000000..52054dfd86 --- /dev/null +++ b/scripts/uefi_unittest.exp @@ -0,0 +1,78 @@ +#!/usr/bin/expect -f + +# Disable pty echo and newline translation to prevent double carriage returns +set stty_init "-onlcr -echo" + +proc kill_qemu {} { + global qemu_pid + catch {exec kill -TERM $qemu_pid} + after 1000 + catch {exec kill -KILL $qemu_pid} + catch {wait} +} + +proc shutdown_qemu {status} { + kill_qemu + exit $status +} + +# Set timeout for the first stage: waiting for the shell +set timeout 10 + +# Spawn the QEMU process +spawn qemu-system-aarch64 \ + -cpu max \ + -m 512 \ + -smp 1 \ + -machine virt,highmem=off \ + -kernel build-qemu-virt-arm64-test/lk.elf \ + -net none \ + -nographic \ + -drive if=none,file=lib/uefi/helloworld_aa64.efi,id=blk,format=raw \ + -device virtio-blk-device,drive=blk +set qemu_pid [exp_pid] + +# Wait for the app shell to start +expect { + "starting app shell" { + # Send the command to load the UEFI app + send "uefi_load virtio0\r" + } + timeout { + puts "Timeout waiting for 'starting app shell'" + kill_qemu + exit 1 + } + eof { + puts "QEMU exited unexpectedly before shell" + exit 1 + } +} + +# Set timeout for the second stage: waiting for Hello World! +set timeout 5 + +# Wait for the expected output from the UEFI app +expect { + "Hello World!" { + # Successfully saw the output, now shut down + shutdown_qemu 0 + } + "panic (caller" { + puts "LK panic while running the UEFI unittest" + shutdown_qemu 1 + } + "CRASH: starting debug shell" { + puts "LK crashed and entered the debug shell while running the UEFI unittest" + shutdown_qemu 1 + } + timeout { + puts "Timeout waiting for 'Hello World!'" + # Try to shut down anyway + shutdown_qemu 1 + } + eof { + puts "QEMU exited unexpectedly before Hello World!" + exit 1 + } +} diff --git a/scripts/unittest.exp b/scripts/unittest.exp new file mode 100755 index 0000000000..c30b62322d --- /dev/null +++ b/scripts/unittest.exp @@ -0,0 +1,80 @@ +#!/usr/bin/expect -f + +# Disable pty echo and newline translation to prevent double carriage returns +set stty_init "-onlcr -echo" + +proc kill_qemu {} { + global qemu_pid + catch {exec kill -TERM $qemu_pid} + after 1000 + catch {exec kill -KILL $qemu_pid} + catch {wait} +} + +proc shutdown_qemu {status} { + kill_qemu + exit $status +} + +# Set timeout for the first stage: waiting for the shell +set timeout 10 + +# Spawn the QEMU process +spawn qemu-system-aarch64 \ + -cpu max \ + -m 512 \ + -smp 1 \ + -machine virt,highmem=off \ + -kernel build-qemu-virt-arm64-test/lk.elf \ + -net none \ + -nographic +set qemu_pid [exp_pid] + +# Wait for the app shell to start +expect { + "starting app shell" { + # Send the command to run unittests + send "ut all\r" + } + timeout { + puts "Timeout waiting for 'starting app shell'" + kill_qemu + exit 1 + } + eof { + puts "QEMU exited unexpectedly before shell" + exit 1 + } +} + +# Set timeout for the second stage: waiting for tests to finish +set timeout 30 + +# Wait for the expected output from the unittests +expect { + "SUCCESS! All test cases passed" { + # Successfully saw the output, now shut down + shutdown_qemu 0 + } + "FAILURE! Some test cases failed" { + puts "Some test cases failed!" + shutdown_qemu 1 + } + "panic (caller" { + puts "LK panic while running unittests" + shutdown_qemu 1 + } + "CRASH: starting debug shell" { + puts "LK crashed and entered the debug shell while running unittests" + shutdown_qemu 1 + } + timeout { + puts "Timeout waiting for unittests to complete" + # Try to shut down anyway + shutdown_qemu 1 + } + eof { + puts "QEMU exited unexpectedly before unittests completed" + exit 1 + } +} diff --git a/scripts/unittest.py b/scripts/unittest.py deleted file mode 100644 index 3403cfe8a0..0000000000 --- a/scripts/unittest.py +++ /dev/null @@ -1,100 +0,0 @@ -import os -import sys -import signal -import subprocess -from subprocess import PIPE, STDOUT -import io -import time -import select - - -def wait_for_output(fp: io.TextIOBase, predicate, timeout: float): - start_time = time.time() - end_time = start_time + timeout - poll_obj = select.poll() - poll_obj.register(fp, select.POLLIN) - output = [] - cur_line = "" - while time.time() < end_time: - poll_result = poll_obj.poll(max(end_time-time.time(), 0.001)) - if poll_result: - data = fp.read() - if "\n" in data: - output.append(cur_line + data[:data.index("\n") + 1]) - cur_line = data[data.index("\n") + 1:] - if predicate(output[-1]): - return True, output - else: - cur_line += data - - return False, output - - -def shutdown_little_kernel(p: subprocess.Popen): - try: - ret = p.poll() - if ret: - print("LittleKernel already exited with code", ret) - return - status_path = "/proc/{}/status".format(p.pid) - if os.path.exists(status_path): - with open(status_path) as fp: - lines = fp.readlines() - state_line = [l for l in lines if "State:" in l] - if state_line: - print("LittleKernel process state after test:",state_line[0].rstrip()) - else: - print(status_path, "does not exists") - p.stdin.write("poweroff\n") - p.stdin.flush() - p.wait(0.3) - p.send_signal(signal.SIGINT) - p.wait(1) - except subprocess.TimeoutExpired: - pass - finally: - p.kill() - p.wait() - - -def main(): - # Test relies on reading subprocess output, so set bufsize=0 - # to ensure that we get real-time output. - p = subprocess.Popen(['qemu-system-aarch64', - '-cpu', - 'max', - '-m', - '512', - '-smp', - '1', - '-machine', - 'virt,highmem=off', - '-kernel', - 'build-qemu-virt-arm64-test/lk.elf', - '-net', - 'none', - '-nographic', - '-drive', - 'if=none,file=lib/uefi/helloworld_aa64.efi,id=blk,format=raw', - '-device', - 'virtio-blk-device,drive=blk'], stdout=PIPE, stdin=PIPE, stderr=STDOUT, text=True, bufsize=0) - try: - os.set_blocking(p.stdout.fileno(), False) - condition_met, output = wait_for_output( - p.stdout, lambda l: "starting app shell" in l, 5) - assert condition_met, "Did not see 'starting app shell', stdout: {}".format( - "".join(output)) - p.stdin.write("uefi_load virtio0\n") - p.stdin.flush() - condition_met, output = wait_for_output( - p.stdout, lambda l: "Hello World!" in l, 0.5) - print("".join(output)) - if condition_met: - return - - finally: - shutdown_little_kernel(p) - - -if __name__ == "__main__": - main()