4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -29,5 +29,5 @@ jobs:
|
|||||||
|
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
run: |
|
run: |
|
||||||
chmod +x scripts/run-tests.sh scripts/api-smoke.sh
|
chmod +x scripts/lm.sh scripts/test/*.sh scripts/lib/*.sh
|
||||||
./scripts/run-tests.sh
|
./scripts/lm.sh test run
|
||||||
|
|||||||
25
README.md
25
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
|
```bash
|
||||||
cd /home/robotics/RD/Test3
|
cd /home/robotics/RD/Test3
|
||||||
sudo docker compose up --build -d
|
./scripts/lm.sh docker up
|
||||||
# hoặc: sudo ./scripts/docker-up.sh
|
# hoặc: sudo docker compose up --build -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Kiểm tra giới hạn:
|
Kiểm tra giới hạn:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./scripts/docker-stats.sh
|
./scripts/lm.sh docker stats
|
||||||
```
|
```
|
||||||
|
|
||||||
Dừng:
|
Dừng:
|
||||||
|
|
||||||
```bash
|
```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).
|
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
|
```bash
|
||||||
# Vào shell container
|
# Vào shell container
|
||||||
sudo docker exec -it lidar-manager-limited bash
|
./scripts/lm.sh docker shell
|
||||||
# hoặc: sudo ./scripts/docker-shell.sh
|
|
||||||
|
|
||||||
# Trong container, thử:
|
# Trong container, thử:
|
||||||
htop # CPU/RAM (q để thoát)
|
htop # CPU/RAM (q để thoát)
|
||||||
@@ -70,8 +69,8 @@ cat /proc/meminfo | head
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./scripts/docker-htop.sh
|
./scripts/lm.sh docker htop
|
||||||
sudo ./scripts/docker-stats.sh
|
./scripts/lm.sh docker stats
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test tự động
|
## Test tự động
|
||||||
@@ -80,8 +79,8 @@ Chạy toàn bộ: unit C++ (GTest), API smoke (`curl`), pytest integration.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/robotics/RD/Test3
|
cd /home/robotics/RD/Test3
|
||||||
chmod +x scripts/run-tests.sh scripts/api-smoke.sh
|
chmod +x scripts/lm.sh scripts/test/*.sh
|
||||||
./scripts/run-tests.sh
|
./scripts/lm.sh test run
|
||||||
```
|
```
|
||||||
|
|
||||||
Chỉ unit test C++:
|
Chỉ unit test C++:
|
||||||
@@ -96,7 +95,7 @@ Chỉ API smoke (server đang chạy, dùng fixture `tests/fixtures/data/`):
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./build/lidar_manager_web 18080 www tests/fixtures/data/state.json &
|
./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`).
|
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`):
|
Benchmark hiệu năng trong container (cần `docker compose up -d`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x scripts/docker-benchmark.sh
|
./scripts/lm.sh docker bench
|
||||||
sudo ./scripts/docker-benchmark.sh
|
# hoặc chỉ HTTP: ./scripts/lm.sh bench http
|
||||||
```
|
```
|
||||||
|
|
||||||
CI: GitHub Actions workflow `.github/workflows/test.yml`.
|
CI: GitHub Actions workflow `.github/workflows/test.yml`.
|
||||||
|
|||||||
43
scripts/README.md
Normal file
43
scripts/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Scripts Test3
|
||||||
|
|
||||||
|
CLI thống nhất: `./scripts/lm.sh <nhóm> <lệnh>`
|
||||||
|
|
||||||
|
## 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 |
|
||||||
17
scripts/bench/http.sh
Executable file
17
scripts/bench/http.sh
Executable file
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
@@ -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."
|
|
||||||
@@ -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
|
|
||||||
31
scripts/docker/bench.sh
Executable file
31
scripts/docker/bench.sh
Executable file
@@ -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"
|
||||||
11
scripts/docker/down.sh
Executable file
11
scripts/docker/down.sh
Executable file
@@ -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
|
||||||
14
scripts/docker/htop.sh
Executable file
14
scripts/docker/htop.sh
Executable file
@@ -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
|
||||||
10
scripts/docker/shell.sh
Executable file
10
scripts/docker/shell.sh
Executable file
@@ -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
|
||||||
29
scripts/docker/stats.sh
Executable file
29
scripts/docker/stats.sh
Executable file
@@ -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"
|
||||||
26
scripts/docker/test.sh
Executable file
26
scripts/docker/test.sh
Executable file
@@ -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."
|
||||||
19
scripts/docker/up.sh
Executable file
19
scripts/docker/up.sh
Executable file
@@ -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"
|
||||||
71
scripts/lib/bench.sh
Executable file
71
scripts/lib/bench.sh
Executable file
@@ -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
|
||||||
|
}
|
||||||
33
scripts/lib/common.sh
Executable file
33
scripts/lib/common.sh
Executable file
@@ -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
|
||||||
|
}
|
||||||
15
scripts/docker-lib.sh → scripts/lib/docker.sh
Normal file → Executable file
15
scripts/docker-lib.sh → scripts/lib/docker.sh
Normal file → Executable file
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
# shellcheck shell=bash
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
||||||
|
|
||||||
docker_cmd() {
|
docker_cmd() {
|
||||||
if docker info >/dev/null 2>&1; then
|
if docker info >/dev/null 2>&1; then
|
||||||
@@ -10,8 +12,17 @@ docker_cmd() {
|
|||||||
fi
|
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() {
|
print_container_limits() {
|
||||||
local name="${1:-lidar-manager-limited}"
|
local name="${1:-$LM_CONTAINER}"
|
||||||
local nano mem cpus ram_mb
|
local nano mem cpus ram_mb
|
||||||
nano="$("${DOCKER[@]}" inspect -f '{{.HostConfig.NanoCpus}}' "$name")"
|
nano="$("${DOCKER[@]}" inspect -f '{{.HostConfig.NanoCpus}}' "$name")"
|
||||||
mem="$("${DOCKER[@]}" inspect -f '{{.HostConfig.Memory}}' "$name")"
|
mem="$("${DOCKER[@]}" inspect -f '{{.HostConfig.Memory}}' "$name")"
|
||||||
74
scripts/lm.sh
Executable file
74
scripts/lm.sh
Executable file
@@ -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 <<EOF
|
||||||
|
Usage: ./scripts/lm.sh <nhóm> <lệnh> [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
|
||||||
@@ -1,23 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Build unit tests, start lidar_manager_web on a temp data dir, run API smoke + pytest.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
# shellcheck source=../lib/common.sh
|
||||||
cd "$ROOT"
|
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}"
|
BASE="http://127.0.0.1:${PORT}"
|
||||||
BIN="${ROOT}/build/lidar_manager_web"
|
BIN="${LM_ROOT}/build/lidar_manager_web"
|
||||||
DATA_DIR="$(mktemp -d)"
|
DATA_DIR="$(mktemp -d)"
|
||||||
SERVER_PID=""
|
SERVER_PID=""
|
||||||
|
|
||||||
# Dọn server test cũ trên cùng port (nếu còn).
|
free_port "$PORT"
|
||||||
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
|
|
||||||
sleep 0.2
|
sleep 0.2
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
@@ -41,30 +35,22 @@ cp -a tests/fixtures/data/. "$DATA_DIR/"
|
|||||||
mkdir -p "$DATA_DIR/models"
|
mkdir -p "$DATA_DIR/models"
|
||||||
|
|
||||||
echo "==> Start server on port $PORT"
|
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=$!
|
SERVER_PID=$!
|
||||||
|
|
||||||
for i in $(seq 1 30); do
|
if ! wait_for_health "$BASE" 30; then
|
||||||
if curl -sf "$BASE/api/health" >/dev/null 2>&1; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||||
echo "Server exited early:" >&2
|
echo "Server exited early:" >&2
|
||||||
cat "$DATA_DIR/server.log" >&2 || true
|
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
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> API smoke tests"
|
echo "==> API smoke tests"
|
||||||
chmod +x scripts/api-smoke.sh
|
"$LM_SCRIPTS/test/smoke.sh" "$BASE"
|
||||||
./scripts/api-smoke.sh "$BASE"
|
|
||||||
|
|
||||||
if command -v python3 >/dev/null 2>&1; then
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
echo "==> Python integration tests (pytest)"
|
echo "==> Python integration tests (pytest)"
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
set -euo pipefail
|
||||||
|
|
||||||
BASE="${1:-http://127.0.0.1:18080}"
|
# shellcheck source=../lib/common.sh
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
source "$(dirname "$0")/../lib/common.sh"
|
||||||
|
|
||||||
|
BASE="${1:-http://127.0.0.1:${LM_TEST_PORT}}"
|
||||||
MISSION_ID="${TEST_MISSION_ID:-}"
|
MISSION_ID="${TEST_MISSION_ID:-}"
|
||||||
|
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -11,8 +13,10 @@ class MissionStoreTest : public ::testing::Test
|
|||||||
protected:
|
protected:
|
||||||
void SetUp() override
|
void SetUp() override
|
||||||
{
|
{
|
||||||
dir_ = std::filesystem::temp_directory_path() / "lm_test_store";
|
static std::atomic<unsigned> seq{0};
|
||||||
std::filesystem::remove_all(dir_);
|
dir_ = std::filesystem::temp_directory_path()
|
||||||
|
/ ("lm_test_" + std::to_string(getpid()) + "_"
|
||||||
|
+ std::to_string(seq.fetch_add(1)));
|
||||||
std::filesystem::create_directories(dir_);
|
std::filesystem::create_directories(dir_);
|
||||||
store_path_ = dir_ / "missions.json";
|
store_path_ = dir_ / "missions.json";
|
||||||
std::filesystem::copy_file(std::filesystem::path(TEST_FIXTURE_DIR) / "missions.json",
|
std::filesystem::copy_file(std::filesystem::path(TEST_FIXTURE_DIR) / "missions.json",
|
||||||
@@ -21,6 +25,12 @@ protected:
|
|||||||
store_ = std::make_unique<lm::MissionStore>(store_path_);
|
store_ = std::make_unique<lm::MissionStore>(store_path_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TearDown() override
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove_all(dir_, ec);
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path dir_;
|
std::filesystem::path dir_;
|
||||||
std::filesystem::path store_path_;
|
std::filesystem::path store_path_;
|
||||||
std::unique_ptr<lm::MissionStore> store_;
|
std::unique_ptr<lm::MissionStore> store_;
|
||||||
|
|||||||
Reference in New Issue
Block a user