Skip to content

Commit 17ca4d4

Browse files
committed
Basic automated testing script
The goal is to run command, walk away, and come back to results. Usage: ``` ./scripts/gfxr_capture_replay_test.sh -p com.google.bigwheels.project_sample_01_triangle.debug ```
1 parent 4bd3d7f commit 17ca4d4

File tree

2 files changed

+446
-0
lines changed

2 files changed

+446
-0
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
#!/bin/bash
2+
3+
# Copyright 2025 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
# TODO should script abort trigger cleanup? what about bugreport capture?
18+
19+
set -eux
20+
21+
print_usage() {
22+
set +x
23+
echo "End-to-end capture-replay test for package"
24+
echo
25+
echo "Usage: gfxr_capture_replay_test.sh [-hcdrs] -p PACKAGE"
26+
echo
27+
echo "Required:"
28+
echo " -p PACKAGE: The app package to test"
29+
echo
30+
echo "Optional:"
31+
echo " -h: Print help"
32+
echo " -c: Wait for debugger before capturing"
33+
echo " -d: Use validation layers during capture"
34+
echo " -r: Wait for debugger before replay"
35+
echo " -s: Use validation layers during replay"
36+
echo
37+
echo "Example:"
38+
echo " gfxr_capture_replay_test.sh -p com.google.bigwheels.project_cube_xr.debug"
39+
set -x
40+
}
41+
42+
CAPTURE_DEBUG=0
43+
REPLAY_DEBUG=0
44+
REPLAY_VALIDATION=0
45+
CAPTURE_VALIDATION=0
46+
while getopts cdhp:rs getopts_flag
47+
do
48+
case "${getopts_flag}" in
49+
c) CAPTURE_DEBUG=1;;
50+
d) CAPTURE_VALIDATION=1;;
51+
h)
52+
print_usage
53+
exit 0
54+
;;
55+
p) PACKAGE="${OPTARG}";;
56+
r) REPLAY_DEBUG=1;;
57+
s) REPLAY_VALIDATION=1;;
58+
esac
59+
done
60+
61+
THIS_DIR=$(dirname "$0")
62+
. "${THIS_DIR}/test_automation/common.sh"
63+
64+
ACTIVITY="$(find_default_activity "${PACKAGE}")"
65+
66+
# Fairly reliable directory on remote device, as long as app has MANAGE_EXTERNAL_STORAGE permissions.
67+
# /data/local/tmp doesn't work on all devices tested.
68+
REMOTE_TEMP_DIR=/sdcard/Download
69+
GFXR_CAPTURE_DIR_BASENAME=gfxr_capture
70+
REPLAY_PACKAGE=com.lunarg.gfxreconstruct.replay
71+
GFXRECON=./third_party/gfxreconstruct/android/scripts/gfxrecon.py
72+
BUILD_DIR=./build
73+
GFXR_DUMP_RESOURCES="${BUILD_DIR}/gfxr_dump_resources/gfxr_dump_resources"
74+
JSON_BASENAME=dump.json
75+
DUMP_DIR="${REMOTE_TEMP_DIR}/dump"
76+
# Needs to be high enough to skip over loading screens but not too high to trip the wait_for_logcat_line timeout.
77+
CAPTURE_FRAME=720
78+
GFXR_BASENAME="${PACKAGE}_frame_${CAPTURE_FRAME}.gfxr"
79+
GFXA_BASENAME="${PACKAGE}_asset_file.gfxa"
80+
GFXR_REPLAY_APK=./install/gfxr-replay.apk
81+
RESULTS_DIR="${PACKAGE}-$(date +%Y%m%d_%H%M%S)"
82+
JSON="${RESULTS_DIR}/${JSON_BASENAME}"
83+
LOCAL_TEMP_DIR=/tmp
84+
ARCHIVE_BASENAME=android-binaries-1.4.313.0
85+
ARCHIVE_FILE=android-binaries-1.4.313.0.tar.gz
86+
VALIDATION_LAYER_DIR=${LOCAL_TEMP_DIR}/${ARCHIVE_BASENAME}
87+
VALIDATION_LAYER_LIB=libVkLayer_khronos_validation.so
88+
REMOTE_TEMP_FILEPATH="/data/local/tmp/${VALIDATION_LAYER_LIB}"
89+
ARCH=$(adb shell getprop ro.product.cpu.abi)
90+
LOCAL_VALIDATION_LAYER_FILEPATH="${VALIDATION_LAYER_DIR}/${ARCH}/${VALIDATION_LAYER_LIB}"
91+
FRAME_DELIMITER_PROP=openxr.enable_frame_delimiter
92+
93+
#
94+
# Clear anything previously set (in case the script exited prematurely)
95+
#
96+
97+
unset_gfxr_props
98+
unset_vulkan_debug_settings
99+
100+
#
101+
# Ask OpenXR runtime to emit frame debug marker
102+
#
103+
104+
# Only try if it's not set (to avoid having to root all the time)
105+
ENABLE_FRAME_DELIMITER="$(adb shell getprop ${FRAME_DELIMITER_PROP})"
106+
if [ -z "${ENABLE_FRAME_DELIMITER}" ]
107+
then
108+
adb root
109+
adb shell setprop ${FRAME_DELIMITER_PROP} true
110+
adb unroot
111+
fi
112+
113+
#
114+
# Download validation layers
115+
#
116+
117+
if [ ${REPLAY_VALIDATION} -eq 1 -o ${CAPTURE_VALIDATION} -eq 1 ]
118+
then
119+
# Download the archive and cache the result
120+
if [ ! -f "${LOCAL_TEMP_DIR}/${ARCHIVE_FILE}" ]; then
121+
$(cd "${LOCAL_TEMP_DIR}" && wget https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.tar.gz)
122+
fi
123+
# Extract the archive and cache the result
124+
if [ ! -e "${LOCAL_VALIDATION_LAYER_FILEPATH}" ]; then
125+
$(cd "${LOCAL_TEMP_DIR}" && tar xf "${ARCHIVE_FILE}")
126+
fi
127+
fi
128+
129+
mkdir -p "${RESULTS_DIR}"
130+
131+
#
132+
# 1. Install replay package for both capture layer and replay activity
133+
#
134+
135+
# Check if we need to reinstall the replay APK. First, is it installed.
136+
# TODO would be nice if we could isolate this into a function
137+
install_replay_apk=0
138+
if is_app_installed "${REPLAY_PACKAGE}"
139+
then
140+
REMOTE_REPLAY_APK_FILEPATH="$(get_app_path "${REPLAY_PACKAGE}")"
141+
# Second, do the files match.
142+
REMOTE_APK_SHA=$(adb shell sha256sum -b "${REMOTE_REPLAY_APK_FILEPATH}")
143+
LOCAL_APK_SHA=$(sha256sum "${GFXR_REPLAY_APK}" | awk '{ print $1 }')
144+
if [ "${REMOTE_APK_SHA}" != "${LOCAL_APK_SHA}" ]
145+
then
146+
adb uninstall "${REPLAY_PACKAGE}"
147+
install_replay_apk=1
148+
fi
149+
else
150+
install_replay_apk=1
151+
fi
152+
153+
if [ $install_replay_apk -eq 1 ]
154+
then
155+
python "${GFXRECON}" install-apk "${GFXR_REPLAY_APK}"
156+
# Replay with --dump-resources needs permissions to store generated BMPs
157+
adb shell appops set "${REPLAY_PACKAGE}" MANAGE_EXTERNAL_STORAGE allow
158+
fi
159+
160+
# Install the validation layer into the replay app so we can easily find it in both capture and replay
161+
if [ ${REPLAY_VALIDATION} -eq 1 -o ${CAPTURE_VALIDATION} -eq 1 ]
162+
then
163+
# run-as is probably fine since we control the replay app is built
164+
adb push "${LOCAL_VALIDATION_LAYER_FILEPATH}" "${REMOTE_TEMP_FILEPATH}"
165+
# Can't mv since REMOTE_TEMP_FILEPATH is owned by shell or root
166+
adb shell run-as "${REPLAY_PACKAGE}" cp "${REMOTE_TEMP_FILEPATH}" .
167+
adb shell rm -rf "${REMOTE_TEMP_FILEPATH}"
168+
fi
169+
170+
# TODO copy replay APK into RESULTS_DIR?
171+
172+
#
173+
# 2. Configure PACKAGE to use capture layer from replay package
174+
#
175+
176+
CAPTURE_LAYERS="VK_LAYER_LUNARG_gfxreconstruct"
177+
CAPTURE_DEBUG_LAYER_APPS="${REPLAY_PACKAGE}"
178+
if [ ${CAPTURE_VALIDATION} -eq 1 ]
179+
then
180+
# Put validation layer last otherwise we try to capture bogus objects. Also replay fails otherwise.
181+
CAPTURE_LAYERS="${CAPTURE_LAYERS}:VK_LAYER_KHRONOS_validation"
182+
CAPTURE_DEBUG_LAYER_APPS="${PACKAGE}:${CAPTURE_DEBUG_LAYER_APPS}"
183+
184+
# TODO need to use run-as until the validation layers are packed into the replay APK
185+
adb push "${LOCAL_VALIDATION_LAYER_FILEPATH}" "${REMOTE_TEMP_FILEPATH}"
186+
# Can't mv since REMOTE_TEMP_FILEPATH is owned by shell or root
187+
adb shell run-as "${PACKAGE}" cp "${REMOTE_TEMP_FILEPATH}" .
188+
adb shell rm -rf "${REMOTE_TEMP_FILEPATH}"
189+
fi
190+
191+
# Adapted from https://developer.android.com/ndk/guides/graphics/validation-layer.
192+
adb shell settings put global enable_gpu_debug_layers 1
193+
adb shell settings put global gpu_debug_app "${PACKAGE}"
194+
adb shell settings put global gpu_debug_layers "${CAPTURE_LAYERS}"
195+
# Both the capture and validation layers are in the replay APK since it's an easy place to put them.
196+
adb shell settings put global gpu_debug_layer_app "${CAPTURE_DEBUG_LAYER_APPS}"
197+
198+
#
199+
# 3. Configure GFXR behavior
200+
#
201+
202+
# See //third_party/gfxreconstruct/USAGE_android.md for more options.
203+
adb shell mkdir -p "${REMOTE_TEMP_DIR}"
204+
adb shell setprop debug.gfxrecon.capture_file "${REMOTE_TEMP_DIR}/${PACKAGE}.gfxr"
205+
# NOTE: quit_after_capture_frames doesn't work with capture_android_trigger or capture_frames :(
206+
# Prefer capture_frames over trigger since it's more friendly to scripting. It knows better when the app is ready and producing frames compared to trigger capture (where we have to guess).
207+
# adb shell setprop debug.gfxrecon.capture_trigger_frames 1
208+
# adb shell setprop debug.gfxrecon.capture_android_trigger false
209+
adb shell setprop debug.gfxrecon.capture_frames "${CAPTURE_FRAME}"
210+
adb shell setprop debug.gfxrecon.capture_use_asset_file false # XXX asset file is known to have problems
211+
# Remove timestamp from capture filename so it's more predictable
212+
adb shell setprop debug.gfxrecon.capture_file_timestamp false
213+
# Try to write all packets, even when we crash
214+
adb shell setprop debug.gfxrecon.capture_file_flush true
215+
# More logging
216+
adb shell setprop debug.gfxrecon.log_level debug
217+
# Capture layer in PACKAGE needs permissions to read capture/asset file from storage.
218+
adb shell appops set "${PACKAGE}" MANAGE_EXTERNAL_STORAGE allow
219+
220+
#
221+
# 4. Capture
222+
#
223+
224+
# Clear logcat so that we can use it to determine when capture is done
225+
adb logcat -c
226+
227+
if [ ${CAPTURE_DEBUG} -eq 1 ]
228+
then
229+
adb shell am set-debug-app -w "${PACKAGE}"
230+
fi
231+
232+
adb shell am start -S -W -n "${PACKAGE}/${ACTIVITY}"
233+
234+
# Given how long it takes to attach the debugger, etc, it is unlikely that you'll want the script to proceed.
235+
if [ ${CAPTURE_DEBUG} -eq 1 ]
236+
then
237+
exit 0
238+
fi
239+
240+
# Wait for capture to finish. Luckily, the app logs when it's done so use that as the signal to proceed.
241+
# This only works since we clear the logcat at the start of the test.
242+
if ! wait_for_logcat_line gfxrecon "Finished recording graphics API capture"
243+
then
244+
adb bugreport
245+
fi
246+
adb shell am force-stop "${PACKAGE}"
247+
248+
# Pull the GFXR/GFXA for gfxr_dump_resources
249+
adb pull "${REMOTE_TEMP_DIR}/${GFXR_BASENAME}" "${RESULTS_DIR}"
250+
# TODO asset file was disabled for testing; re-enable
251+
# adb pull "${REMOTE_TEMP_DIR}/${GFXA_BASENAME}" "${RESULTS_DIR}"
252+
253+
#
254+
# 5. Post-capture clean-up
255+
#
256+
257+
# NOTE: Don't clean up GFXA/GFXR since we can use it for replay. Saves having to push again.
258+
259+
# Next launch should not use GFXR
260+
unset_gfxr_props
261+
unset_vulkan_debug_settings
262+
263+
#
264+
# 6. Replay with dump-resources
265+
#
266+
267+
"${GFXR_DUMP_RESOURCES}" --last_draw_only "${RESULTS_DIR}/${GFXR_BASENAME}" "${JSON}"
268+
adb shell mkdir -p "${DUMP_DIR}"
269+
adb push "${JSON}" "${REMOTE_TEMP_DIR}"
270+
271+
if [ ${REPLAY_VALIDATION} -eq 1 ]
272+
then
273+
adb shell settings put global enable_gpu_debug_layers 1
274+
adb shell settings put global gpu_debug_app "${REPLAY_PACKAGE}"
275+
adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
276+
adb shell settings put global gpu_debug_layer_app "${REPLAY_PACKAGE}"
277+
fi
278+
279+
if [ ${REPLAY_DEBUG} -eq 1 ]
280+
then
281+
adb shell am set-debug-app -w "${REPLAY_PACKAGE}"
282+
fi
283+
284+
python "${GFXRECON}" replay \
285+
--dump-resources "${REMOTE_TEMP_DIR}/${JSON_BASENAME}" \
286+
--dump-resources-dir "${DUMP_DIR}" \
287+
--dump-resources-dump-all-image-subresources \
288+
--log-level debug \
289+
"${REMOTE_TEMP_DIR}/${GFXR_BASENAME}"
290+
291+
# `gfxrecon.py replay` does not wait for the app to start so. However, if it starts logging then we can assume that it has started.
292+
# This only works since we clear the logcat at the start of the test.
293+
if ! wait_for_logcat_line gfxrecon "Initializing GFXReconstruct capture layer"
294+
then
295+
adb bugreport
296+
fi
297+
# We can infer that replay is finished when the replay app process is gone.
298+
while adb shell pidof "${REPLAY_PACKAGE}"
299+
do
300+
sleep 1
301+
done
302+
if is_crash_detected
303+
then
304+
adb bugreport
305+
fi
306+
307+
# Pull the entire dump dir since it has both a meta JSON file along with the BMP of the final image.
308+
adb pull "${DUMP_DIR}" "${RESULTS_DIR}"
309+
310+
#
311+
# 7. Post-replay cleanup
312+
#
313+
314+
adb shell rm -rf "${DUMP_DIR}"
315+
adb shell rm -rf "${REMOTE_TEMP_DIR}/${GFXR_BASENAME}"
316+
adb shell rm -rf "${REMOTE_TEMP_DIR}/${GFXA_BASENAME}"
317+
adb shell rm -rf "${REMOTE_TEMP_DIR}/${JSON_BASENAME}"
318+
319+
# Next launch should not use GFXR
320+
321+
322+
#
323+
# 8. Collect results
324+
#
325+
326+
# Show logcat for diagnostic purposes. Include DEBUG in case there was a crash.
327+
adb logcat -d -s gfxrecon,DEBUG
328+
329+
# Convert BMP captures into JPG for convenience.
330+
find "${RESULTS_DIR}" -name "*.bmp" | xargs -P0 -I {} convert {} {}.jpg
331+
332+
tar czf "${RESULTS_DIR}.tgz" "${RESULTS_DIR}"
333+
echo "Results written to: ${RESULTS_DIR}"

0 commit comments

Comments
 (0)