@@ -18,89 +18,109 @@ if [ "$PORT" == "" ]; then
1818 PORT=9000
1919fi
2020
21+ # Local developer speedups, both off in CI (GITHUB_ACTION is set by Actions).
22+ # EXTENDER_DEV_CACHE=1 keep the downloaded Defold SDKs in a named volume between runs
23+ # EXTENDER_KEEP_STACK=1 leave the containers running afterwards (see stop-test-server.sh)
24+ if [ " $GITHUB_ACTION " != " " ]; then
25+ EXTENDER_DEV_CACHE=0
26+ fi
27+ if [ " $EXTENDER_DEV_CACHE " == " " ]; then
28+ EXTENDER_DEV_CACHE=1
29+ fi
30+ if [ " $EXTENDER_KEEP_STACK " == " " ]; then
31+ EXTENDER_KEEP_STACK=0
32+ fi
33+
2134echo " Using RUN_ENV: ${RUN_ENV} "
2235echo " Using compose profile: ${COMPOSE_PROFILE} "
2336echo " Start application: ${APPLICATION} "
2437echo " Using PORT: ${PORT} "
2538
2639URL=http://localhost:${PORT}
2740
41+ COMPOSE_FILES=(-f " ${DIR} /../docker/docker-compose.yml" )
42+ if [ " $EXTENDER_DEV_CACHE " == " 1" ]; then
43+ # The volume is external so that the 'extender-test' and 'extender-test-auth'
44+ # projects share one SDK cache instead of getting a project-prefixed copy each.
45+ docker volume create extender-sdk-cache > /dev/null
46+ COMPOSE_FILES+=(-f " ${DIR} /../docker/docker-compose.dev-cache.yml" )
47+ fi
48+
49+ # COMPOSE_PROFILE may name several profiles, e.g. "test-linux test-web"
50+ PROFILE_ARGS=()
51+ for profile in $COMPOSE_PROFILE ; do
52+ PROFILE_ARGS+=(--profile " $profile " )
53+ done
54+
55+ COMPOSE=(docker compose -p " $APPLICATION " " ${COMPOSE_FILES[@]} " " ${PROFILE_ARGS[@]} " )
56+
2857function check_server() {
2958 if curl -s --head --request GET ${URL} | grep " 200 OK" > /dev/null; then
3059 echo " ERROR: ${URL} is already occupied!"
3160 exit 1
3261 fi
3362}
3463
35- # fail early
36- check_server
64+ # A container keeps running whatever extender.jar it loaded at startup, so a stack we
65+ # are reusing must be recreated once the jar is rebuilt - otherwise the tests would
66+ # silently exercise stale code.
67+ function stack_is_current() {
68+ local jar=" ${DIR} /../app/extender.jar"
69+ [ -f " $jar " ] || return 0
70+
71+ local ids
72+ ids=$( " ${COMPOSE[@]} " ps -q 2> /dev/null)
73+ [ -n " $ids " ] || return 1
74+
75+ local jar_mtime
76+ # GNU stat must come first: BSD-style "stat -f %m" fails on GNU but only after printing
77+ # the whole filesystem status to stdout, which would corrupt jar_mtime. "stat -c" on BSD
78+ # fails cleanly with nothing on stdout.
79+ jar_mtime=$( stat -c %Y " $jar " 2> /dev/null || stat -f %m " $jar " )
80+
81+ local started started_epoch
82+ for id in $ids ; do
83+ # StartedAt is UTC (e.g. 2026-07-14T22:40:26.4Z); parse it as UTC on both BSD and GNU date.
84+ started=$( docker inspect -f ' {{.State.StartedAt}}' " $id " )
85+ started_epoch=$( date -u -j -f " %Y-%m-%dT%H:%M:%S" " ${started: 0: 19} " +%s 2> /dev/null \
86+ || date -u -d " $started " +%s)
87+ if [ " $jar_mtime " -gt " $started_epoch " ]; then
88+ return 1
89+ fi
90+ done
91+ return 0
92+ }
93+
94+ # fail early - but a stack we intend to reuse is expected to answer on ${URL}
95+ if [ " $EXTENDER_KEEP_STACK " != " 1" ]; then
96+ check_server
97+ fi
3798
3899# For CI to be able to work with the test files
39100if [ " $GITHUB_ACTION " != " " ]; then
40101 chmod -R a+xrw ${DIR} /../test-data || true
41102fi
42103
43- docker compose -p $APPLICATION -f ${DIR} /../docker/docker-compose.yml --profile $COMPOSE_PROFILE up -d
44-
45- # Retry configuration
46- max_retries=10
47- retry_interval=15
48-
49- check_containers_health () {
50- all_healthy=true
51-
52- for container in $( docker ps -q) ; do
53- health_field=$( docker inspect --format=' {{.State.Health}}' " $container " )
54- if [ " $health_field " == " <nil>" ]; then
55- echo " Container has no health status. Skipped."
56- continue
57- fi
58- name=$( docker inspect --format=' {{.Name}}' " $container " | sed ' s/\///' )
59- app_name=$( docker inspect " $container " | jq -r ' .[0].Config.Labels["com.docker.compose.project"]' )
60- health_status=$( docker inspect --format=' {{.State.Health.Status}}' " $container " )
61-
62- if [ " $app_name " != " $APPLICATION " ]; then
63- echo " Skip health check of unrelated container."
64- fi
65-
66- # If health status is empty, container doesn't have a health check defined
67- if [ -z " $health_status " ]; then
68- health_status=" no health check"
69- continue
70- fi
71-
72- echo " $name : $health_status "
73-
74- # Check if the container is not healthy
75- if [ " $health_status " != " healthy" ]; then
76- all_healthy=false
77- fi
78- done
79-
80- # Return whether all containers are healthy
81- $all_healthy && return 0 || return 1
82- }
104+ UP_ARGS=(up -d --wait --wait-timeout 300)
105+ if [ " $EXTENDER_KEEP_STACK " == " 1" ] && ! stack_is_current; then
106+ echo " extender.jar is newer than the running containers - recreating them"
107+ UP_ARGS+=(--force-recreate)
108+ fi
83109
84- # Main loop to retry until all containers are healthy or retries run out
85- for (( i= 1 ; i<= $max_retries ; i++ )) ; do
86- echo " Attempt $i of $max_retries : Checking container health..."
87-
88- if check_containers_health; then
89- echo " All containers are healthy!"
90- CREATED_SERVICES=$( docker compose -p $APPLICATION ps -a --services)
91- for service in $CREATED_SERVICES ; do
92- echo " Copy test sdk to $service "
93- docker compose -p $APPLICATION cp " ${DIR} /../test-data/sdk/a" " $service :/var/extender/sdk"
94- docker compose -p $APPLICATION exec -u root $service chown -R extender:extender /var/extender/sdk/a
95- done
96-
97- exit 0
98- else
99- echo " Some containers are not healthy yet. Retrying in $retry_interval seconds..."
100- sleep $retry_interval
101- fi
110+ " ${COMPOSE[@]} " " ${UP_ARGS[@]} "
111+
112+ for service in $( " ${COMPOSE[@]} " ps --services) ; do
113+ # base-env creates /var/extender and /var/extender/cache but not /var/extender/sdk,
114+ # so with EXTENDER_DEV_CACHE the volume mounts root-owned and the extender user
115+ # (uid 2222) cannot write to it. Harmless when there is no volume.
116+ " ${COMPOSE[@]} " exec -T -u root " $service " mkdir -p /var/extender/sdk
117+ " ${COMPOSE[@]} " exec -T -u root " $service " chown extender:extender /var/extender/sdk
118+
119+ # "a" is the version-controlled test sdk. It survives in the cache volume, and both
120+ # evictCache() and destroy() in DefoldSdkService deliberately exempt it.
121+ if ! " ${COMPOSE[@]} " exec -T " $service " test -d /var/extender/sdk/a; then
122+ echo " Copy test sdk to $service "
123+ " ${COMPOSE[@]} " cp " ${DIR} /../test-data/sdk/a" " $service :/var/extender/sdk"
124+ " ${COMPOSE[@]} " exec -T -u root " $service " chown -R extender:extender /var/extender/sdk/a
125+ fi
102126done
103-
104- # If we reach this point, some containers did not become healthy within the retry limit
105- echo " Some containers did not become healthy after $max_retries retries."
106- exit 1
0 commit comments