From 9776e29d7d133fa8255c3a8e5a47de576e5c12b7 Mon Sep 17 00:00:00 2001 From: HiepLM Date: Sat, 13 Jun 2026 14:11:42 +0700 Subject: [PATCH] final TESTING --- .github/workflows/test.yml | 4 +- README.md | 25 +++-- scripts/README.md | 43 ++++++++ scripts/bench/http.sh | 17 +++ scripts/benchmark-http.sh | 93 ----------------- scripts/docker-benchmark.sh | 125 ----------------------- scripts/docker-htop.sh | 21 ---- scripts/docker-stats.sh | 44 -------- scripts/docker-test.sh | 41 -------- scripts/docker-up.sh | 20 ---- scripts/docker/bench.sh | 31 ++++++ scripts/docker/down.sh | 11 ++ scripts/docker/htop.sh | 14 +++ scripts/docker/shell.sh | 10 ++ scripts/docker/stats.sh | 29 ++++++ scripts/docker/test.sh | 26 +++++ scripts/docker/up.sh | 19 ++++ scripts/lib/bench.sh | 71 +++++++++++++ scripts/lib/common.sh | 33 ++++++ scripts/{docker-lib.sh => lib/docker.sh} | 15 ++- scripts/lm.sh | 74 ++++++++++++++ scripts/{run-tests.sh => test/run.sh} | 38 +++---- scripts/{api-smoke.sh => test/smoke.sh} | 8 +- tests/test_mission_store.cpp | 14 ++- 24 files changed, 434 insertions(+), 392 deletions(-) create mode 100644 scripts/README.md create mode 100755 scripts/bench/http.sh delete mode 100755 scripts/benchmark-http.sh delete mode 100644 scripts/docker-benchmark.sh delete mode 100755 scripts/docker-htop.sh delete mode 100755 scripts/docker-stats.sh delete mode 100755 scripts/docker-test.sh delete mode 100755 scripts/docker-up.sh create mode 100755 scripts/docker/bench.sh create mode 100755 scripts/docker/down.sh create mode 100755 scripts/docker/htop.sh create mode 100755 scripts/docker/shell.sh create mode 100755 scripts/docker/stats.sh create mode 100755 scripts/docker/test.sh create mode 100755 scripts/docker/up.sh create mode 100755 scripts/lib/bench.sh create mode 100755 scripts/lib/common.sh rename scripts/{docker-lib.sh => lib/docker.sh} (60%) mode change 100644 => 100755 create mode 100755 scripts/lm.sh rename scripts/{run-tests.sh => test/run.sh} (57%) rename scripts/{api-smoke.sh => test/smoke.sh} (97%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d5c933..3383562 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,5 +29,5 @@ jobs: - name: Run test suite run: | - chmod +x scripts/run-tests.sh scripts/api-smoke.sh - ./scripts/run-tests.sh + chmod +x scripts/lm.sh scripts/test/*.sh scripts/lib/*.sh + ./scripts/lm.sh test run diff --git a/README.md b/README.md index cfab678..7a6cd55 100644 --- a/README.md +++ b/README.md @@ -36,20 +36,20 @@ Mô phỏng cấu hình controller tối thiểu SICK (Dual-Core, 4 GB) trên m ```bash cd /home/robotics/RD/Test3 -sudo docker compose up --build -d -# hoặc: sudo ./scripts/docker-up.sh +./scripts/lm.sh docker up +# hoặc: sudo docker compose up --build -d ``` Kiểm tra giới hạn: ```bash -sudo ./scripts/docker-stats.sh +./scripts/lm.sh docker stats ``` Dừng: ```bash -sudo docker compose down +./scripts/lm.sh docker down ``` Dữ liệu layout vẫn lưu tại `data/` trên host (volume mount). @@ -58,8 +58,7 @@ Kiểm tra tài nguyên trong container: ```bash # Vào shell container -sudo docker exec -it lidar-manager-limited bash -# hoặc: sudo ./scripts/docker-shell.sh +./scripts/lm.sh docker shell # Trong container, thử: htop # CPU/RAM (q để thoát) @@ -70,8 +69,8 @@ cat /proc/meminfo | head ``` ```bash -sudo ./scripts/docker-htop.sh -sudo ./scripts/docker-stats.sh +./scripts/lm.sh docker htop +./scripts/lm.sh docker stats ``` ## Test tự động @@ -80,8 +79,8 @@ Chạy toàn bộ: unit C++ (GTest), API smoke (`curl`), pytest integration. ```bash cd /home/robotics/RD/Test3 -chmod +x scripts/run-tests.sh scripts/api-smoke.sh -./scripts/run-tests.sh +chmod +x scripts/lm.sh scripts/test/*.sh +./scripts/lm.sh test run ``` Chỉ unit test C++: @@ -96,7 +95,7 @@ Chỉ API smoke (server đang chạy, dùng fixture `tests/fixtures/data/`): ```bash ./build/lidar_manager_web 18080 www tests/fixtures/data/state.json & -./scripts/api-smoke.sh http://127.0.0.1:18080 +./scripts/lm.sh test smoke http://127.0.0.1:18080 ``` Fixture mission id mặc định: `testmission00001` (`tests/fixtures/data/missions.json`). @@ -104,8 +103,8 @@ Fixture mission id mặc định: `testmission00001` (`tests/fixtures/data/missi Benchmark hiệu năng trong container (cần `docker compose up -d`): ```bash -chmod +x scripts/docker-benchmark.sh -sudo ./scripts/docker-benchmark.sh +./scripts/lm.sh docker bench +# hoặc chỉ HTTP: ./scripts/lm.sh bench http ``` CI: GitHub Actions workflow `.github/workflows/test.yml`. diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..4391b97 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,43 @@ +# Scripts Test3 + +CLI thống nhất: `./scripts/lm.sh ` + +## Cấu trúc + +``` +scripts/ +├── lm.sh # entry point +├── lib/ +│ ├── common.sh # LM_ROOT, wait_for_health, free_port +│ ├── docker.sh # docker_cmd, require_container +│ └── bench.sh # bench_http_suite +├── test/ +│ ├── run.sh # GTest + smoke + pytest (server tạm :18080) +│ └── smoke.sh # API smoke (curl) +├── docker/ +│ ├── up.sh down.sh stats.sh htop.sh shell.sh +│ ├── bench.sh # stats + HTTP latency trong container +│ └── test.sh # up + smoke + pytest trên :8080 +└── bench/ + └── http.sh # đo latency (local hoặc URL tùy chọn) +``` + +## Lệnh thường dùng + +| Mục đích | Lệnh | +|----------|------| +| Test đầy đủ (local) | `./scripts/lm.sh test run` | +| API smoke | `./scripts/lm.sh test smoke [url]` | +| Docker up | `./scripts/lm.sh docker up` | +| Test trên container | `./scripts/lm.sh docker test` | +| Benchmark HTTP | `./scripts/lm.sh bench http [url]` | + +## Biến môi trường + +| Biến | Mặc định | Ý nghĩa | +|------|----------|---------| +| `LM_URL` | `http://127.0.0.1:8080` | URL container | +| `LM_TEST_PORT` | `18080` | Port server tạm khi `test run` | +| `LM_CONTAINER` | `lidar-manager-limited` | Tên container | +| `TEST_BASE_URL` | — | Base URL cho pytest | +| `BENCH_REQUESTS` | `100` | Số request mỗi endpoint benchmark | diff --git a/scripts/bench/http.sh b/scripts/bench/http.sh new file mode 100755 index 0000000..f64452e --- /dev/null +++ b/scripts/bench/http.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/bench.sh +source "$(dirname "$0")/../lib/bench.sh" + +BASE="${1:-$LM_URL}" +echo "=== HTTP benchmark ===" +echo "URL: $BASE" +echo +bench_http_suite "$BASE" +echo +echo "=== Process ===" +ps -C lidar_manager_web -o pid,rss,vsz,pcpu,pmem,etime,cmd 2>/dev/null \ + || pgrep -af '[./]lidar_manager_web' | grep -v pgrep || true diff --git a/scripts/benchmark-http.sh b/scripts/benchmark-http.sh deleted file mode 100755 index d0d96a4..0000000 --- a/scripts/benchmark-http.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -# Benchmark HTTP latency + tài nguyên process (local hoặc container qua URL). -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -BASE="${1:-http://127.0.0.1:8080}" -REQUESTS="${BENCH_REQUESTS:-100}" -WARMUP="${BENCH_WARMUP:-10}" -LABEL="${2:-}" - -echo "=== lidar_manager_web benchmark ===" -echo "URL: $BASE" -echo "Requests/endpoint: $REQUESTS (warmup $WARMUP)" -[[ -n "$LABEL" ]] && echo "Label: $LABEL" -echo - -if ! curl -sf "$BASE/api/health" >/dev/null; then - echo "Server không phản hồi tại $BASE" >&2 - exit 1 -fi - -bench_endpoint() { - local name="$1" - local method="$2" - local path="$3" - local body="${4:-}" - python3 - "$name" "$method" "$BASE$path" "$REQUESTS" "$WARMUP" "$body" <<'PY' -import statistics -import sys -import time -import urllib.error -import urllib.request - -name, method, url, n_req, n_warm, body = sys.argv[1:7] -n_req = int(n_req) -n_warm = int(n_warm) -data = body.encode() if body else None -headers = {"Content-Type": "application/json"} if data else {} - -def once(): - req = urllib.request.Request(url, data=data, headers=headers, method=method) - t0 = time.perf_counter() - with urllib.request.urlopen(req, timeout=10) as resp: - resp.read() - return (time.perf_counter() - t0) * 1000.0 - -for _ in range(n_warm): - try: - once() - except Exception: - pass - -samples = [] -errors = 0 -for _ in range(n_req): - try: - samples.append(once()) - except Exception as exc: - errors += 1 - -if not samples: - print(f"{name}: FAIL errors={errors}") -else: - samples.sort() - def pct(p): - i = max(0, min(len(samples) - 1, int(len(samples) * p / 100.0) - 1)) - return samples[i] - print( - f"{name}: ok={len(samples)} err={errors} " - f"p50={pct(50):.2f}ms p95={pct(95):.2f}ms avg={statistics.mean(samples):.2f}ms max={samples[-1]:.2f}ms" - ) -PY -} - -echo "=== HTTP latency ===" -bench_endpoint "GET /api/health" GET "/api/health" -bench_endpoint "GET /api/state" GET "/api/state" -bench_endpoint "GET /api/missions" GET "/api/missions" -bench_endpoint "GET /api/mission_queue" GET "/api/mission_queue" -bench_endpoint "GET /api/v2.0.0/mission_queue" GET "/api/v2.0.0/mission_queue" -bench_endpoint "GET /" GET "/" -bench_endpoint "GET /missions.js" GET "/missions.js" - -MID="$(curl -sf "$BASE/api/missions" | python3 -c "import json,sys; m=json.load(sys.stdin).get('missions',[]); print(m[0]['id'] if m else '')" 2>/dev/null || true)" -if [[ -n "$MID" ]]; then - bench_endpoint "POST /api/v2.0.0/mission_queue" POST "/api/v2.0.0/mission_queue" \ - "{\"mission_id\":\"$MID\",\"priority\":0}" -fi - -echo -echo "=== Process lidar_manager_web ===" -ps -C lidar_manager_web -o pid,rss,vsz,pcpu,pmem,etime,cmd 2>/dev/null \ - || pgrep -af '[./]lidar_manager_web' | grep -v pgrep || true diff --git a/scripts/docker-benchmark.sh b/scripts/docker-benchmark.sh deleted file mode 100644 index 4f3b465..0000000 --- a/scripts/docker-benchmark.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env bash -# Hiệu năng lidar_manager_web trong container Docker. -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -# shellcheck source=docker-lib.sh -source "$ROOT/scripts/docker-lib.sh" - -docker_cmd - -NAME="${1:-lidar-manager-limited}" -BASE="${2:-http://127.0.0.1:8080}" -REQUESTS="${BENCH_REQUESTS:-100}" -WARMUP="${BENCH_WARMUP:-10}" - -if ! "${DOCKER[@]}" ps --format '{{.Names}}' | grep -qx "$NAME"; then - echo "Container '$NAME' không chạy. Chạy: sudo docker compose up -d --build" >&2 - exit 1 -fi - -echo "=== PhenikaaX lidar_manager_web — benchmark container ===" -echo "Container: $NAME" -echo "URL: $BASE" -echo "Requests/endpoint: $REQUESTS (warmup $WARMUP)" -echo - -echo "=== Docker stats (snapshot) ===" -"${DOCKER[@]}" stats --no-stream --format \ - 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}' "$NAME" -echo -print_container_limits "$NAME" -echo - -echo "=== Process trong container ===" -"${DOCKER[@]}" exec "$NAME" sh -c ' - echo "PID RSS_KB VSZ_KB CMD" - ps -o pid,rss,vsz,cmd -C lidar_manager_web 2>/dev/null || ps aux | grep lidar_manager_web | grep -v grep - echo - echo "--- /proc/meminfo (head) ---" - head -5 /proc/meminfo - echo "--- nproc ---" - nproc -' -echo - -bench_endpoint() { - local label="$1" - local method="$2" - local path="$3" - local body="${4:-}" - - python3 - "$label" "$method" "$BASE$path" "$REQUESTS" "$WARMUP" "$body" <<'PY' -import json -import statistics -import sys -import time -import urllib.error -import urllib.request - -label, method, url, n_req, n_warm, body = sys.argv[1:7] -n_req = int(n_req) -n_warm = int(n_warm) -data = body.encode() if body else None -headers = {"Content-Type": "application/json"} if data else {} - -def once(): - req = urllib.request.Request(url, data=data, headers=headers, method=method) - t0 = time.perf_counter() - with urllib.request.urlopen(req, timeout=10) as resp: - resp.read() - return (time.perf_counter() - t0) * 1000.0 - -for _ in range(n_warm): - try: - once() - except Exception: - pass - -samples = [] -errors = 0 -for _ in range(n_req): - try: - samples.append(once()) - except Exception: - errors += 1 - -if not samples: - print(f"{label}: FAIL (no successful samples, errors={errors})") -else: - samples.sort() - def pct(p): - i = max(0, min(len(samples) - 1, int(len(samples) * p / 100.0) - 1)) - return samples[i] - print( - f"{label}: ok={len(samples)} err={errors} " - f"min={samples[0]:.2f}ms p50={pct(50):.2f}ms p95={pct(95):.2f}ms " - f"max={samples[-1]:.2f}ms avg={statistics.mean(samples):.2f}ms" - ) -PY -} - -echo "=== HTTP latency (ms) ===" -if ! curl -sf "$BASE/api/health" >/dev/null; then - echo "Server không phản hồi tại $BASE" >&2 - exit 1 -fi - -bench_endpoint "GET /api/health" GET "/api/health" -bench_endpoint "GET /api/state" GET "/api/state" -bench_endpoint "GET /api/missions" GET "/api/missions" -bench_endpoint "GET /api/mission_queue" GET "/api/mission_queue" -bench_endpoint "GET /api/v2.0.0/mission_queue" GET "/api/v2.0.0/mission_queue" -bench_endpoint "GET / (index.html)" GET "/" -bench_endpoint "GET /missions.js" GET "/missions.js" - -MID="$(curl -sf "$BASE/api/missions" | python3 -c "import json,sys; m=json.load(sys.stdin).get('missions',[]); print(m[0]['id'] if m else '')")" -if [[ -n "$MID" ]]; then - bench_endpoint "POST /api/v2.0.0/mission_queue" POST "/api/v2.0.0/mission_queue" \ - "{\"mission_id\":\"$MID\",\"priority\":0}" -fi - -echo -echo "=== Docker stats (sau benchmark) ===" -"${DOCKER[@]}" stats --no-stream --format \ - 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}' "$NAME" diff --git a/scripts/docker-htop.sh b/scripts/docker-htop.sh deleted file mode 100755 index 89a69be..0000000 --- a/scripts/docker-htop.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" -# shellcheck source=docker-lib.sh -source "$ROOT/scripts/docker-lib.sh" - -docker_cmd - -if ! "${DOCKER[@]}" ps --format '{{.Names}}' | grep -qx 'lidar-manager-limited'; then - echo "Container chưa chạy. Khởi động: sudo docker compose up -d" - exit 1 -fi - -echo "Giới hạn container:" -print_container_limits lidar-manager-limited - -echo -echo "Mở htop trong container (q để thoát)..." -exec "${DOCKER[@]}" exec -it lidar-manager-limited htop diff --git a/scripts/docker-stats.sh b/scripts/docker-stats.sh deleted file mode 100755 index 1989f28..0000000 --- a/scripts/docker-stats.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -# shellcheck source=docker-lib.sh -source "$ROOT/scripts/docker-lib.sh" - -docker_cmd - -NAME="${1:-lidar-manager-limited}" - -if ! "${DOCKER[@]}" ps --format '{{.Names}}' | grep -qx "$NAME"; then - echo "Container '$NAME' không chạy." - exit 1 -fi - -echo "=== docker stats (live) ===" -"${DOCKER[@]}" stats --no-stream "$NAME" - -echo -echo "=== limits ===" -print_container_limits "$NAME" - -CID="$("${DOCKER[@]}" inspect -f '{{.Id}}' "$NAME")" -CG="/sys/fs/cgroup" - -echo -echo "=== cgroup (host) ===" -printf 'memory.usage = ' -cat "$CG/memory/docker/$CID/memory.usage_in_bytes" 2>/dev/null \ - || cat "$CG/memory/system.slice/docker-$CID.scope/memory.usage_in_bytes" 2>/dev/null \ - || echo "n/a" -printf ' bytes\n' - -printf 'cpu.cfs_quota / period = ' -cat "$CG/cpu,cpuacct/docker/$CID/cpu.cfs_quota_us" 2>/dev/null \ - || echo -n "n/a" -printf ' / ' -cat "$CG/cpu,cpuacct/docker/$CID/cpu.cfs_period_us" 2>/dev/null \ - || echo "n/a" - -echo -echo "=== processes in container ===" -"${DOCKER[@]}" top "$NAME" diff --git a/scripts/docker-test.sh b/scripts/docker-test.sh deleted file mode 100755 index d902599..0000000 --- a/scripts/docker-test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# Build image, start container, chạy smoke + pytest trên port 8080. -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" -# shellcheck source=docker-lib.sh -source "$ROOT/scripts/docker-lib.sh" - -docker_cmd - -BASE="${TEST_BASE_URL:-http://127.0.0.1:8080}" - -echo "==> Docker compose up --build -d" -"${DOCKER[@]}" compose up --build -d - -echo "==> Đợi server sẵn sàng ($BASE)" -for i in $(seq 1 40); do - if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - break - fi - sleep 0.5 -done - -if ! curl -sf "$BASE/api/health" >/dev/null 2>&1; then - echo "Container không phản hồi tại $BASE" >&2 - "${DOCKER[@]}" logs --tail 30 lidar-manager-limited 2>&1 || true - exit 1 -fi - -echo "==> Unit + integration tests (local build)" -./scripts/run-tests.sh - -echo "==> API smoke (container $BASE)" -./scripts/api-smoke.sh "$BASE" - -echo "==> pytest (container $BASE)" -TEST_BASE_URL="$BASE" python3 -m pytest tests/test_api_integration.py -q - -echo -echo "Docker + tests OK." diff --git a/scripts/docker-up.sh b/scripts/docker-up.sh deleted file mode 100755 index 5c8ff5d..0000000 --- a/scripts/docker-up.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" -# shellcheck source=docker-lib.sh -source "$ROOT/scripts/docker-lib.sh" - -docker_cmd - -if ! docker info >/dev/null 2>&1 && ! sudo -n docker info >/dev/null 2>&1; then - echo "Cần quyền Docker. Chạy một trong hai cách:" - echo " sudo usermod -aG docker \$USER # rồi đăng nhập lại" - echo " sudo $0" -fi - -"${DOCKER[@]}" compose up --build -d -echo "Đã chạy: http://localhost:8080/" -echo -n "Giới hạn: " -print_container_limits lidar-manager-limited diff --git a/scripts/docker/bench.sh b/scripts/docker/bench.sh new file mode 100755 index 0000000..85464b6 --- /dev/null +++ b/scripts/docker/bench.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/docker.sh +source "$(dirname "$0")/../lib/docker.sh" +# shellcheck source=../lib/bench.sh +source "$(dirname "$0")/../lib/bench.sh" + +NAME="${1:-$LM_CONTAINER}" +BASE="${2:-$LM_URL}" +require_container "$NAME" + +echo "=== Benchmark container: $NAME ===" +echo "URL: $BASE" +echo + +echo "=== docker stats (trước) ===" +"${DOCKER[@]}" stats --no-stream --format \ + 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}' "$NAME" +print_container_limits "$NAME" +echo + +echo "=== HTTP latency ===" +bench_http_suite "$BASE" + +echo +echo "=== docker stats (sau) ===" +"${DOCKER[@]}" stats --no-stream --format \ + 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}' "$NAME" diff --git a/scripts/docker/down.sh b/scripts/docker/down.sh new file mode 100755 index 0000000..8c87659 --- /dev/null +++ b/scripts/docker/down.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/docker.sh +source "$(dirname "$0")/../lib/docker.sh" + +cd "$LM_ROOT" +docker_cmd +"${DOCKER[@]}" compose down diff --git a/scripts/docker/htop.sh b/scripts/docker/htop.sh new file mode 100755 index 0000000..37b8b6c --- /dev/null +++ b/scripts/docker/htop.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/docker.sh +source "$(dirname "$0")/../lib/docker.sh" + +require_container "$LM_CONTAINER" +echo "Giới hạn container:" +print_container_limits "$LM_CONTAINER" +echo +echo "htop (q để thoát)..." +exec "${DOCKER[@]}" exec -it "$LM_CONTAINER" htop diff --git a/scripts/docker/shell.sh b/scripts/docker/shell.sh new file mode 100755 index 0000000..3232f20 --- /dev/null +++ b/scripts/docker/shell.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/docker.sh +source "$(dirname "$0")/../lib/docker.sh" + +require_container "$LM_CONTAINER" +exec "${DOCKER[@]}" exec -it "$LM_CONTAINER" bash diff --git a/scripts/docker/stats.sh b/scripts/docker/stats.sh new file mode 100755 index 0000000..0833d99 --- /dev/null +++ b/scripts/docker/stats.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/docker.sh +source "$(dirname "$0")/../lib/docker.sh" + +NAME="${1:-$LM_CONTAINER}" +require_container "$NAME" + +echo "=== docker stats ===" +"${DOCKER[@]}" stats --no-stream "$NAME" +echo +echo "=== limits ===" +print_container_limits "$NAME" + +CID="$("${DOCKER[@]}" inspect -f '{{.Id}}' "$NAME")" +CG="/sys/fs/cgroup" +echo +echo "=== cgroup (host) ===" +printf 'memory.usage = ' +cat "$CG/memory/docker/$CID/memory.usage_in_bytes" 2>/dev/null \ + || cat "$CG/memory/system.slice/docker-$CID.scope/memory.usage_in_bytes" 2>/dev/null \ + || echo "n/a" +printf ' bytes\n' +echo +echo "=== processes ===" +"${DOCKER[@]}" top "$NAME" diff --git a/scripts/docker/test.sh b/scripts/docker/test.sh new file mode 100755 index 0000000..8f74bc0 --- /dev/null +++ b/scripts/docker/test.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" + +BASE="${TEST_BASE_URL:-$LM_URL}" +export LM_URL="$BASE" + +echo "==> Docker up" +"$(dirname "$0")/up.sh" + +echo "==> Đợi server ($BASE)" +if ! wait_for_health "$BASE"; then + echo "Container không phản hồi tại $BASE" >&2 + exit 1 +fi + +echo "==> API smoke" +"$LM_SCRIPTS/test/smoke.sh" "$BASE" + +echo "==> pytest" +TEST_BASE_URL="$BASE" python3 -m pytest "$LM_ROOT/tests/test_api_integration.py" -q + +echo +echo "Docker tests OK." diff --git a/scripts/docker/up.sh b/scripts/docker/up.sh new file mode 100755 index 0000000..172ce1b --- /dev/null +++ b/scripts/docker/up.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" +# shellcheck source=../lib/docker.sh +source "$(dirname "$0")/../lib/docker.sh" + +cd "$LM_ROOT" +docker_cmd + +if ! docker info >/dev/null 2>&1 && ! sudo -n docker info >/dev/null 2>&1; then + echo "Cần quyền Docker. Thử: sudo $0 hoặc usermod -aG docker \$USER" +fi + +"${DOCKER[@]}" compose up --build -d +echo "Đã chạy: http://localhost:8080/" +echo -n "Giới hạn: " +print_container_limits "$LM_CONTAINER" diff --git a/scripts/lib/bench.sh b/scripts/lib/bench.sh new file mode 100755 index 0000000..d6d68b6 --- /dev/null +++ b/scripts/lib/bench.sh @@ -0,0 +1,71 @@ +# shellcheck shell=bash + +bench_http_endpoint() { + local label="$1" method="$2" path="$3" base="$4" + local body="${5:-}" requests="${6:-${BENCH_REQUESTS:-100}}" warmup="${7:-${BENCH_WARMUP:-10}}" + python3 - "$label" "$method" "$base$path" "$requests" "$warmup" "$body" <<'PY' +import statistics +import sys +import urllib.request + +label, method, url, n_req, n_warm, body = sys.argv[1:7] +n_req = int(n_req) +n_warm = int(n_warm) +data = body.encode() if body else None +headers = {"Content-Type": "application/json"} if data else {} + +def once(): + req = urllib.request.Request(url, data=data, headers=headers, method=method) + t0 = __import__("time").perf_counter() + with urllib.request.urlopen(req, timeout=10) as resp: + resp.read() + return (__import__("time").perf_counter() - t0) * 1000.0 + +for _ in range(n_warm): + try: + once() + except Exception: + pass + +samples = [] +errors = 0 +for _ in range(n_req): + try: + samples.append(once()) + except Exception: + errors += 1 + +if not samples: + print(f"{label}: FAIL errors={errors}") +else: + samples.sort() + def pct(p): + i = max(0, min(len(samples) - 1, int(len(samples) * p / 100.0) - 1)) + return samples[i] + print( + f"{label}: ok={len(samples)} err={errors} " + f"p50={pct(50):.2f}ms p95={pct(95):.2f}ms avg={statistics.mean(samples):.2f}ms max={samples[-1]:.2f}ms" + ) +PY +} + +bench_http_suite() { + local base="${1:-$LM_URL}" + if ! curl -sf "${base}/api/health" >/dev/null; then + echo "Server không phản hồi tại $base" >&2 + return 1 + fi + bench_http_endpoint "GET /api/health" GET "/api/health" "$base" + bench_http_endpoint "GET /api/state" GET "/api/state" "$base" + bench_http_endpoint "GET /api/missions" GET "/api/missions" "$base" + bench_http_endpoint "GET /api/mission_queue" GET "/api/mission_queue" "$base" + bench_http_endpoint "GET /api/v2.0.0/mission_queue" GET "/api/v2.0.0/mission_queue" "$base" + bench_http_endpoint "GET /" GET "/" "$base" + bench_http_endpoint "GET /missions.js" GET "/missions.js" "$base" + local mid + mid="$(curl -sf "${base}/api/missions" | python3 -c "import json,sys; m=json.load(sys.stdin).get('missions',[]); print(m[0]['id'] if m else '')" 2>/dev/null || true)" + if [[ -n "$mid" ]]; then + bench_http_endpoint "POST /api/v2.0.0/mission_queue" POST "/api/v2.0.0/mission_queue" "$base" \ + "{\"mission_id\":\"${mid}\",\"priority\":0}" + fi +} diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh new file mode 100755 index 0000000..ee941e9 --- /dev/null +++ b/scripts/lib/common.sh @@ -0,0 +1,33 @@ +# Shared paths and helpers for Test3 scripts. +# shellcheck shell=bash + +_lm_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LM_ROOT="$(cd "$_lm_lib_dir/../.." && pwd)" +LM_SCRIPTS="$(cd "$_lm_lib_dir/.." && pwd)" +LM_CONTAINER="${LM_CONTAINER:-lidar-manager-limited}" +LM_URL="${LM_URL:-http://127.0.0.1:8080}" +LM_TEST_PORT="${LM_TEST_PORT:-18080}" + +wait_for_health() { + local base="${1:-$LM_URL}" + local tries="${2:-40}" + local i + for ((i = 1; i <= tries; i++)); do + if curl -sf "${base}/api/health" >/dev/null 2>&1; then + return 0 + fi + sleep 0.5 + done + return 1 +} + +free_port() { + local port="$1" + if command -v fuser >/dev/null 2>&1; then + fuser -k "${port}/tcp" 2>/dev/null || true + elif command -v lsof >/dev/null 2>&1; then + local pids + pids="$(lsof -ti "tcp:${port}" 2>/dev/null || true)" + [[ -n "$pids" ]] && kill $pids 2>/dev/null || true + fi +} diff --git a/scripts/docker-lib.sh b/scripts/lib/docker.sh old mode 100644 new mode 100755 similarity index 60% rename from scripts/docker-lib.sh rename to scripts/lib/docker.sh index 6423002..1818ad6 --- a/scripts/docker-lib.sh +++ b/scripts/lib/docker.sh @@ -1,4 +1,6 @@ -#!/usr/bin/env bash +# shellcheck shell=bash +# shellcheck source=common.sh +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" docker_cmd() { if docker info >/dev/null 2>&1; then @@ -10,8 +12,17 @@ docker_cmd() { fi } +require_container() { + local name="${1:-$LM_CONTAINER}" + docker_cmd + if ! "${DOCKER[@]}" ps --format '{{.Names}}' | grep -qx "$name"; then + echo "Container '$name' không chạy. Thử: ./scripts/lm.sh docker up" >&2 + exit 1 + fi +} + print_container_limits() { - local name="${1:-lidar-manager-limited}" + local name="${1:-$LM_CONTAINER}" local nano mem cpus ram_mb nano="$("${DOCKER[@]}" inspect -f '{{.HostConfig.NanoCpus}}' "$name")" mem="$("${DOCKER[@]}" inspect -f '{{.HostConfig.Memory}}' "$name")" diff --git a/scripts/lm.sh b/scripts/lm.sh new file mode 100755 index 0000000..0c028fb --- /dev/null +++ b/scripts/lm.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# PhenikaaX Test3 — CLI gom script theo nhóm. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && pwd)" + +usage() { + cat < [args...] + +Test (local build + server tạm): + test run GTest + smoke + pytest (port \$LM_TEST_PORT, mặc định 18080) + test smoke [url] Chỉ API smoke (mặc định http://127.0.0.1:18080) + +Docker (container :8080): + docker up compose up --build -d + docker down compose down + docker stats [name] CPU/RAM/cgroup + docker htop htop trong container + docker shell bash trong container + docker bench [url] Stats + đo latency HTTP + docker test up + smoke + pytest trên container + +Benchmark: + bench http [url] Đo latency (mặc định \$LM_URL) + +Biến môi trường: LM_URL, LM_TEST_PORT, LM_CONTAINER, TEST_BASE_URL, BENCH_REQUESTS +EOF +} + +cmd="${1:-help}" +shift || true + +case "$cmd" in + help|-h|--help) + usage + ;; + test) + sub="${1:-run}" + shift || true + case "$sub" in + run) exec "$ROOT/test/run.sh" "$@" ;; + smoke) exec "$ROOT/test/smoke.sh" "$@" ;; + *) echo "Unknown: test $sub" >&2; usage >&2; exit 1 ;; + esac + ;; + docker) + sub="${1:-}" + shift || true + case "$sub" in + up) exec "$ROOT/docker/up.sh" "$@" ;; + down) exec "$ROOT/docker/down.sh" "$@" ;; + stats) exec "$ROOT/docker/stats.sh" "$@" ;; + htop) exec "$ROOT/docker/htop.sh" "$@" ;; + shell) exec "$ROOT/docker/shell.sh" "$@" ;; + bench) exec "$ROOT/docker/bench.sh" "$@" ;; + test) exec "$ROOT/docker/test.sh" "$@" ;; + *) echo "Unknown: docker $sub" >&2; usage >&2; exit 1 ;; + esac + ;; + bench) + sub="${1:-http}" + shift || true + case "$sub" in + http) exec "$ROOT/bench/http.sh" "$@" ;; + *) echo "Unknown: bench $sub" >&2; usage >&2; exit 1 ;; + esac + ;; + *) + echo "Unknown group: $cmd" >&2 + usage >&2 + exit 1 + ;; +esac diff --git a/scripts/run-tests.sh b/scripts/test/run.sh similarity index 57% rename from scripts/run-tests.sh rename to scripts/test/run.sh index 443494e..e166f27 100755 --- a/scripts/run-tests.sh +++ b/scripts/test/run.sh @@ -1,23 +1,17 @@ #!/usr/bin/env bash -# Build unit tests, start lidar_manager_web on a temp data dir, run API smoke + pytest. set -euo pipefail -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" -PORT="${TEST_PORT:-18080}" +cd "$LM_ROOT" +PORT="${TEST_PORT:-$LM_TEST_PORT}" BASE="http://127.0.0.1:${PORT}" -BIN="${ROOT}/build/lidar_manager_web" +BIN="${LM_ROOT}/build/lidar_manager_web" DATA_DIR="$(mktemp -d)" SERVER_PID="" -# Dọn server test cũ trên cùng port (nếu còn). -if command -v fuser >/dev/null 2>&1; then - fuser -k "${PORT}/tcp" 2>/dev/null || true -elif command -v lsof >/dev/null 2>&1; then - PIDS="$(lsof -ti "tcp:${PORT}" 2>/dev/null || true)" - [[ -n "$PIDS" ]] && kill $PIDS 2>/dev/null || true -fi +free_port "$PORT" sleep 0.2 cleanup() { @@ -41,30 +35,22 @@ cp -a tests/fixtures/data/. "$DATA_DIR/" mkdir -p "$DATA_DIR/models" echo "==> Start server on port $PORT" -"$BIN" "$PORT" "$ROOT/www" "$DATA_DIR/state.json" >"$DATA_DIR/server.log" 2>&1 & +"$BIN" "$PORT" "$LM_ROOT/www" "$DATA_DIR/state.json" >"$DATA_DIR/server.log" 2>&1 & SERVER_PID=$! -for i in $(seq 1 30); do - if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - break - fi +if ! wait_for_health "$BASE" 30; then if ! kill -0 "$SERVER_PID" 2>/dev/null; then echo "Server exited early:" >&2 cat "$DATA_DIR/server.log" >&2 || true - exit 1 + else + echo "Server did not become ready on $BASE" >&2 + cat "$DATA_DIR/server.log" >&2 || true fi - sleep 0.2 -done - -if ! curl -sf "$BASE/api/health" >/dev/null 2>&1; then - echo "Server did not become ready on $BASE" >&2 - cat "$DATA_DIR/server.log" >&2 || true exit 1 fi echo "==> API smoke tests" -chmod +x scripts/api-smoke.sh -./scripts/api-smoke.sh "$BASE" +"$LM_SCRIPTS/test/smoke.sh" "$BASE" if command -v python3 >/dev/null 2>&1; then echo "==> Python integration tests (pytest)" diff --git a/scripts/api-smoke.sh b/scripts/test/smoke.sh similarity index 97% rename from scripts/api-smoke.sh rename to scripts/test/smoke.sh index bff3b99..3f91cbf 100755 --- a/scripts/api-smoke.sh +++ b/scripts/test/smoke.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash -# API smoke tests for lidar_manager_web — run against a live server instance. +# API smoke tests — chạy trên server đang lắng nghe. set -euo pipefail -BASE="${1:-http://127.0.0.1:18080}" -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck source=../lib/common.sh +source "$(dirname "$0")/../lib/common.sh" + +BASE="${1:-http://127.0.0.1:${LM_TEST_PORT}}" MISSION_ID="${TEST_MISSION_ID:-}" RED='\033[0;31m' diff --git a/tests/test_mission_store.cpp b/tests/test_mission_store.cpp index 9f0dfc1..ccf6902 100644 --- a/tests/test_mission_store.cpp +++ b/tests/test_mission_store.cpp @@ -2,7 +2,9 @@ #include +#include #include +#include namespace { @@ -11,8 +13,10 @@ class MissionStoreTest : public ::testing::Test protected: void SetUp() override { - dir_ = std::filesystem::temp_directory_path() / "lm_test_store"; - std::filesystem::remove_all(dir_); + static std::atomic seq{0}; + dir_ = std::filesystem::temp_directory_path() + / ("lm_test_" + std::to_string(getpid()) + "_" + + std::to_string(seq.fetch_add(1))); std::filesystem::create_directories(dir_); store_path_ = dir_ / "missions.json"; std::filesystem::copy_file(std::filesystem::path(TEST_FIXTURE_DIR) / "missions.json", @@ -21,6 +25,12 @@ protected: store_ = std::make_unique(store_path_); } + void TearDown() override + { + std::error_code ec; + std::filesystem::remove_all(dir_, ec); + } + std::filesystem::path dir_; std::filesystem::path store_path_; std::unique_ptr store_;