Skip to content

Commit 9181402

Browse files
committed
Add script to run e2e tests locally
1 parent bf3acdb commit 9181402

4 files changed

Lines changed: 227 additions & 2 deletions

File tree

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ test: build
3333
.PHONY: end2end
3434
end2end:
3535
poetry run pytest end2end/
36+
.PHONY: e2e
37+
e2e:
38+
@if [ -z "$(app)" ]; then echo "Usage: make e2e app=<app-name> (e.g. make e2e app=flask-postgres)"; exit 1; fi
39+
./end2end/e2e.sh $(app)
3640
.PHONY: cov
3741
cov: build
3842
poetry run pytest aikido_zen/ --cov=aikido_zen --cov-report=xml

end2end/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# End-to-end tests
2+
3+
Requires Docker.
4+
5+
```sh
6+
./end2end/e2e.sh <app-name>
7+
# or
8+
make e2e app=<app-name>
9+
```
10+
11+
Available apps:
12+
13+
- `django-mysql`
14+
- `django-mysql-gunicorn`
15+
- `django-postgres-gunicorn`
16+
- `flask-mongo`
17+
- `flask-mysql`
18+
- `flask-mysql-uwsgi`
19+
- `flask-postgres`
20+
- `flask-postgres-xml`
21+
- `quart-postgres-uvicorn`
22+
- `starlette-postgres-uvicorn`

