77 push :
88 branches : [main]
99 paths :
10- - ' examples/**'
11- - ' runtimes/**'
12- - ' host/**'
13- - ' .github/workflows/test-examples.yml'
10+ - " examples/**"
11+ - " runtimes/**"
12+ - " host/**"
13+ - " .github/workflows/test-examples.yml"
1414
1515# Pushing a new commit to the same ref cancels any in-flight run on
1616# the previous commit — don't waste CI on stale code.
@@ -61,12 +61,14 @@ jobs:
6161 - python-agent-driver
6262 - powershell
6363 - networking-py
64+ - go-http
65+ - dotnet-http
6466 steps :
6567 - uses : actions/checkout@v4
6668
6769 - uses : actions/setup-go@v5
6870 with :
69- go-version : ' 1.25.1'
71+ go-version : " 1.25.1"
7072 cache : false
7173
7274 - name : Install just
9092 - name : Build local-python-base images (python-agent-driver only)
9193 if : matrix.example == 'python-agent-driver'
9294 env :
93- DOCKER_BUILDKIT : ' 0 '
95+ DOCKER_BUILDKIT : " 0 "
9496 run : |
9597 docker build --target base -t local-python-base-dev:latest \
9698 -f runtimes/python.Dockerfile runtimes/
@@ -100,7 +102,7 @@ jobs:
100102 - name : Build rootfs
101103 working-directory : examples/${{ matrix.example }}
102104 env :
103- DOCKER_BUILDKIT : ' 0 '
105+ DOCKER_BUILDKIT : " 0 "
104106 run : |
105107 just rootfs
106108
@@ -224,6 +226,14 @@ jobs:
224226 - example : networking-py
225227 args : " --port 8080 -- /echo_server_test.py"
226228 expect : " SUCCESS: bind\\ +listen on port 8080 allowed"
229+ - example : go-http
230+ args : " --port 8080 -- /bin/server"
231+ expect : " Hello from Hyperlight-Unikraft!"
232+ http_port : " 8080"
233+ - example : dotnet-http
234+ args : " --port 8080 -- /app/KestrelHyperlight"
235+ expect : " Hello from Kestrel on Hyperlight!"
236+ http_port : " 8080"
227237 steps :
228238 - uses : actions/checkout@v4
229239
@@ -283,7 +293,7 @@ jobs:
283293 - name : Build local-python-base images (python-agent-driver only)
284294 if : steps.kvm_check.outputs.available == 'true' && matrix.example == 'python-agent-driver'
285295 env :
286- DOCKER_BUILDKIT : ' 0 '
296+ DOCKER_BUILDKIT : " 0 "
287297 run : |
288298 docker build --target base -t local-python-base-dev:latest \
289299 -f runtimes/python.Dockerfile runtimes/
@@ -294,7 +304,7 @@ jobs:
294304 if : steps.kvm_check.outputs.available == 'true'
295305 working-directory : examples/${{ matrix.example }}
296306 env :
297- DOCKER_BUILDKIT : ' 0 '
307+ DOCKER_BUILDKIT : " 0 "
298308 run : |
299309 just rootfs
300310 kraft-hyperlight --no-prompt build --plat hyperlight --arch x86_64 || true
@@ -345,10 +355,48 @@ jobs:
345355 mkdir -p "$mount_dir"
346356 mount_args="--mount $mount_dir:/host"
347357 fi
358+
348359 tool_args=""
349360 if [ "${{ matrix.needs_echo_tool }}" = "true" ]; then
350361 tool_args="--tool echo=../echo-wasm-host-fxn/target/wasm32-wasip1/release/echo-wasm-host-fxn.wasm"
351362 fi
363+
364+ # HTTP server examples: start in background, poll, curl, kill.
365+ http_port="${{ matrix.http_port }}"
366+ if [ -n "$http_port" ]; then
367+ mem_args=""
368+ if [ -n "$memory" ]; then
369+ mem_args="-m $memory"
370+ fi
371+ hyperlight-unikraft -q $mem_args "$kernel" --initrd "$cpio" $mount_args $tool_args ${{ matrix.args }} &
372+ server_pid=$!
373+ sleep 3
374+ ready=0
375+ for i in $(seq 1 30); do
376+ if curl -s --max-time 10 "http://127.0.0.1:${http_port}" >/dev/null 2>&1; then
377+ ready=1
378+ break
379+ fi
380+ sleep 1
381+ done
382+ if [ "$ready" -ne 1 ]; then
383+ echo "FAIL: server did not become ready within timeout"
384+ kill "$server_pid" 2>/dev/null || true
385+ exit 1
386+ fi
387+ response=$(curl -s --max-time 10 "http://127.0.0.1:${http_port}")
388+ kill "$server_pid" 2>/dev/null || true
389+ wait "$server_pid" 2>/dev/null || true
390+ echo "=== HTTP response ==="
391+ echo "$response"
392+ if echo "$response" | grep -qF "$expect"; then
393+ echo "PASS: matched /$expect/"
394+ else
395+ echo "FAIL: did not match /$expect/"
396+ exit 1
397+ fi
398+ exit 0
399+ fi
352400 case "${{ matrix.driver }}" in
353401 multifn-test)
354402 cmd=(timeout 60 /home/runner/work/hyperlight-unikraft/hyperlight-unikraft/host/target/release/multifn-test "$kernel" "$cpio")
@@ -414,12 +462,14 @@ jobs:
414462 - python-agent-driver
415463 - powershell
416464 - networking-py
465+ - go-http
466+ - dotnet-http
417467 steps :
418468 - uses : actions/checkout@v4
419469
420470 - uses : actions/setup-go@v5
421471 with :
422- go-version : ' 1.25.1'
472+ go-version : " 1.25.1"
423473 cache : false
424474
425475 - name : Install just
@@ -443,7 +493,7 @@ jobs:
443493 - name : Build local-python-base images (python-agent-driver only)
444494 if : matrix.example == 'python-agent-driver'
445495 env :
446- DOCKER_BUILDKIT : ' 0 '
496+ DOCKER_BUILDKIT : " 0 "
447497 run : |
448498 docker build --target base -t local-python-base-dev:latest \
449499 -f runtimes/python.Dockerfile runtimes/
@@ -453,7 +503,7 @@ jobs:
453503 - name : Build rootfs + kernel
454504 working-directory : examples/${{ matrix.example }}
455505 env :
456- DOCKER_BUILDKIT : ' 0 '
506+ DOCKER_BUILDKIT : " 0 "
457507 run : |
458508 just rootfs
459509 kraft-hyperlight --no-prompt build --plat hyperlight --arch x86_64 || true
@@ -566,6 +616,14 @@ jobs:
566616 - example : networking-py
567617 args : " --port 8080 -- /echo_server_test.py"
568618 expect : " SUCCESS: bind\\ +listen on port 8080 allowed"
619+ - example : go-http
620+ args : " --port 8080 -- /bin/server"
621+ expect : " Hello from Hyperlight-Unikraft!"
622+ http_port : " 8080"
623+ - example : dotnet-http
624+ args : " --port 8080 -- /app/KestrelHyperlight"
625+ expect : " Hello from Kestrel on Hyperlight!"
626+ http_port : " 8080"
569627 steps :
570628 - uses : actions/checkout@v4
571629
@@ -647,6 +705,54 @@ jobs:
647705 $PSNativeCommandUseErrorActionPreference = $false
648706 $ErrorActionPreference = 'Continue'
649707
708+ # HTTP server examples: start in background, poll, curl, kill.
709+ $httpPort = '${{ matrix.http_port }}'
710+ if ($httpPort -ne '') {
711+ $argList = @()
712+ if ($runArgs -ne '') {
713+ $argList = $runArgs.Split(' ') | Where-Object { $_ -ne '' }
714+ }
715+ $memArgs = @()
716+ if ($memory -ne '') {
717+ $memArgs = @('-m', $memory)
718+ }
719+ $stderrLog = Join-Path $env:RUNNER_TEMP 'hl-stderr.log'
720+ $proc = Start-Process -FilePath 'hyperlight-unikraft' `
721+ -ArgumentList (@('-q') + $memArgs + @($kernel, '--initrd', $cpio) + $argList) `
722+ -PassThru -NoNewWindow -RedirectStandardError $stderrLog
723+ # Give the server time to boot the unikernel and start listening
724+ Start-Sleep -Seconds 5
725+ $ready = $false
726+ for ($i = 0; $i -lt 30; $i++) {
727+ try {
728+ $null = Invoke-WebRequest -Uri "http://127.0.0.1:${httpPort}" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
729+ $ready = $true
730+ break
731+ } catch {
732+ Start-Sleep -Seconds 1
733+ }
734+ }
735+ if (-not $ready) {
736+ Write-Host "FAIL: server did not become ready within timeout"
737+ Write-Host "=== stderr (last 200 lines) ==="
738+ Get-Content $stderrLog -Tail 200 -ErrorAction SilentlyContinue
739+ Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
740+ exit 1
741+ }
742+ $raw = (Invoke-WebRequest -Uri "http://127.0.0.1:${httpPort}" -UseBasicParsing -TimeoutSec 10).Content
743+ if ($raw -is [byte[]]) { $resp = [System.Text.Encoding]::UTF8.GetString($raw) } else { $resp = $raw }
744+ Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
745+ Write-Host "=== HTTP response ==="
746+ Write-Host $resp
747+ if ($resp -match [regex]::Escape($expect)) {
748+ Write-Host "PASS: matched /$expect/"
749+ exit 0
750+ } else {
751+ Write-Host "FAIL: did not match /$expect/"
752+ exit 1
753+ }
754+ }
755+
650756 switch ($driver) {
651757 'multifn-test' {
652758 $out = & multifn-test $kernel $cpio 2>&1
@@ -842,7 +948,14 @@ jobs:
842948
843949 test-examples-passed :
844950 if : always()
845- needs : [build-example, runtime-test, package-images-for-windows, runtime-test-windows, pyhl-snapshot-test]
951+ needs :
952+ [
953+ build-example,
954+ runtime-test,
955+ package-images-for-windows,
956+ runtime-test-windows,
957+ pyhl-snapshot-test,
958+ ]
846959 runs-on : ubuntu-latest
847960 permissions : {}
848961 steps :
0 commit comments