Skip to content

Commit 7638a4c

Browse files
authored
Add integration test suite under tests/integration/ (#17)
* add integration test suite under tests/integration/ with file-based event logging * fix integration tests: avoid client-scope variable collision and update ClientListener path * fix lifecycle ordering assertion and binary test — onFirstOpen is async, use charsetDecode for binary * add wsClient.close() / wsClient.isClose() / wsClients.close() tests * fix broadcast test var-scope, add env-var config override test + CI job * add reflection hot-reinstall test via cfadmin updateRHExtension * use cfadmin restart instead of updateRHExtension to trigger reflection fallback * disable reflection-restart CI step — blocked by extension classloader bug after cfadmin restart
1 parent efce09b commit 7638a4c

19 files changed

Lines changed: 1013 additions & 28 deletions

.github/workflows/main.yml

Lines changed: 257 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,13 @@ jobs:
167167
echo "Extensions to deploy:"
168168
ls -la lucee-express/lucee-server/deploy/
169169
170-
- name: Copy test websocket listener
170+
- name: Copy test websocket listeners
171171
run: |
172-
# The websocket extension looks for listeners in {lucee-server}/websockets/
172+
# The websocket extension looks for listeners in {lucee-server}/websockets/.
173+
# Collect listeners from every websockets/ subfolder under tests/ so both
174+
# top-level (server-only) and tests/integration/ listeners get picked up.
173175
mkdir -p lucee-express/lucee-server/context/websockets
174-
cp tests/websockets/*.cfc lucee-express/lucee-server/context/websockets/
176+
find tests -path '*/websockets/*.cfc' -exec cp {} lucee-express/lucee-server/context/websockets/ \;
175177
echo "WebSocket listeners:"
176178
ls -la lucee-express/lucee-server/context/websockets/
177179
@@ -244,23 +246,57 @@ jobs:
244246
echo "TEST_RESULT=✅ PASSED" >> $GITHUB_OUTPUT
245247
fi
246248
247-
- name: Test WebSocket connection
248-
id: test-websocket-client
249+
- name: Run integration tests
250+
id: integration-tests
249251
continue-on-error: true
250252
run: |
251-
echo "Testing WebSocket connection on Lucee ${{ matrix.lucee }}..."
252-
RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8888/tests/test-websocket-client.cfm)
253-
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
254-
BODY=$(echo "$RESPONSE" | sed '$d')
255-
echo "$BODY"
256-
# Save output for summary
257-
echo "$BODY" > /tmp/test-websocket-client.txt
258-
if [ "$HTTP_CODE" != "200" ] || echo "$BODY" | grep -q "FAILED"; then
259-
echo "CLIENT_TEST_RESULT=❌ FAILED" >> $GITHUB_OUTPUT
260-
echo "CLIENT_TEST_FAILED=true" >> $GITHUB_OUTPUT
253+
echo "Running integration tests on Lucee ${{ matrix.lucee }}..."
254+
FAILED=false
255+
RESULTS=""
256+
for test in test-websocket-client test-lifecycle-callbacks test-return-value-send test-wsclient-broadcast test-wsclients-plural test-binary-send test-close; do
257+
echo ""
258+
echo "=============================================="
259+
echo "Running ${test}"
260+
echo "=============================================="
261+
RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8888/tests/integration/${test}.cfm)
262+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
263+
BODY=$(echo "$RESPONSE" | sed '$d')
264+
echo "$BODY"
265+
echo "$BODY" > /tmp/${test}.txt
266+
if [ "$HTTP_CODE" != "200" ] || echo "$BODY" | grep -q "FAILED"; then
267+
echo "${test}: ❌ FAILED"
268+
RESULTS="${RESULTS}${test}: ❌ FAILED\n"
269+
FAILED=true
270+
else
271+
echo "${test}: ✅ PASSED"
272+
RESULTS="${RESULTS}${test}: ✅ PASSED\n"
273+
fi
274+
done
275+
echo -e "$RESULTS" > /tmp/integration-results.txt
276+
if [ "$FAILED" = "true" ]; then
277+
echo "INTEGRATION_FAILED=true" >> $GITHUB_OUTPUT
278+
echo "INTEGRATION_RESULT=❌ Some tests failed" >> $GITHUB_OUTPUT
261279
else
262-
echo "Integration test passed!"
263-
echo "CLIENT_TEST_RESULT=✅ PASSED" >> $GITHUB_OUTPUT
280+
echo "INTEGRATION_RESULT=✅ All tests passed" >> $GITHUB_OUTPUT
281+
fi
282+
283+
- name: Dump WebSocket event logs
284+
if: always()
285+
run: |
286+
# Event logs are written to the system temp dir by file-based test listeners
287+
# (see LifecycleListener.cfc). Dumped on always() so failing tests have
288+
# visible debug info in the CI log.
289+
echo "=== WebSocket Test Event Logs ==="
290+
found=false
291+
for f in /tmp/ws-*-events.log; do
292+
[ -f "$f" ] || continue
293+
found=true
294+
echo ""
295+
echo "--- $f ---"
296+
cat "$f"
297+
done
298+
if [ "$found" = "false" ]; then
299+
echo "(no event logs found — no test wrote to /tmp/ws-*-events.log)"
264300
fi
265301
266302
- name: Test idleTimeout (LDEV-6219)
@@ -292,7 +328,7 @@ jobs:
292328
echo "| Tomcat | ${{ steps.config.outputs.tomcat }} |" >> $GITHUB_STEP_SUMMARY
293329
echo "| Java | ${{ steps.config.outputs.java }} |" >> $GITHUB_STEP_SUMMARY
294330
echo "| websocketInfo() | ${{ steps.test-websocket-info.outputs.TEST_RESULT || '⚠️ Unknown' }} |" >> $GITHUB_STEP_SUMMARY
295-
echo "| WebSocket Connection | ${{ steps.test-websocket-client.outputs.CLIENT_TEST_RESULT || '⚠️ Unknown' }} |" >> $GITHUB_STEP_SUMMARY
331+
echo "| Integration Tests | ${{ steps.integration-tests.outputs.INTEGRATION_RESULT || '⚠️ Unknown' }} |" >> $GITHUB_STEP_SUMMARY
296332
echo "| idleTimeout (LDEV-6219) | ${{ steps.test-idle-timeout.outputs.IDLE_TEST_RESULT || '⚠️ Unknown' }} |" >> $GITHUB_STEP_SUMMARY
297333
298334
# Include failure details in summary
@@ -304,12 +340,21 @@ jobs:
304340
echo '```' >> $GITHUB_STEP_SUMMARY
305341
fi
306342
307-
if [ "${{ steps.test-websocket-client.outputs.CLIENT_TEST_FAILED }}" = "true" ]; then
343+
if [ "${{ steps.integration-tests.outputs.INTEGRATION_FAILED }}" = "true" ]; then
308344
echo "" >> $GITHUB_STEP_SUMMARY
309-
echo "### WebSocket Connection Test Output" >> $GITHUB_STEP_SUMMARY
345+
echo "### Integration Tests" >> $GITHUB_STEP_SUMMARY
310346
echo '```' >> $GITHUB_STEP_SUMMARY
311-
cat /tmp/test-websocket-client.txt >> $GITHUB_STEP_SUMMARY || true
347+
cat /tmp/integration-results.txt >> $GITHUB_STEP_SUMMARY || true
312348
echo '```' >> $GITHUB_STEP_SUMMARY
349+
for test in test-websocket-client test-lifecycle-callbacks test-return-value-send test-wsclient-broadcast test-wsclients-plural test-binary-send test-close; do
350+
if [ -f /tmp/${test}.txt ] && grep -q "FAILED" /tmp/${test}.txt; then
351+
echo "" >> $GITHUB_STEP_SUMMARY
352+
echo "#### ${test}" >> $GITHUB_STEP_SUMMARY
353+
echo '```' >> $GITHUB_STEP_SUMMARY
354+
cat /tmp/${test}.txt >> $GITHUB_STEP_SUMMARY || true
355+
echo '```' >> $GITHUB_STEP_SUMMARY
356+
fi
357+
done
313358
fi
314359
315360
if [ "${{ steps.test-idle-timeout.outputs.IDLE_TEST_FAILED }}" = "true" ]; then
@@ -321,7 +366,7 @@ jobs:
321366
fi
322367
323368
- name: Fail if tests failed
324-
if: steps.test-websocket-info.outputs.TEST_FAILED == 'true' || steps.test-websocket-client.outputs.CLIENT_TEST_FAILED == 'true' || steps.test-idle-timeout.outputs.IDLE_TEST_FAILED == 'true'
369+
if: steps.test-websocket-info.outputs.TEST_FAILED == 'true' || steps.integration-tests.outputs.INTEGRATION_FAILED == 'true' || steps.test-idle-timeout.outputs.IDLE_TEST_FAILED == 'true'
325370
run: exit 1
326371

327372
- name: Stop Lucee Express
@@ -345,10 +390,198 @@ jobs:
345390
lucee-express/logs/
346391
lucee-express/lucee-server/context/logs/
347392
393+
test-config-override:
394+
name: Test - Config override + reflection hot-reinstall
395+
runs-on: ubuntu-latest
396+
needs: [build-extension]
397+
env:
398+
LUCEE_LOGGING_FORCE_APPENDER: console
399+
LUCEE_LOGGING_FORCE_LEVEL: info
400+
LUCEE_WEBSOCKET_CONFIG: /tmp/ws-alt-config.json
401+
LUCEE_ADMIN_PASSWORD: testadmin
402+
steps:
403+
- name: Checkout
404+
uses: actions/checkout@v6
405+
406+
- name: Set up JDK 21
407+
uses: actions/setup-java@v5
408+
with:
409+
java-version: '21'
410+
distribution: 'temurin'
411+
412+
- name: Download extension
413+
uses: actions/download-artifact@v8
414+
with:
415+
name: extension-lex
416+
path: extension
417+
418+
- name: Download Lucee Express template (tomcat-11)
419+
run: |
420+
EXPRESS_URL=$(curl -s https://update.lucee.org/rest/update/provider/expressTemplates | jq -r '.["tomcat-11"]')
421+
curl -L -o express-template.zip "$EXPRESS_URL"
422+
unzip -q express-template.zip -d lucee-express
423+
424+
- name: Download Lucee JAR (7.0 snapshot)
425+
id: lucee-jar
426+
run: |
427+
LUCEE_URL=$(curl -s "https://update.lucee.org/rest/update/provider/latest/7.0/snapshot/jar/url" | tr -d '"')
428+
LUCEE_VERSION=$(basename "$LUCEE_URL" | sed 's/lucee-//;s/\.jar//')
429+
echo "LUCEE_VERSION=$LUCEE_VERSION" >> $GITHUB_OUTPUT
430+
curl -L -f -o lucee.jar "$LUCEE_URL"
431+
rm -f lucee-express/lib/lucee-*.jar
432+
cp lucee.jar lucee-express/lib/
433+
434+
- name: Download websocket-client extension
435+
run: curl -L -o websocket-client.lex "https://ext.lucee.org/websocket-client-extension-2.3.0.9-SNAPSHOT.lex"
436+
437+
- name: Install extensions into Express
438+
run: |
439+
mkdir -p lucee-express/lucee-server/deploy
440+
cp extension/*.lex lucee-express/lucee-server/deploy/
441+
cp websocket-client.lex lucee-express/lucee-server/deploy/
442+
443+
- name: Set up ALT config + listener path (no default listeners copied)
444+
run: |
445+
# Alt config file — the LUCEE_WEBSOCKET_CONFIG env var points the extension here
446+
echo '{"directory":"/tmp/ws-alt-listeners/","requestTimeout":50,"idleTimeout":300}' > /tmp/ws-alt-config.json
447+
# Alt listener directory — extension will look here (not in default {lucee-config}/websockets/)
448+
mkdir -p /tmp/ws-alt-listeners
449+
cp tests/websockets/TestListener.cfc /tmp/ws-alt-listeners/
450+
echo "Alt config:"; cat /tmp/ws-alt-config.json
451+
echo "Alt listeners:"; ls -la /tmp/ws-alt-listeners/
452+
453+
- name: Copy tests to webroot
454+
run: cp -r tests lucee-express/webapps/ROOT/
455+
456+
- name: Configure Tomcat port
457+
run: sed -i 's/port="8080"/port="8888"/g' lucee-express/conf/server.xml
458+
459+
- name: Resolve built .lex absolute path
460+
run: |
461+
LEX_FILE=$(ls $GITHUB_WORKSPACE/extension/*.lex | head -1)
462+
echo "WS_EXT_LEX_PATH=$LEX_FILE" >> $GITHUB_ENV
463+
echo "Resolved .lex to: $LEX_FILE"
464+
465+
- name: Start Lucee Express (with LUCEE_WEBSOCKET_CONFIG + LUCEE_ADMIN_PASSWORD in env)
466+
run: cd lucee-express && ./bin/catalina.sh start
467+
468+
- name: Wait for server
469+
run: |
470+
for i in {1..60}; do
471+
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8888/ | grep -q "200\|302\|404"; then
472+
echo "HTTP ready after $i seconds"; break
473+
fi
474+
sleep 1
475+
done
476+
477+
- name: Run config-override test
478+
id: config-override-test
479+
continue-on-error: true
480+
run: |
481+
RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8888/tests/integration/test-config-override.cfm)
482+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
483+
BODY=$(echo "$RESPONSE" | sed '$d')
484+
echo "$BODY"
485+
if [ "$HTTP_CODE" != "200" ] || echo "$BODY" | grep -q "FAILED"; then
486+
echo "CONFIG_OVERRIDE_FAILED=true" >> $GITHUB_OUTPUT
487+
fi
488+
489+
# ------------------------------------------------------------------
490+
# Reflection / Lucee-restart scenario — TEMPORARILY DISABLED.
491+
#
492+
# The post-restart path hits a real bug: after cfadmin action="restart",
493+
# the new CFMLEngine's extension classloader can't load
494+
# JakartaWebSocketEndpoint.class — `WebSocketEndpointFactory.scanWebContexts`
495+
# and `registerEndpoint` both fail with "unable to load class path".
496+
# The reflection fallback never gets a chance to patch the old static slot
497+
# because the new classes themselves aren't loadable.
498+
#
499+
# The test CFMs (trigger-lucee-restart.cfm, test-reflection-restart.cfm)
500+
# and this workflow block are kept in place for when the underlying bug
501+
# is fixed — just re-enable the steps below.
502+
#
503+
# See LDEV-6221 and the companion catalina.out output in the earlier CI
504+
# run logs for the stack trace.
505+
# ------------------------------------------------------------------
506+
# - name: Trigger Lucee engine restart (LDEV-6221 scenario)
507+
# id: restart-trigger
508+
# continue-on-error: true
509+
# run: |
510+
# RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8888/tests/integration/trigger-lucee-restart.cfm)
511+
# HTTP_CODE=$(echo "$RESPONSE" | tail -1)
512+
# BODY=$(echo "$RESPONSE" | sed '$d')
513+
# echo "$BODY"
514+
# if [ "$HTTP_CODE" != "200" ] || echo "$BODY" | grep -q "FAILED"; then
515+
# echo "RESTART_TRIGGER_FAILED=true" >> $GITHUB_OUTPUT
516+
# fi
517+
#
518+
# - name: Wait for Lucee to come back online
519+
# run: |
520+
# for i in {1..60}; do
521+
# if curl -s -o /dev/null -w "%{http_code}" http://localhost:8888/tests/test-websocket-info.cfm | grep -q "200"; then
522+
# echo "Lucee responding after $i attempts"
523+
# break
524+
# fi
525+
# sleep 1
526+
# done
527+
#
528+
# - name: Run post-restart reflection round-trip
529+
# id: reflection-test
530+
# continue-on-error: true
531+
# run: |
532+
# RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8888/tests/integration/test-reflection-restart.cfm)
533+
# HTTP_CODE=$(echo "$RESPONSE" | tail -1)
534+
# BODY=$(echo "$RESPONSE" | sed '$d')
535+
# echo "$BODY"
536+
# if [ "$HTTP_CODE" != "200" ] || echo "$BODY" | grep -q "FAILED"; then
537+
# echo "REFLECTION_FAILED=true" >> $GITHUB_OUTPUT
538+
# fi
539+
#
540+
# - name: Verify reflection warning in catalina.out
541+
# id: reflection-log-check
542+
# continue-on-error: true
543+
# run: |
544+
# if grep -q "calling \[.*\] via reflection" lucee-express/logs/catalina.out; then
545+
# echo "Reflection warning found — hot re-install exercised the fallback path"
546+
# grep "calling \[.*\] via reflection" lucee-express/logs/catalina.out
547+
# else
548+
# echo "REFLECTION_LOG_MISSING=true" >> $GITHUB_OUTPUT
549+
# echo "Reflection warning NOT found in catalina.out — hot re-install did not hit the fallback path"
550+
# fi
551+
552+
- name: Dump WebSocket event logs
553+
if: always()
554+
run: |
555+
for f in /tmp/ws-*-events.log; do
556+
[ -f "$f" ] || continue
557+
echo "--- $f ---"; cat "$f"
558+
done || echo "(no event logs found)"
559+
560+
- name: Stop Lucee Express
561+
if: always()
562+
run: cd lucee-express && ./bin/shutdown.sh || true
563+
564+
- name: Show catalina.out
565+
if: always()
566+
run: cat lucee-express/logs/catalina.out || echo "No catalina.out found"
567+
568+
- name: Upload logs
569+
if: always()
570+
uses: actions/upload-artifact@v7
571+
with:
572+
name: lucee-logs-config-override-${{ steps.lucee-jar.outputs.LUCEE_VERSION }}
573+
path: |
574+
lucee-express/logs/
575+
lucee-express/lucee-server/context/logs/
576+
577+
- name: Fail if any sub-test failed
578+
if: steps.config-override-test.outputs.CONFIG_OVERRIDE_FAILED == 'true'
579+
run: exit 1
580+
348581
deploy:
349582
runs-on: ubuntu-latest
350-
needs: [build-extension, test]
351-
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.test.result == 'success' && !inputs.dry-run
583+
needs: [build-extension, test, test-config-override]
584+
if: github.event_name == 'workflow_dispatch' && needs.test.result == 'success' && needs.test-config-override.result == 'success' && !inputs.dry-run
352585
steps:
353586
- name: Checkout repository
354587
uses: actions/checkout@v6
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
component hint="Client-side listener that captures both text and binary messages" {
2+
3+
variables.received = [];
4+
variables.receivedBinary = [];
5+
6+
function onMessage( message ) {
7+
arrayAppend( variables.received, arguments.message );
8+
}
9+
10+
function onBinaryMessage( binary ) {
11+
arrayAppend( variables.receivedBinary, arguments.binary );
12+
}
13+
14+
function onClose() {}
15+
16+
function onError( type, cause ) {
17+
systemOutput( "WebSocket client error: #arguments.type# - #arguments.cause.getMessage()#", true );
18+
}
19+
20+
array function getMessages() {
21+
return variables.received;
22+
}
23+
24+
array function getBinaryMessages() {
25+
return variables.receivedBinary;
26+
}
27+
28+
}
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
component {
22

33
variables.received = [];
4+
variables.closed = false;
45

56
function onMessage( message ) {
67
arrayAppend( variables.received, message );
78
}
89

9-
function onClose() {}
10+
function onClose() {
11+
variables.closed = true;
12+
}
1013

1114
function onError( type, cause ) {
1215
systemOutput( "WebSocket client error: #type# - #cause.getMessage()#", true );
@@ -16,4 +19,8 @@ component {
1619
return variables.received;
1720
}
1821

22+
boolean function isClosed() {
23+
return variables.closed;
24+
}
25+
1926
}

0 commit comments

Comments
 (0)