@@ -13,6 +13,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
1313ROOT_DIR=" $SCRIPT_DIR /.."
1414META_FILE=" $ROOT_DIR /frameworks/$FRAMEWORK /meta.json"
1515CERTS_DIR=" $ROOT_DIR /certs"
16+ DATA_DIR=" $ROOT_DIR /data"
1617
1718cleanup () {
1819 docker rm -f " $CONTAINER_NAME " 2> /dev/null || true
4142 docker build -t " $IMAGE_NAME " " frameworks/$FRAMEWORK " || { echo " FAIL: Docker build failed" ; exit 1; }
4243fi
4344
44- # Run
45+ # Mount volumes based on subscribed tests
4546docker_args=(-d --name " $CONTAINER_NAME " -p " $PORT :8080" )
46- docker_args+=(-v " $ROOT_DIR /data /dataset.json:/data/dataset.json:ro" )
47+ docker_args+=(-v " $DATA_DIR /dataset.json:/data/dataset.json:ro" )
4748
48- if has_test " baseline-h2" && [ -d " $CERTS_DIR " ]; then
49+ needs_h2=false
50+ if has_test " baseline-h2" || has_test " static-h2" || has_test " baseline-h3" || has_test " static-h3" ; then
51+ needs_h2=true
52+ fi
53+
54+ if $needs_h2 && [ -d " $CERTS_DIR " ]; then
4955 docker_args+=(-p " $H2PORT :8443" -v " $CERTS_DIR :/certs:ro" )
5056fi
5157
58+ if has_test " compression" ; then
59+ docker_args+=(-v " $DATA_DIR /dataset-large.json:/data/dataset-large.json:ro" )
60+ fi
61+
62+ if has_test " static-h2" || has_test " static-h3" ; then
63+ docker_args+=(-v " $DATA_DIR /static:/data/static:ro" )
64+ fi
65+
5266docker run " ${docker_args[@]} " " $IMAGE_NAME "
5367
5468# Wait for server to start
@@ -65,6 +79,8 @@ for i in $(seq 1 30); do
6579done
6680echo " [ready] Server is up"
6781
82+ # ───── Helpers ─────
83+
6884check () {
6985 local label=" $1 "
7086 local expected_body=" $2 "
@@ -73,68 +89,109 @@ check() {
7389 response=$( curl -s -D- " $@ " )
7490 local body
7591 body=$( echo " $response " | tail -1)
76- local server_hdr
77- server_hdr=$( echo " $response " | grep -i " ^server:" || true)
7892
79- local ok=true
80-
81- if [ " $body " != " $expected_body " ]; then
93+ if [ " $body " = " $expected_body " ]; then
94+ echo " PASS [$label ]"
95+ PASS=$(( PASS + 1 ))
96+ else
8297 echo " FAIL [$label ]: expected body '$expected_body ', got '$body '"
83- ok=false
98+ FAIL= $(( FAIL + 1 ))
8499 fi
100+ }
85101
86- if [ -z " $server_hdr " ]; then
87- echo " FAIL [$label ]: missing Server header in response"
88- ok=false
102+ check_status () {
103+ local label=" $1 "
104+ local expected_status=" $2 "
105+ shift 2
106+ local http_code
107+ http_code=$( curl -s -o /dev/null -w ' %{http_code}' " $@ " )
108+
109+ if [ " $http_code " = " $expected_status " ]; then
110+ echo " PASS [$label ] (HTTP $http_code )"
111+ PASS=$(( PASS + 1 ))
112+ else
113+ echo " FAIL [$label ]: expected HTTP $expected_status , got HTTP $http_code "
114+ FAIL=$(( FAIL + 1 ))
89115 fi
116+ }
90117
91- if $ok ; then
92- echo " PASS [$label ]"
118+ check_header () {
119+ local label=" $1 "
120+ local header_name=" $2 "
121+ local expected_value=" $3 "
122+ shift 3
123+ local headers
124+ headers=$( curl -s -D- -o /dev/null " $@ " )
125+ local value
126+ value=$( echo " $headers " | grep -i " ^${header_name} :" | sed ' s/^[^:]*: *//' | tr -d ' \r' || true)
127+
128+ if [ " $value " = " $expected_value " ]; then
129+ echo " PASS [$label ] ($header_name : $value )"
93130 PASS=$(( PASS + 1 ))
94131 else
132+ echo " FAIL [$label ]: expected $header_name '$expected_value ', got '$value '"
95133 FAIL=$(( FAIL + 1 ))
96134 fi
97135}
98136
99- # baseline / limited-conn both use /baseline11
137+ wait_h2 () {
138+ echo " [wait] Waiting for HTTPS port..."
139+ for i in $( seq 1 15) ; do
140+ if curl -sk -o /dev/null " https://localhost:$H2PORT /baseline2?a=1&b=1" 2> /dev/null; then
141+ return 0
142+ fi
143+ if [ " $i " -eq 15 ]; then
144+ echo " FAIL: HTTPS port $H2PORT not responding"
145+ FAIL=$(( FAIL + 1 ))
146+ return 1
147+ fi
148+ sleep 1
149+ done
150+ }
151+
152+ # ───── Baseline (GET/POST /baseline11) ─────
153+
100154if has_test " baseline" || has_test " limited-conn" ; then
101155 echo " [test] baseline endpoints"
102156 check " GET /baseline11?a=13&b=42" " 55" \
103157 " http://localhost:$PORT /baseline11?a=13&b=42"
104158
105- check " POST /baseline11?a=13&b=42 + CL body=20" " 75" \
159+ check " POST /baseline11?a=13&b=42 body=20" " 75" \
106160 -X POST -H " Content-Type: text/plain" -d " 20" \
107161 " http://localhost:$PORT /baseline11?a=13&b=42"
108162
109- check " POST /baseline11?a=13&b=42 + chunked body=20" " 75" \
163+ check " POST /baseline11?a=13&b=42 chunked body=20" " 75" \
110164 -X POST -H " Content-Type: text/plain" -H " Transfer-Encoding: chunked" -d " 20" \
111165 " http://localhost:$PORT /baseline11?a=13&b=42"
112166
113- # Cache detection: same URL, different body — verify server reads the body
114- echo " [test] cache detection"
167+ # Anti-cheat: randomized inputs to detect hardcoded responses
168+ echo " [test] baseline anti-cheat (randomized inputs)"
169+ A1=$(( RANDOM % 900 + 100 ))
170+ B1=$(( RANDOM % 900 + 100 ))
171+ check " GET /baseline11?a=$A1 &b=$B1 (random)" " $(( A1 + B1 )) " \
172+ " http://localhost:$PORT /baseline11?a=$A1 &b=$B1 "
173+
115174 BODY1=$(( RANDOM % 900 + 100 ))
116175 BODY2=$(( RANDOM % 900 + 100 ))
117176 while [ " $BODY1 " -eq " $BODY2 " ]; do BODY2=$(( RANDOM % 900 + 100 )) ; done
118- EXPECTED1=$(( 13 + 42 + BODY1 ))
119- EXPECTED2=$(( 13 + 42 + BODY2 ))
120-
121- check " POST /baseline11?a=13&b=42 + body=$BODY1 (cache check 1)" " $EXPECTED1 " \
177+ check " POST body=$BODY1 (cache check 1)" " $(( 13 + 42 + BODY1 )) " \
122178 -X POST -H " Content-Type: text/plain" -d " $BODY1 " \
123179 " http://localhost:$PORT /baseline11?a=13&b=42"
124-
125- check " POST /baseline11?a=13&b=42 + body=$BODY2 (cache check 2)" " $EXPECTED2 " \
180+ check " POST body=$BODY2 (cache check 2)" " $(( 13 + 42 + BODY2 )) " \
126181 -X POST -H " Content-Type: text/plain" -d " $BODY2 " \
127182 " http://localhost:$PORT /baseline11?a=13&b=42"
128183fi
129184
130- # pipelined uses /pipeline
185+ # ───── Pipelined (GET /pipeline) ─────
186+
131187if has_test " pipelined" ; then
132188 echo " [test] pipelined endpoint"
133189 check " GET /pipeline" " ok" \
134190 " http://localhost:$PORT /pipeline"
135191fi
136192
137- # json uses /json
193+ # ───── JSON Processing (GET /json) ─────
194+
138195if has_test " json" ; then
139196 echo " [test] json endpoint"
140197 response=$( curl -s " http://localhost:$PORT /json" )
@@ -144,46 +201,191 @@ d = json.load(sys.stdin)
144201count = d.get('count', 0)
145202items = d.get('items', [])
146203has_total = all('total' in item for item in items) if items else False
147- print(f'{count} {has_total}')
148- " 2> /dev/null || echo " 0 False" )
204+ # Verify total is computed correctly (price * quantity, rounded to 2 decimals)
205+ correct_totals = True
206+ for item in items:
207+ expected = round(item['price'] * item['quantity'], 2)
208+ if abs(item.get('total', 0) - expected) > 0.01:
209+ correct_totals = False
210+ break
211+ print(f'{count} {has_total} {correct_totals}')
212+ " 2> /dev/null || echo " 0 False False" )
149213 json_count=$( echo " $json_result " | cut -d' ' -f1)
150214 json_total=$( echo " $json_result " | cut -d' ' -f2)
215+ json_correct=$( echo " $json_result " | cut -d' ' -f3)
151216
152- if [ " $json_count " = " 50" ] && [ " $json_total " = " True" ]; then
153- echo " PASS [GET /json]"
217+ if [ " $json_count " = " 50" ] && [ " $json_total " = " True" ] && [ " $json_correct " = " True " ] ; then
218+ echo " PASS [GET /json] (50 items, totals computed correctly) "
154219 PASS=$(( PASS + 1 ))
155220 else
156- echo " FAIL [GET /json]: count=$json_count , has_total=$json_total "
221+ echo " FAIL [GET /json]: count=$json_count , has_total=$json_total , correct_totals= $json_correct "
157222 FAIL=$(( FAIL + 1 ))
158223 fi
224+
225+ # Check Content-Type header
226+ check_header " GET /json Content-Type" " Content-Type" " application/json" \
227+ " http://localhost:$PORT /json"
159228fi
160229
161- # baseline-h2 uses /baseline2 over HTTP/2 + TLS on port 8443
230+ # ───── Upload (POST /upload) ─────
231+
232+ if has_test " upload" ; then
233+ echo " [test] upload endpoint"
234+ # Small upload: known CRC32
235+ UPLOAD_BODY=" Hello, HttpArena!"
236+ EXPECTED_CRC=$( python3 -c " import zlib; print(format(zlib.crc32(b'$UPLOAD_BODY ') & 0xFFFFFFFF, '08x'))" )
237+ check " POST /upload small body" " $EXPECTED_CRC " \
238+ -X POST -H " Content-Type: application/octet-stream" --data-binary " $UPLOAD_BODY " \
239+ " http://localhost:$PORT /upload"
240+
241+ # Anti-cheat: random body to detect hardcoded CRC
242+ RANDOM_BODY=$( head -c 64 /dev/urandom | base64 | head -c 48)
243+ EXPECTED_RANDOM_CRC=$( echo -n " $RANDOM_BODY " | python3 -c " import sys,zlib; print(format(zlib.crc32(sys.stdin.buffer.read()) & 0xFFFFFFFF, '08x'))" )
244+ ACTUAL_CRC=$( curl -s -X POST -H " Content-Type: application/octet-stream" --data-binary " $RANDOM_BODY " " http://localhost:$PORT /upload" )
245+ if [ " $ACTUAL_CRC " = " $EXPECTED_RANDOM_CRC " ]; then
246+ echo " PASS [POST /upload random body] (CRC32: $ACTUAL_CRC )"
247+ PASS=$(( PASS + 1 ))
248+ else
249+ echo " FAIL [POST /upload random body]: expected CRC '$EXPECTED_RANDOM_CRC ', got '$ACTUAL_CRC '"
250+ FAIL=$(( FAIL + 1 ))
251+ fi
252+ fi
253+
254+ # ───── Compression (GET /compression) ─────
255+
256+ if has_test " compression" ; then
257+ echo " [test] compression endpoint"
258+
259+ # Must return Content-Encoding: gzip when Accept-Encoding: gzip is sent
260+ comp_headers=$( curl -s -D- -o /dev/null -H " Accept-Encoding: gzip" " http://localhost:$PORT /compression" )
261+ comp_encoding=$( echo " $comp_headers " | grep -i " ^content-encoding:" | tr -d ' \r' | awk ' {print tolower($2)}' || true)
262+ if [ " $comp_encoding " = " gzip" ]; then
263+ echo " PASS [compression Content-Encoding: gzip]"
264+ PASS=$(( PASS + 1 ))
265+ else
266+ echo " FAIL [compression]: expected Content-Encoding gzip, got '$comp_encoding '"
267+ FAIL=$(( FAIL + 1 ))
268+ fi
269+
270+ # Verify compressed response is valid JSON with items and totals
271+ comp_response=$( curl -s --compressed -H " Accept-Encoding: gzip" " http://localhost:$PORT /compression" )
272+ comp_result=$( echo " $comp_response " | python3 -c "
273+ import sys, json
274+ d = json.load(sys.stdin)
275+ count = d.get('count', 0)
276+ items = d.get('items', [])
277+ has_total = all('total' in item for item in items) if items else False
278+ print(f'{count} {has_total}')
279+ " 2> /dev/null || echo " 0 False" )
280+ comp_count=$( echo " $comp_result " | cut -d' ' -f1)
281+ comp_total=$( echo " $comp_result " | cut -d' ' -f2)
282+
283+ if [ " $comp_count " = " 6000" ] && [ " $comp_total " = " True" ]; then
284+ echo " PASS [compression response] (6000 items with totals)"
285+ PASS=$(( PASS + 1 ))
286+ else
287+ echo " FAIL [compression response]: count=$comp_count , has_total=$comp_total "
288+ FAIL=$(( FAIL + 1 ))
289+ fi
290+
291+ # Verify compressed size is reasonable (should be well under 1MB uncompressed ~1MB)
292+ comp_size=$( curl -s -o /dev/null -w ' %{size_download}' -H " Accept-Encoding: gzip" " http://localhost:$PORT /compression" )
293+ if [ " $comp_size " -lt 500000 ]; then
294+ echo " PASS [compression size] ($comp_size bytes < 500KB)"
295+ PASS=$(( PASS + 1 ))
296+ else
297+ echo " FAIL [compression size]: $comp_size bytes — compression not effective"
298+ FAIL=$(( FAIL + 1 ))
299+ fi
300+ fi
301+
302+ # ───── Caching (GET /caching with ETag) ─────
303+
304+ if has_test " caching" ; then
305+ echo " [test] caching endpoint"
306+
307+ # Without If-None-Match: should return 200 with body "OK" and ETag header
308+ check_status " GET /caching without If-None-Match" " 200" \
309+ " http://localhost:$PORT /caching"
310+
311+ check " GET /caching body" " OK" \
312+ " http://localhost:$PORT /caching"
313+
314+ check_header " GET /caching ETag header" " ETag" ' "AOK"' \
315+ " http://localhost:$PORT /caching"
316+
317+ # With matching If-None-Match: should return 304
318+ check_status " GET /caching with matching If-None-Match" " 304" \
319+ -H ' If-None-Match: "AOK"' " http://localhost:$PORT /caching"
320+
321+ # 304 should have ETag header
322+ check_header " GET /caching 304 ETag header" " ETag" ' "AOK"' \
323+ -H ' If-None-Match: "AOK"' " http://localhost:$PORT /caching"
324+
325+ # 304 should have no body
326+ caching_304_body=$( curl -s -H ' If-None-Match: "AOK"' " http://localhost:$PORT /caching" )
327+ if [ -z " $caching_304_body " ]; then
328+ echo " PASS [GET /caching 304 no body]"
329+ PASS=$(( PASS + 1 ))
330+ else
331+ echo " FAIL [GET /caching 304 no body]: got body '$caching_304_body '"
332+ FAIL=$(( FAIL + 1 ))
333+ fi
334+
335+ # With non-matching If-None-Match: should return 200
336+ check_status " GET /caching with non-matching If-None-Match" " 200" \
337+ -H ' If-None-Match: "WRONG"' " http://localhost:$PORT /caching"
338+ fi
339+
340+ # ───── Baseline H2 (GET /baseline2 over HTTP/2 + TLS) ─────
341+
162342if has_test " baseline-h2" ; then
163343 echo " [test] baseline-h2 endpoint"
164- # Wait for H2 port
165- for i in $( seq 1 10) ; do
166- if curl -sk -o /dev/null " https://localhost:$H2PORT /baseline2?a=1&b=1" 2> /dev/null; then
167- break
168- fi
169- if [ " $i " -eq 10 ]; then
170- echo " FAIL [baseline-h2]: HTTPS port $H2PORT not responding"
344+ if wait_h2; then
345+ check " GET /baseline2?a=13&b=42 over HTTP/2" " 55" \
346+ -sk " https://localhost:$H2PORT /baseline2?a=13&b=42"
347+
348+ # Anti-cheat: randomized query params
349+ A3=$(( RANDOM % 900 + 100 ))
350+ B3=$(( RANDOM % 900 + 100 ))
351+ check " GET /baseline2?a=$A3 &b=$B3 over HTTP/2 (random)" " $(( A3 + B3 )) " \
352+ -sk " https://localhost:$H2PORT /baseline2?a=$A3 &b=$B3 "
353+ fi
354+ fi
355+
356+ # ───── Static Files H2 (GET /static/* over HTTP/2 + TLS) ─────
357+
358+ if has_test " static-h2" ; then
359+ echo " [test] static-h2 endpoint"
360+ if wait_h2; then
361+ # Check a few static files exist and return correct Content-Type
362+ check_header " GET /static/reset.css Content-Type" " Content-Type" " text/css" \
363+ -sk " https://localhost:$H2PORT /static/reset.css"
364+
365+ check_header " GET /static/app.js Content-Type" " Content-Type" " application/javascript" \
366+ -sk " https://localhost:$H2PORT /static/app.js"
367+
368+ check_header " GET /static/manifest.json Content-Type" " Content-Type" " application/json" \
369+ -sk " https://localhost:$H2PORT /static/manifest.json"
370+
371+ # Check response size is non-zero
372+ static_size=$( curl -sk -o /dev/null -w ' %{size_download}' " https://localhost:$H2PORT /static/reset.css" )
373+ if [ " $static_size " -gt 0 ]; then
374+ echo " PASS [static-h2 response size] ($static_size bytes)"
375+ PASS=$(( PASS + 1 ))
376+ else
377+ echo " FAIL [static-h2 response size]: empty response"
171378 FAIL=$(( FAIL + 1 ))
172379 fi
173- sleep 1
174- done
175380
176- check " GET /baseline2?a=13&b=42 over HTTP/2" " 55" \
177- -sk " https://localhost:$H2PORT /baseline2?a=13&b=42"
178-
179- # Cache detection: same URL, different query params
180- A3=$(( RANDOM % 900 + 100 ))
181- B3=$(( RANDOM % 900 + 100 ))
182- EXPECTED3=$(( A3 + B3 ))
183- check " GET /baseline2?a=$A3 &b=$B3 over HTTP/2 (cache check)" " $EXPECTED3 " \
184- -sk " https://localhost:$H2PORT /baseline2?a=$A3 &b=$B3 "
381+ # 404 for missing files
382+ check_status " GET /static/nonexistent.txt" " 404" \
383+ -sk " https://localhost:$H2PORT /static/nonexistent.txt"
384+ fi
185385fi
186386
387+ # ───── Summary ─────
388+
187389echo " "
188390echo " === Results: $PASS passed, $FAIL failed ==="
189391[ " $FAIL " -eq 0 ] || exit 1
0 commit comments