end2end/e2e.sh

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/bin/bash
2+
# Run a single e2e test suite locally.
3+
# Usage: ./e2e.sh <app-name>
4+
# Example: ./e2e.sh flask-postgres
5+
6+
set -euo pipefail
7+
8+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
9+
10+
get_testfiles() {
11+
local app="$1"
12+
local slug="${app//-/_}"
13+
local found=""
14+
for f in "$SCRIPT_DIR"/end2end/"${slug}"*_test.py; do
15+
[[ -f "$f" ]] || continue
16+
local stem best_app candidate candidate_slug
17+
stem="$(basename "$f" _test.py)"
18+
best_app=""
19+
for dir in "$SCRIPT_DIR"/sample-apps/*/; do
20+
candidate="$(basename "$dir")"
21+
candidate_slug="${candidate//-/_}"
22+
if [[ "$stem" == "${candidate_slug}"* ]] && [[ ${#candidate} -gt ${#best_app} ]]; then
23+
best_app="$candidate"
24+
fi
25+
done
26+
[[ "$best_app" == "$app" ]] && found="$found end2end/$(basename "$f")"
27+
done
28+
echo "${found# }"
29+
}
30+
31+
get_available_apps() {
32+
for dir in "$SCRIPT_DIR"/sample-apps/*/; do
33+
app="$(basename "$dir")"
34+
if grep -q "runZenDisabled" "$dir/Makefile" 2>/dev/null; then
35+
if [[ -n "$(get_testfiles "$app")" ]]; then
36+
echo " $app"
37+
fi
38+
fi
39+
done | sort
40+
}
41+
42+
APP="${1:-}"
43+
if [[ -z "$APP" ]]; then
44+
echo "Usage: ./e2e.sh <app-name>"
45+
echo ""
46+
echo "Available apps:"
47+
get_available_apps
48+
exit 1
49+
fi
50+
51+
APP_DIR="sample-apps/$APP"
52+
if [[ ! -d "$SCRIPT_DIR/$APP_DIR" ]]; then
53+
echo "Unknown app: $APP"
54+
echo ""
55+
echo "Available apps:"
56+
get_available_apps
57+
exit 1
58+
fi
59+
60+
TESTFILE="$(get_testfiles "$APP")"
61+
if [[ -z "$TESTFILE" ]]; then
62+
echo "No test file found for $APP (expected end2end/$(echo "$APP" | tr '-' '_')*_test.py)"
63+
exit 1
64+
fi
65+
LOG_DIR="$(mktemp -d)"
66+
MOCK_PID=""
67+
APP_PID=""
68+
APP_DISABLED_PID=""
69+
70+
kill_tree() {
71+
local pid="$1"
72+
# Kill all descendants first, then the process itself
73+
for child in $(pgrep -P "$pid" 2>/dev/null); do kill_tree "$child"; done
74+
kill "$pid" 2>/dev/null || true
75+
}
76+
77+
cleanup() {
78+
local exit_code=$?
79+
echo ""
80+
echo "Cleaning up..."
81+
[[ -n "$MOCK_PID" ]] && kill_tree "$MOCK_PID"
82+
[[ -n "$APP_PID" ]] && kill_tree "$APP_PID"
83+
[[ -n "$APP_DISABLED_PID" ]] && kill_tree "$APP_DISABLED_PID"
84+
if [[ $exit_code -ne 0 ]]; then
85+
echo ""
86+
echo "=== app.log ==="
87+
cat "$LOG_DIR/app.log" 2>/dev/null || true
88+
echo ""
89+
echo "=== app-disabled.log ==="
90+
cat "$LOG_DIR/app-disabled.log" 2>/dev/null || true
91+
fi
92+
rm -rf "$LOG_DIR"
93+
}
94+
trap cleanup EXIT
95+
96+
wait_for_port() {
97+
local port="$1"
98+
local label="$2"
99+
local logfile="$3"
100+
local pid="${4:-}"
101+
local timeout="${5:-60}"
102+
local end=$((SECONDS + timeout))
103+
echo "Waiting for $label on port $port..."
104+
while [[ $SECONDS -lt $end ]]; do
105+
if [[ -n "$pid" ]] && ! kill -0 "$pid" 2>/dev/null; then
106+
echo " $label process exited unexpectedly"
107+
echo "--- logs ---"
108+
cat "$logfile" 2>/dev/null || true
109+
exit 1
110+
fi
111+
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$port/" 2>/dev/null | grep -qE "^[2345]"; then
112+
echo " $label is up"
113+
return 0
114+
fi
115+
sleep 2
116+
done
117+
echo " $label failed to start on port $port within ${timeout}s"
118+
echo "--- logs ---"
119+
cat "$logfile" 2>/dev/null || true
120+
exit 1
121+
}
122+
123+
wait_for_tcp() {
124+
local host="$1"
125+
local port="$2"
126+
local label="$3"
127+
local timeout="${4:-60}"
128+
local end=$((SECONDS + timeout))
129+
echo "Waiting for $label on $host:$port..."
130+
while [[ $SECONDS -lt $end ]]; do
131+
if (echo > /dev/tcp/"$host"/"$port") 2>/dev/null; then
132+
echo " $label is up"
133+
return 0
134+
fi
135+
sleep 2
136+
done
137+
echo " $label failed to start within ${timeout}s"
138+
exit 1
139+
}
140+
141+
# 1. Databases
142+
echo "Starting databases..."
143+
docker compose -f "$SCRIPT_DIR/sample-apps/databases/docker-compose.yml" up -d
144+
wait_for_tcp localhost 5432 "postgres"
145+
146+
# 2. Mock server
147+
echo "Starting mock Aikido server..."
148+
poetry run python3 "$SCRIPT_DIR/end2end/server/mock_aikido_core.py" 5000 > "$LOG_DIR/mock-server.log" 2>&1 &
149+
MOCK_PID=$!
150+
wait_for_port 5000 "mock-server" "$LOG_DIR/mock-server.log" "$MOCK_PID"
151+
152+
# Extract ports from the app's Makefile
153+
APP_PORT=$(grep -m1 "^PORT\s*=" "$APP_DIR/Makefile" | awk '{print $3}')
154+
APP_PORT_DISABLED=$(grep -m1 "^PORT_DISABLED\s*=" "$APP_DIR/Makefile" | awk '{print $3}')
155+
APP_PORT="${APP_PORT:-8080}"
156+
APP_PORT_DISABLED="${APP_PORT_DISABLED:-8081}"
157+
158+
# 3. Kill any lingering processes from previous runs
159+
AIKIDO_SOCK=$(AIKIDO_TOKEN="AIK_secret_token" poetry run python3 -c "
160+
import os; os.environ['AIKIDO_TOKEN']='AIK_secret_token'
161+
from aikido_zen.helpers.hash_aikido_token import hash_aikido_token
162+
from aikido_zen.helpers.get_temp_dir import get_temp_dir
163+
print(f'{get_temp_dir()}/aikido_python_{hash_aikido_token()}.sock')
164+
" 2>/dev/null)
165+
if [[ -n "$AIKIDO_SOCK" ]]; then
166+
rm -f "$AIKIDO_SOCK"
167+
fi
168+
for port in "$APP_PORT" "$APP_PORT_DISABLED"; do
169+
lsof -ti :"$port" 2>/dev/null | xargs kill -9 2>/dev/null || true
170+
done
171+
172+
# 4. Install sample app deps
173+
echo "Installing $APP dependencies..."
174+
cd "$SCRIPT_DIR/$APP_DIR"
175+
poetry install -q
176+
177+
# 5. Start app with firewall enabled
178+
echo "Starting $APP (firewall on)..."
179+
make run > "$LOG_DIR/app.log" 2>&1 &
180+
APP_PID=$!
181+
cd "$SCRIPT_DIR"
182+
183+
wait_for_port "$APP_PORT" "$APP (fw-on)" "$LOG_DIR/app.log" "$APP_PID"
184+
185+
# 5. Start app with firewall disabled
186+
echo "Starting $APP (firewall off)..."
187+
cd "$SCRIPT_DIR/$APP_DIR"
188+
make runZenDisabled > "$LOG_DIR/app-disabled.log" 2>&1 &
189+
APP_DISABLED_PID=$!
190+
cd "$SCRIPT_DIR"
191+
wait_for_port "$APP_PORT_DISABLED" "$APP (fw-off)" "$LOG_DIR/app-disabled.log" "$APP_DISABLED_PID"
192+
193+
# 6. Run tests
194+
echo ""
195+
echo "Running tests: $TESTFILE"
196+
echo "---"
197+
# shellcheck disable=SC2086
198+
poetry run pytest $TESTFILE -v

end2end/server/check_events_from_mock.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ def clear_events_from_mock(url):
1616
def filter_on_event_type(events, type):
1717
return [event for event in events if event["type"] == type]
1818

19-
def validate_started_event(event, stack, dry_mode=False, serverless=False, os_name="Linux", platform="CPython"):
19+
def validate_started_event(event, stack, dry_mode=False, serverless=False, os_name=None, platform="CPython"):
2020
assert event["agent"]["dryMode"] == dry_mode
2121
assert event["agent"]["library"] == "firewall-python"
2222
assert event["agent"]["nodeEnv"] == ""
23-
assert event["agent"]["os"]["name"] == os_name
23+
if os_name is not None:
24+
assert event["agent"]["os"]["name"] == os_name
2425
assert event["agent"]["platform"]["name"] == platform
2526
assert event["agent"]["serverless"] == serverless
2627
# # Check for packages is disabled until we start using them in core :

0 commit comments

Comments
 (0)