Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/starry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ cargo xtask starry qemu \

See `jcode/README.md` for interactive usage and troubleshooting.

## Nginx

The `nginx` case is a QEMU app integration workflow. It installs Alpine nginx
packages in a staging root during prebuild, injects runtime artifacts to the
app overlay, then runs nginx smoke tests inside StarryOS.

```bash
cargo xtask starry app run -t nginx --arch x86_64
```

`apps/starry/nginx` maintains four directories: `smoke`, `phase`, `stress`, and
`debug`. Currently only smoke is connected as nginx test entry in tgoskits workflows.

## Orange Pi 5 Plus UVC

Expand Down
38 changes: 38 additions & 0 deletions apps/starry/nginx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Starry Nginx App

This app organizes nginx tests into four directories:

- `smoke/`: CI entry smoke test (the only connected nginx test in tgoskits)
- `phase/`: stage-unit tests named by `x-x`, such as `nginx-1-3-lifecycle-tests.sh`
- `stress/`: pressure test planning and future scripts
- `debug/`: flexible issue-focused scripts

## CI-Connected Entry

Only smoke is connected:

```bash
cargo xtask starry app run -t nginx --arch riscv64
```

`qemu-*.toml` starts `/usr/bin/nginx-smoke-tests.sh` only.

## Local Development CLI

Unified local CLI script:

```bash
./apps/starry/nginx/nginx-cli-tests.sh smoke
./apps/starry/nginx/nginx-cli-tests.sh phase1
./apps/starry/nginx/nginx-cli-tests.sh phase2
./apps/starry/nginx/nginx-cli-tests.sh all
```

## Build/Prepare Logic

`prebuild.sh` injects only smoke entry and shared mirror helper into guest overlay:

- `/usr/bin/nginx-smoke-tests.sh`
- `/usr/bin/nginx-alpine-mirror.sh`

Mirror helper: `apps/starry/nginx/nginx-alpine-mirror.sh`.
14 changes: 14 additions & 0 deletions apps/starry/nginx/build-riscv64gc-unknown-none-elf.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
target = "riscv64gc-unknown-none-elf"
env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" }
log = "Warn"
features = [
"ax-hal/riscv64-qemu-virt",
"qemu",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,ax-driver/pci feature 不存在。请参照 test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml 的 feature 集修正。当前 dev 分支上 riscv64 的正确组合为:ax-feat/rtcax-driver/rtcax-driver/serialax-driver/virtio-*starry-kernel/inputplat_dyn = true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改

"ax-driver/pci",
"ax-driver/virtio-blk",
"ax-driver/virtio-net",
"ax-driver/virtio-gpu",
"ax-driver/virtio-input",
"ax-driver/virtio-socket",
]
plat_dyn = false
14 changes: 14 additions & 0 deletions apps/starry/nginx/build-x86_64-unknown-none.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
target = "x86_64-unknown-none"
env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" }
log = "Warn"
features = [
"ax-hal/x86-pc",
"qemu",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ax-driver crate 当前没有 pci feature(可通过 cargo metadata 确认)。此处应为 ax-driver/plat-static,参照 test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml

另外,riscv64 侧的 feature 集也需要参照 dev 分支上的 test-suit 共享 build config 重新对齐(注意 dev 分支上 riscv64 使用 ax-feat/rtc + ax-driver/rtc + ax-driver/serial + plat_dyn = true,而非 ax-hal/riscv64-qemu-virt + ax-driver/pci + plat_dyn = false)。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改

"ax-driver/pci",
"ax-driver/virtio-blk",
"ax-driver/virtio-net",
"ax-driver/virtio-gpu",
"ax-driver/virtio-input",
"ax-driver/virtio-socket",
]
plat_dyn = false
12 changes: 12 additions & 0 deletions apps/starry/nginx/debug/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Nginx Debug Tests

This directory stores flexible debug scripts for single issue reproduction and diagnosis.

Current scripts:

- `nginx-http-basic-tests.sh`: early HTTP basic script kept for issue-level debugging.

Rule:

- Debug scripts are free-form and can focus on one syscall or one behavior path.
- Debug scripts are not connected to tgoskits nginx CI entry.
183 changes: 183 additions & 0 deletions apps/starry/nginx/debug/nginx-http-basic-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/bin/sh

BASE=/tmp/nginx-http-basic
CONF="$BASE/conf/http-basic.conf"
WWW="$BASE/www"
LOGDIR="$BASE/logs"
OUT="$BASE/out"
NGINX_PID=
FAILURES=0
TIMEOUT_CMD=

. /usr/bin/nginx-alpine-mirror.sh

log() { printf 'NGINX_HTTP_BASIC_LOG: %s\n' "$*"; }
pass() { printf 'NGINX_HTTP_BASIC_STEP_PASS: %s\n' "$*"; }
fail() { printf 'NGINX_HTTP_BASIC_STEP_FAIL: %s\n' "$*"; FAILURES=$((FAILURES + 1)); }

