[Fix] (ci): harden Android trace replay CI

This commit is contained in:
2026-06-21 00:07:13 +08:00
parent b4f9401395
commit f270988e03
2 changed files with 75 additions and 16 deletions
@@ -4,7 +4,9 @@
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include <jni.h> #include <jni.h>
#include <exception>
#include <string> #include <string>
#include <sys/stat.h>
extern "C" void mobilegl_trace_set_native_window(ANativeWindow *window); extern "C" void mobilegl_trace_set_native_window(ANativeWindow *window);
extern "C" void mobilegl_trace_set_requested_size(int width, int height); extern "C" void mobilegl_trace_set_requested_size(int width, int height);
@@ -46,6 +48,35 @@ jobject MakeResult(JNIEnv* env, const mobilegl_trace::Result& result) {
return object; return object;
} }
void EnsureDirectory(const std::string& path) {
if (!path.empty()) {
mkdir(path.c_str(), 0775);
}
}
mobilegl_trace::Result MakeFailureResult(const mobilegl_trace::Request& request,
int statusCode,
const std::string& message) {
mobilegl_trace::Result result;
result.statusCode = statusCode;
result.message = message;
if (!request.outputDir.empty()) {
EnsureDirectory(request.outputDir);
result.resultPath = request.outputDir + "/result.json";
result.actualPath = request.outputDir + "/actual.png";
}
result.diffPath = request.diffPath;
return result;
}
class ScopedTraceReplayState {
public:
~ScopedTraceReplayState() {
mobilegl_trace_set_native_window(nullptr);
mobilegl_trace_set_requested_size(0, 0);
}
};
} // namespace } // namespace
extern "C" JNIEXPORT jobject JNICALL extern "C" JNIEXPORT jobject JNICALL
@@ -89,6 +120,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv*
request.cropHeight = cropHeight; request.cropHeight = cropHeight;
request.fuzzPercent = fuzzPercent; request.fuzzPercent = fuzzPercent;
ScopedTraceReplayState replayState;
mobilegl_trace_set_requested_size(request.width, request.height); mobilegl_trace_set_requested_size(request.width, request.height);
const bool needsNativeWindow = request.backend == "DirectVulkan"; const bool needsNativeWindow = request.backend == "DirectVulkan";
ANativeWindow *window = needsNativeWindow && surface != nullptr ? ANativeWindow_fromSurface(env, surface) : nullptr; ANativeWindow *window = needsNativeWindow && surface != nullptr ? ANativeWindow_fromSurface(env, surface) : nullptr;
@@ -97,9 +129,19 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv*
ANativeWindow_release(window); ANativeWindow_release(window);
} }
mobilegl_trace::Result result = mobilegl_trace::RunTraceReplay(request); mobilegl_trace::Result result;
mobilegl_trace_set_native_window(nullptr); try {
mobilegl_trace_set_requested_size(0, 0); result = mobilegl_trace::RunTraceReplay(request);
} catch (const std::exception& exception) {
result = MakeFailureResult(request,
mobilegl_trace::STATUS_RETRACE_FAILED,
"trace replay failed with unhandled native exception: " +
std::string(exception.what()));
} catch (...) {
result = MakeFailureResult(request,
mobilegl_trace::STATUS_RETRACE_FAILED,
"trace replay failed with unknown native exception");
}
if (!result.resultPath.empty()) { if (!result.resultPath.empty()) {
mobilegl_trace::WriteResultJson(request, result); mobilegl_trace::WriteResultJson(request, result);
} }
+30 -13
View File
@@ -136,6 +136,17 @@ if [ -n "${alternate_golden_path}" ]; then
fi fi
safe_case="$(printf '%s' "${case_name}" | sed 's/[^A-Za-z0-9._-]/_/g')" safe_case="$(printf '%s' "${case_name}" | sed 's/[^A-Za-z0-9._-]/_/g')"
app_dir="/data/user/0/${package_name}/files/trace-replay"
collect_run_diagnostics() {
diagnostics_dir="$1"
mkdir -p "${diagnostics_dir}"
"${ADB}" logcat -d -t 2000 > "${diagnostics_dir}/logcat.txt" || true
"${ADB}" shell pidof "${package_name}" > "${diagnostics_dir}/pidof.txt" 2>&1 || true
"${ADB}" shell dumpsys activity activities > "${diagnostics_dir}/activity.txt" 2>&1 || true
"${ADB}" shell run-as "${package_name}" ls -laR "${app_dir}" > "${diagnostics_dir}/app-files.txt" 2>&1 || true
"${ADB}" exec-out run-as "${package_name}" cat "${app_dir}/output/retrace.log" > "${diagnostics_dir}/retrace.log" || true
}
prepare_fixture() { prepare_fixture() {
fixture_dir="${fixture_root}/${safe_case}" fixture_dir="${fixture_root}/${safe_case}"
@@ -162,17 +173,16 @@ copy_fixture_to_app() {
golden_tmp="/data/local/tmp/mobilegl-${safe_case}.golden.png" golden_tmp="/data/local/tmp/mobilegl-${safe_case}.golden.png"
alternate_golden_tmp="/data/local/tmp/mobilegl-${safe_case}.alternate-golden.png" alternate_golden_tmp="/data/local/tmp/mobilegl-${safe_case}.alternate-golden.png"
"${ADB}" shell run-as "${package_name}" rm -rf files/trace-replay "${ADB}" shell run-as "${package_name}" rm -rf "${app_dir}"
"${ADB}" shell run-as "${package_name}" mkdir -p files/trace-replay/input files/trace-replay/output "${ADB}" shell run-as "${package_name}" mkdir -p "${app_dir}/input" "${app_dir}/output"
"${ADB}" shell run-as "${package_name}" cp "${trace_tmp}" files/trace-replay/input/trace.trace "${ADB}" shell run-as "${package_name}" cp "${trace_tmp}" "${app_dir}/input/trace.trace"
"${ADB}" shell run-as "${package_name}" cp "${golden_tmp}" files/trace-replay/input/golden.png "${ADB}" shell run-as "${package_name}" cp "${golden_tmp}" "${app_dir}/input/golden.png"
if [ -n "${alternate_golden_path}" ]; then if [ -n "${alternate_golden_path}" ]; then
"${ADB}" shell run-as "${package_name}" cp "${alternate_golden_tmp}" files/trace-replay/input/alternate-golden.png "${ADB}" shell run-as "${package_name}" cp "${alternate_golden_tmp}" "${app_dir}/input/alternate-golden.png"
fi fi
} }
run_retrace() { run_retrace() {
app_dir="/data/user/0/${package_name}/files/trace-replay"
result_dir="${result_root}/${safe_case}-${backend}" result_dir="${result_root}/${safe_case}-${backend}"
alternate_golden_app_path="" alternate_golden_app_path=""
if [ -n "${alternate_golden_path}" ]; then if [ -n "${alternate_golden_path}" ]; then
@@ -207,19 +217,26 @@ run_retrace() {
"${ADB}" shell "$@" "${ADB}" shell "$@"
for _ in $(seq 1 "${timeout_seconds}"); do for _ in $(seq 1 "${timeout_seconds}"); do
if "${ADB}" shell run-as "${package_name}" ls files/trace-replay/output/result.json >/dev/null 2>&1; then if "${ADB}" shell run-as "${package_name}" ls "${app_dir}/output/result.json" >/dev/null 2>&1; then
break break
fi fi
sleep 1 sleep 1
done done
"${ADB}" logcat -d -t 1000 > "${result_dir}/logcat.txt" || true collect_run_diagnostics "${result_dir}"
"${ADB}" shell run-as "${package_name}" ls files/trace-replay/output/result.json if ! "${ADB}" shell run-as "${package_name}" ls "${app_dir}/output/result.json" >/dev/null 2>&1; then
"${ADB}" exec-out run-as "${package_name}" cat files/trace-replay/output/result.json > "${result_dir}/result.json" echo "trace-replay-ci.sh: result.json was not created after ${timeout_seconds}s" >&2
echo "trace-replay-ci.sh: recent relevant logcat lines:" >&2
grep -E 'MobileGLTraceRunner|AndroidRuntime|FATAL EXCEPTION|trace_replay|MobileGL|libc' "${result_dir}/logcat.txt" | tail -200 >&2 || true
echo "trace-replay-ci.sh: app trace-replay files:" >&2
cat "${result_dir}/app-files.txt" >&2 || true
exit 1
fi
"${ADB}" exec-out run-as "${package_name}" cat "${app_dir}/output/result.json" > "${result_dir}/result.json"
cat "${result_dir}/result.json" cat "${result_dir}/result.json"
"${ADB}" exec-out run-as "${package_name}" cat files/trace-replay/output/actual.png > "${result_dir}/${safe_case}-${backend}-actual.png" "${ADB}" exec-out run-as "${package_name}" cat "${app_dir}/output/actual.png" > "${result_dir}/${safe_case}-${backend}-actual.png"
"${ADB}" exec-out run-as "${package_name}" cat "files/trace-replay/output/${safe_case}-diff.png" > "${result_dir}/${safe_case}-${backend}-diff.png" "${ADB}" exec-out run-as "${package_name}" cat "${app_dir}/output/${safe_case}-diff.png" > "${result_dir}/${safe_case}-${backend}-diff.png"
"${ADB}" exec-out run-as "${package_name}" cat files/trace-replay/output/retrace.log > "${result_dir}/retrace.log" || true "${ADB}" exec-out run-as "${package_name}" cat "${app_dir}/output/retrace.log" > "${result_dir}/retrace.log" || true
"${PYTHON}" -c 'import json, sys; result = json.load(open(sys.argv[1], encoding="utf-8")); sys.exit(0 if result.get("passed") else f"trace replay failed: {result}")' "${result_dir}/result.json" "${PYTHON}" -c 'import json, sys; result = json.load(open(sys.argv[1], encoding="utf-8")); sys.exit(0 if result.get("passed") else f"trace replay failed: {result}")' "${result_dir}/result.json"
} }