run_step() {
step_name=$1
shift
log "BEGIN $step_name"
if "$@"; then
pass "$step_name"
return 0
fi
fail "$step_name"
return 1
}

init_timeout_cmd() {
if command -v timeout >/dev/null 2>&1; then
TIMEOUT_CMD='timeout'
return 0
fi
if busybox timeout 2>&1 | grep -qi 'usage'; then
TIMEOUT_CMD='busybox timeout'
return 0
fi
TIMEOUT_CMD=
return 0
}

run_with_timeout() {
timeout_sec=$1
shift
if [ -n "$TIMEOUT_CMD" ]; then
$TIMEOUT_CMD "$timeout_sec" "$@"
return $?
fi
"$@"
}

cleanup() {
if [ -n "$NGINX_PID" ] && kill -0 "$NGINX_PID" 2>/dev/null; then
kill -TERM "$NGINX_PID" 2>/dev/null || true
sleep 1
kill -KILL "$NGINX_PID" 2>/dev/null || true
fi
}

finish() {
status=$?
cleanup
if [ "$FAILURES" -eq 0 ] && [ "$status" -eq 0 ]; then
printf 'NGINX_HTTP_BASIC_PASSED\n'
exit 0
fi
printf 'NGINX_HTTP_BASIC_FAILED failures=%s status=%s\n' "$FAILURES" "$status"
exit 1
}

trap finish EXIT

prepare_packages() {
nginx_apk_add_with_fallback nginx curl busybox-extras || {
printf 'NGINX_HTTP_BASIC_PREPARE_FAILED: all mirrors failed\n'
return 1
}
}

prepare_tree() {
rm -rf "$BASE"
mkdir -p "$BASE/conf" "$WWW/dir" "$LOGDIR" "$OUT" "$BASE/client_temp"
printf 'HTTP_BASIC_INDEX_OK\n' > "$WWW/index.html"
printf 'small static file\n' > "$WWW/small.txt"
: > "$WWW/empty.txt"
printf 'HTTP_BASIC_DIR_INDEX_OK\n' > "$WWW/dir/index.html"

cat > "$CONF" <<'EOF_CONF'
daemon off;
master_process off;
worker_processes 1;
error_log /tmp/nginx-http-basic/logs/error.log debug;
pid /tmp/nginx-http-basic/nginx.pid;

events { worker_connections 64; }

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /tmp/nginx-http-basic/logs/access.log;
sendfile off;
keepalive_timeout 5;
client_body_temp_path /tmp/nginx-http-basic/client_temp;
server {
listen 127.0.0.1:8080;
server_name localhost;
root /tmp/nginx-http-basic/www;
location / { index index.html; }
}
}
EOF_CONF
}

test_config() { nginx -t -c "$CONF" -p "$BASE/"; }

start_nginx() {
nginx -c "$CONF" -p "$BASE/" > "$LOGDIR/nginx-stdout.log" 2>&1 &
NGINX_PID=$!
i=0
while [ "$i" -lt 90 ]; do
if ! kill -0 "$NGINX_PID" 2>/dev/null; then return 1; fi
if run_with_timeout 8 curl -fsS -o "$OUT/startup.body" http://127.0.0.1:8080/ >/dev/null 2>&1; then return 0; fi
sleep 1
i=$((i + 1))
done
return 1
}

test_get_small() {
code=$(run_with_timeout 12 curl -sS -D "$OUT/small.headers" -o "$OUT/small.body" -w '%{http_code}' http://127.0.0.1:8080/small.txt || printf 'curl_failed')
[ "$code" = "200" ] && grep -qx 'small static file' "$OUT/small.body" && grep -qi '^Content-Length: 18' "$OUT/small.headers"
}

test_get_empty() {
code=$(run_with_timeout 12 curl -sS -D "$OUT/empty.headers" -o "$OUT/empty.body" -w '%{http_code}' http://127.0.0.1:8080/empty.txt || printf 'curl_failed')
[ "$code" = "200" ] && [ "$(wc -c < "$OUT/empty.body")" -eq 0 ] && grep -qi '^Content-Length: 0' "$OUT/empty.headers"
}

test_get_dir_slash() {
code=$(run_with_timeout 12 curl -sS -D "$OUT/dir-slash.headers" -o "$OUT/dir-slash.body" -w '%{http_code}' http://127.0.0.1:8080/dir/ || printf 'curl_failed')
[ "$code" = "200" ] && grep -qx 'HTTP_BASIC_DIR_INDEX_OK' "$OUT/dir-slash.body"
}

test_get_dir_redirect() {
code=$(run_with_timeout 12 curl -sS -D "$OUT/dir.headers" -o "$OUT/dir.body" -w '%{http_code}' http://127.0.0.1:8080/dir || printf 'curl_failed')
[ "$code" = "301" ] || [ "$code" = "302" ]
}

test_bad_method() {
if command -v nc >/dev/null 2>&1; then
NC='nc'
elif busybox nc 2>&1 | grep -qi 'usage'; then
NC='busybox nc'
else
return 0
fi
{ printf 'BAD / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'; } | run_with_timeout 12 sh -c "$NC 127.0.0.1 8080" > "$OUT/bad-method.raw"
tr -d '\r' < "$OUT/bad-method.raw" > "$OUT/bad-method.normalized"
grep -Eq '^HTTP/1.1 (400|405)' "$OUT/bad-method.normalized"
}

stop_nginx() {
kill -TERM "$NGINX_PID"
i=0
while kill -0 "$NGINX_PID" 2>/dev/null && [ "$i" -lt 30 ]; do
sleep 1
i=$((i + 1))
done
! kill -0 "$NGINX_PID" 2>/dev/null
}

run_step "prepare packages" prepare_packages || exit 1
run_step "prepare nginx files" prepare_tree || exit 1
run_step "init timeout helper" init_timeout_cmd || exit 1
run_step "nginx config test" test_config || exit 1
run_step "start nginx" start_nginx || exit 1
run_step "GET /small.txt" test_get_small
run_step "GET /empty.txt" test_get_empty
run_step "GET /dir/" test_get_dir_slash
run_step "GET /dir redirect" test_get_dir_redirect
run_step "BAD method returns 400/405" test_bad_method
run_step "stop nginx" stop_nginx
53 changes: 53 additions & 0 deletions apps/starry/nginx/nginx-alpine-mirror.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/sh

set -eu

NGINX_APK_MIRROR_TIMEOUT_SEC="${NGINX_APK_MIRROR_TIMEOUT_SEC:-45}"
NGINX_APK_REPO_FILE="/tmp/nginx-apk-repositories"
NGINX_APK_ATTEMPT_LOG="/tmp/nginx-apk-attempt.log"

write_repo_file() {
mirror="$1"
cat >"$NGINX_APK_REPO_FILE" <<EOF
$mirror/main
$mirror/community
EOF
}

run_apk_add() {
mirror="$1"
shift
packages="$*"

echo "NGINX_APK_MIRROR_TRY: $mirror"
write_repo_file "$mirror"

if timeout "$NGINX_APK_MIRROR_TIMEOUT_SEC" apk --no-progress --update-cache --repositories-file "$NGINX_APK_REPO_FILE" add $packages >"$NGINX_APK_ATTEMPT_LOG" 2>&1; then
echo "NGINX_APK_MIRROR_OK: $mirror"
return 0
fi

rc=$?
if [ "$rc" -eq 124 ]; then
echo "NGINX_APK_MIRROR_FAIL: $mirror (timeout ${NGINX_APK_MIRROR_TIMEOUT_SEC}s)"
else
echo "NGINX_APK_MIRROR_FAIL: $mirror (apk rc=$rc)"
fi
sed -n '1,120p' "$NGINX_APK_ATTEMPT_LOG" || true
return 1
}

nginx_apk_add_with_fallback() {
mirror_cn="https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable"
mirror_global="https://dl-cdn.alpinelinux.org/alpine/latest-stable"

if run_apk_add "$mirror_cn" "$@"; then
return 0
fi
if run_apk_add "$mirror_global" "$@"; then
return 0
fi

echo "NGINX_APK_ALL_MIRRORS_FAILED"
return 1
}
26 changes: 26 additions & 0 deletions apps/starry/nginx/nginx-cli-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
set -eu

cmd=${1:-all}

case "$cmd" in
smoke)
sh ./smoke/nginx-smoke-tests.sh
;;
phase1)
sh ./phase/nginx-1-3-lifecycle-tests.sh
;;
phase2)
sh ./phase/nginx-2-0-http-basic-tests.sh
;;
all)
sh ./smoke/nginx-smoke-tests.sh
sh ./phase/nginx-1-3-lifecycle-tests.sh
sh ./phase/nginx-2-0-http-basic-tests.sh
printf 'NGINX_CLI_ALL_PASSED\n'
;;
*)
printf 'usage: %s [smoke|phase1|phase2|all]\n' "$0"
exit 2
;;
esac
13 changes: 13 additions & 0 deletions apps/starry/nginx/phase/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Nginx Phase Tests

Phase tests are organized by stage unit `x-x`.

Current scripts:

- `nginx-1-3-lifecycle-tests.sh`: covers phase 1.1, 1.2, and 1.3 lifecycle checks.
- `nginx-2-0-http-basic-tests.sh`: covers phase 2 HTTP basic semantics.

Rule:

- Each phase script must include all checks of that phase, including checks already covered by smoke.
- Phase scripts are managed for development iteration and are not connected as tgoskits nginx CI entry.
Loading