From 47224d43f36871b323fa434c39cc6a7459396c1b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:15:10 +0200 Subject: [PATCH 1/4] Capture CN1SS error hooks and avoid unsupported charset --- scripts/android/tests/Cn1ssChunkTools.java | 2 +- scripts/device-runner-app/tests/BaseTest.java | 19 +++ .../tests/Cn1ssDeviceRunner.java | 5 + .../tests/Cn1ssDeviceRunnerHelper.java | 122 ++++++++++++++++++ .../tests/MediaPlaybackScreenshotTest.java | 6 + scripts/lib/cn1ss.sh | 11 ++ scripts/run-android-instrumentation-tests.sh | 54 ++++++-- 7 files changed, 205 insertions(+), 14 deletions(-) diff --git a/scripts/android/tests/Cn1ssChunkTools.java b/scripts/android/tests/Cn1ssChunkTools.java index 7fd03c0ab8..8a6491b7ee 100644 --- a/scripts/android/tests/Cn1ssChunkTools.java +++ b/scripts/android/tests/Cn1ssChunkTools.java @@ -129,7 +129,7 @@ private static void runTests(String[] args) throws IOException { } Path path = Path.of(args[0]); List names = new ArrayList<>(); - for (Chunk chunk : iterateChunks(path, Optional.empty(), Optional.of(DEFAULT_CHANNEL))) { + for (Chunk chunk : iterateChunks(path, Optional.empty(), Optional.empty())) { if (!names.contains(chunk.testName)) { names.add(chunk.testName); } diff --git a/scripts/device-runner-app/tests/BaseTest.java b/scripts/device-runner-app/tests/BaseTest.java index d8e4b76ca9..e9d8c90d70 100644 --- a/scripts/device-runner-app/tests/BaseTest.java +++ b/scripts/device-runner-app/tests/BaseTest.java @@ -1,5 +1,6 @@ package com.codenameone.examples.hellocodenameone.tests; +import com.codename1.io.Log; import com.codename1.testing.AbstractTest; import com.codename1.ui.Form; import com.codename1.ui.util.UITimer; @@ -8,13 +9,23 @@ public abstract class BaseTest extends AbstractTest { private boolean done; + private boolean logEmitted; + private String currentScreenshotName = "default"; protected Form createForm(String title, Layout layout, final String imageName) { + done = false; + currentScreenshotName = Cn1ssDeviceRunnerHelper.sanitizeTestName(imageName); + logEmitted = false; + Cn1ssDeviceRunnerHelper.ensureGlobalErrorTaps(); + Cn1ssDeviceRunnerHelper.resetLogCapture(currentScreenshotName); + Cn1ssDeviceRunnerHelper.emitTestStartMarker(currentScreenshotName); return new Form(title, layout) { @Override protected void onShowCompleted() { + Log.p("CN1SS: form ready for screenshot -> " + imageName); registerReadyCallback(this, () -> { Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot(imageName); + logEmitted = true; done = true; }); } @@ -32,11 +43,19 @@ protected boolean waitForDone() { TestUtils.waitFor(20); timeout--; if(timeout == 0) { + Log.p("CN1SS: timeout waiting for screenshot emission"); + Cn1ssDeviceRunnerHelper.emitLogChannel(currentScreenshotName); + logEmitted = true; return false; } } // give the test a few additional milliseconds for the screenshot emission TestUtils.waitFor(100); + Log.p("CN1SS: screenshot emission completed"); + if (!logEmitted) { + Cn1ssDeviceRunnerHelper.emitLogChannel(currentScreenshotName); + logEmitted = true; + } return true; } } \ No newline at end of file diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java index e68dcff811..09930fd171 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java @@ -2,6 +2,7 @@ import com.codename1.testing.DeviceRunner; import com.codename1.testing.TestReporting; +import com.codename1.io.Log; import com.codename1.ui.Display; import com.codename1.ui.Form; import com.codename1.testing.AbstractTest; @@ -18,18 +19,22 @@ public final class Cn1ssDeviceRunner extends DeviceRunner { }; public void runSuite() { + Log.p("CN1SS: starting device runner suite with " + TEST_CLASSES.length + " tests"); for (AbstractTest testClass : TEST_CLASSES) { log("CN1SS:INFO:suite starting test=" + testClass); try { + Log.p("CN1SS: preparing test " + testClass); testClass.prepare(); testClass.runTest(); testClass.cleanup(); + Log.p("CN1SS: finished test " + testClass); log("CN1SS:INFO:suite finished test=" + testClass); } catch (Throwable t) { log("CN1SS:ERR:suite test=" + testClass + " failed=" + t); t.printStackTrace(); } } + Log.p("CN1SS: device runner suite complete"); log("CN1SS:SUITE:FINISHED"); TestReporting.getInstance().testExecutionFinished(getClass().getName()); } diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java index 409ce0f1fc..564be9ec6a 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java @@ -1,5 +1,6 @@ package com.codenameone.examples.hellocodenameone.tests; +import com.codename1.io.FileSystemStorage; import com.codename1.io.Util; import com.codename1.io.Log; import com.codename1.ui.Display; @@ -10,16 +11,54 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; final class Cn1ssDeviceRunnerHelper { private static final int CHUNK_SIZE = 900; private static final int MAX_PREVIEW_BYTES = 20 * 1024; private static final String PREVIEW_CHANNEL = "PREVIEW"; + private static final String LOG_CHANNEL = "LOG"; + private static final String START_CHANNEL = "START"; + private static final String LOG_FILE_NAME = "cn1ss-device-runner.log"; private static final int[] PREVIEW_QUALITIES = new int[] {60, 50, 40, 35, 30, 25, 20, 18, 16, 14, 12, 10, 8, 6, 5, 4, 3, 2, 1}; + private static final String LOG_FILE_PATH = initializeLogFile(); + private static boolean globalErrorHooksInstalled; private Cn1ssDeviceRunnerHelper() { } + private static String initializeLogFile() { + FileSystemStorage storage = FileSystemStorage.getInstance(); + String path = storage.getAppHomePath() + LOG_FILE_NAME; + if (storage.exists(path)) { + storage.delete(path); + } + Log.getInstance().setFileURL(path); + Log.getInstance().setFileWriteEnabled(true); + Log.p("CN1SS logging initialized at " + path); + return path; + } + + static void resetLogCapture(String testName) { + if (LOG_FILE_PATH == null || LOG_FILE_PATH.length() == 0) { + return; + } + String safeName = sanitizeTestName(testName); + FileSystemStorage storage = FileSystemStorage.getInstance(); + if (storage.exists(LOG_FILE_PATH)) { + storage.delete(LOG_FILE_PATH); + } + Log.getInstance().setFileURL(LOG_FILE_PATH); + Log.getInstance().setFileWriteEnabled(true); + println("CN1SS:INFO:test=" + safeName + " log_reset=true path=" + LOG_FILE_PATH); + } + + static void emitTestStartMarker(String testName) { + String safeName = sanitizeTestName(testName); + emitChannel(toUtf8Bytes("start:" + safeName), safeName, START_CHANNEL); + } + static void runOnEdtSync(Runnable runnable) { Display display = Display.getInstance(); if (display.isEdt()) { @@ -29,6 +68,30 @@ static void runOnEdtSync(Runnable runnable) { } } + static void ensureGlobalErrorTaps() { + if (globalErrorHooksInstalled) { + return; + } + globalErrorHooksInstalled = true; + Display.getInstance().addEdtErrorHandler(evt -> { + Object source = evt.getSource(); + if (source instanceof Throwable) { + Throwable t = (Throwable) source; + Log.e(t); + println("CN1SS:ERR:edt thread throwable=" + t); + } else { + println("CN1SS:ERR:edt event=" + source); + } + }); + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + if (throwable != null) { + Log.e(throwable); + } + println("CN1SS:ERR:thread name=" + thread.getName() + " throwable=" + throwable); + }); + println("CN1SS:INFO:global error taps installed"); + } + static void waitForMillis(long millis) { int duration = (int) Math.max(1, Math.min(Integer.MAX_VALUE, millis)); Util.sleep(duration); @@ -39,6 +102,7 @@ static boolean emitCurrentFormScreenshot(String testName) { Form current = Display.getInstance().getCurrent(); if (current == null) { println("CN1SS:ERR:test=" + safeName + " message=Current form is null"); + emitLogOutput(safeName); println("CN1SS:END:" + safeName); return false; } @@ -52,12 +116,14 @@ static boolean emitCurrentFormScreenshot(String testName) { Util.sleep(50); // timeout if (System.currentTimeMillis() - time > 2000) { + emitLogOutput(safeName); return; } } }); if (img[0] == null) { println("CN1SS:ERR:test=" + safeName + " message=Screenshot process timed out"); + emitLogOutput(safeName); println("CN1SS:END:" + safeName); return false; } @@ -66,6 +132,7 @@ static boolean emitCurrentFormScreenshot(String testName) { ImageIO io = ImageIO.getImageIO(); if (io == null || !io.isFormatSupported(ImageIO.FORMAT_PNG)) { println("CN1SS:ERR:test=" + safeName + " message=PNG encoding unavailable"); + emitLogOutput(safeName); println("CN1SS:END:" + safeName); return false; } @@ -81,10 +148,12 @@ static boolean emitCurrentFormScreenshot(String testName) { } else { println("CN1SS:INFO:test=" + safeName + " preview_jpeg_bytes=0 preview_quality=0"); } + emitLogOutput(safeName); return true; } catch (IOException ex) { println("CN1SS:ERR:test=" + safeName + " message=" + ex); Log.e(ex); + emitLogOutput(safeName); println("CN1SS:END:" + safeName); return false; } finally { @@ -92,6 +161,12 @@ static boolean emitCurrentFormScreenshot(String testName) { } } + static void emitLogChannel(String testName) { + String safeName = sanitizeTestName(testName); + emitLogOutput(safeName); + println("CN1SS:END:" + safeName); + } + private static byte[] encodePreview(ImageIO io, Image screenshot, String safeName) throws IOException { byte[] chosenPreview = null; int chosenQuality = 0; @@ -157,6 +232,22 @@ static String sanitizeTestName(String testName) { return sanitized.toString(); } + private static byte[] toUtf8Bytes(String value) { + if (value == null) { + return new byte[0]; + } + try { + return value.getBytes("UTF-8"); + } catch (UnsupportedEncodingException ex) { + // UTF-8 should always be present, but fall back to ASCII-safe encoding for CN1 + byte[] bytes = new byte[value.length()]; + for (int i = 0; i < value.length(); i++) { + bytes[i] = (byte) (value.charAt(i) & 0x7F); + } + return bytes; + } + } + private static boolean isSafeChar(char ch) { if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { return true; @@ -180,6 +271,37 @@ private static String zeroPad(int value, int width) { return builder.toString(); } + private static void emitLogOutput(String safeName) { + byte[] logBytes = readLogBytes(); + if (logBytes == null || logBytes.length == 0) { + println("CN1SS:INFO:test=" + safeName + " log_bytes=0"); + emitChannel("(no log entries captured)".getBytes(), safeName, LOG_CHANNEL); + return; + } + println("CN1SS:INFO:test=" + safeName + " log_bytes=" + logBytes.length); + emitChannel(logBytes, safeName, LOG_CHANNEL); + } + + private static byte[] readLogBytes() { + if (LOG_FILE_PATH == null || LOG_FILE_PATH.length() == 0) { + return null; + } + FileSystemStorage storage = FileSystemStorage.getInstance(); + if (!storage.exists(LOG_FILE_PATH)) { + return null; + } + InputStream input = null; + try { + input = storage.openInputStream(LOG_FILE_PATH); + return Util.readInputStream(input); + } catch (IOException ex) { + Log.e(ex); + return null; + } finally { + Util.cleanup(input); + } + } + private static void println(String line) { System.out.println(line); } diff --git a/scripts/device-runner-app/tests/MediaPlaybackScreenshotTest.java b/scripts/device-runner-app/tests/MediaPlaybackScreenshotTest.java index 477a0cb61f..ed9a8cdf38 100644 --- a/scripts/device-runner-app/tests/MediaPlaybackScreenshotTest.java +++ b/scripts/device-runner-app/tests/MediaPlaybackScreenshotTest.java @@ -5,6 +5,7 @@ import com.codename1.media.MediaManager; import com.codename1.testing.AbstractTest; import com.codename1.testing.TestUtils; +import com.codename1.io.Log; import com.codename1.ui.Container; import com.codename1.ui.Form; import com.codename1.ui.Label; @@ -28,9 +29,11 @@ public boolean runTest() throws Exception { if (tonePath == null) { updateStatus(statusLabel, form, "Failed to generate tone file"); } else { + Log.p("CN1SS: attempting media playback from " + tonePath); Media media = MediaManager.createMedia(tonePath, false); if (media == null) { updateStatus(statusLabel, form, "Media creation returned null"); + Log.p("CN1SS: media creation returned null for " + tonePath); } media.setTime(0); media.play(); @@ -68,6 +71,7 @@ private static void cleanupMedia(Media media) { private static String writeToneWav() { byte[] wav = buildToneWav(); if (wav == null || wav.length == 0) { + Log.p("CN1SS: generated tone WAV is empty"); return null; } String path = FileSystemStorage.getInstance().getAppHomePath() + "media-playback-test.wav"; @@ -76,9 +80,11 @@ private static String writeToneWav() { try { out = FileSystemStorage.getInstance().openOutputStream(path); out.write(wav); + Log.p("CN1SS: wrote tone WAV to " + path + " (" + wav.length + " bytes)"); return path; } catch (IOException ex) { TestUtils.log("Unable to write tone wav: " + ex.getMessage()); + Log.e(ex); return null; } finally { if (out != null) { diff --git a/scripts/lib/cn1ss.sh b/scripts/lib/cn1ss.sh index ca68390dee..613a38556f 100644 --- a/scripts/lib/cn1ss.sh +++ b/scripts/lib/cn1ss.sh @@ -186,6 +186,11 @@ cn1ss_verify_png() { head -c 8 "$file" | od -An -t x1 | tr -d ' \n' | grep -qi '^89504e470d0a1a0a$' } +cn1ss_verify_text() { + local file="$1" + [ -f "$file" ] +} + cn1ss_verify_jpeg() { local file="$1" [ -s "$file" ] || return 1 @@ -234,6 +239,12 @@ cn1ss_decode_test_preview() { cn1ss_decode_test_asset "$test" "$dest" "PREVIEW" cn1ss_verify_jpeg "$@" } +cn1ss_decode_test_log() { + local test="$1"; shift + local dest="$1"; shift + cn1ss_decode_test_asset "$test" "$dest" "LOG" cn1ss_verify_text "$@" +} + cn1ss_file_size() { local file="$1" if [ ! -f "$file" ]; then diff --git a/scripts/run-android-instrumentation-tests.sh b/scripts/run-android-instrumentation-tests.sh index c953109bda..6b8cf5f9ae 100755 --- a/scripts/run-android-instrumentation-tests.sh +++ b/scripts/run-android-instrumentation-tests.sh @@ -43,6 +43,7 @@ ENV_FILE="$ENV_DIR/env.sh" ARTIFACTS_DIR="${ARTIFACTS_DIR:-${GITHUB_WORKSPACE:-$REPO_ROOT}/artifacts}" ensure_dir "$ARTIFACTS_DIR" TEST_LOG="$ARTIFACTS_DIR/connectedAndroidTest.log" +CN1SS_LOG_LINES="$ARTIFACTS_DIR/cn1ss-lines.log" SCREENSHOT_REF_DIR="$SCRIPT_DIR/android/screenshots" SCREENSHOT_TMP_DIR="$(mktemp -d "${TMPDIR}/cn1ss-XXXXXX" 2>/dev/null || echo "${TMPDIR}/cn1ss-tmp")" ensure_dir "$SCREENSHOT_TMP_DIR" @@ -157,6 +158,15 @@ fi declare -a CN1SS_SOURCES=("LOGCAT:$TEST_LOG") +# Extract CN1SS-prefixed lines for quick inspection +grep -E "CN1SS" "$TEST_LOG" > "$CN1SS_LOG_LINES" 2>/dev/null || true +if [ -s "$CN1SS_LOG_LINES" ]; then + ra_log "CN1SS log lines written to $CN1SS_LOG_LINES" +else + echo "(no CN1SS-prefixed lines found in logcat)" > "$CN1SS_LOG_LINES" + ra_log "No CN1SS-prefixed lines detected; wrote placeholder to $CN1SS_LOG_LINES" +fi + # ---- Chunk accounting (diagnostics) --------------------------------------- @@ -175,13 +185,23 @@ fi # ---- Identify CN1SS test streams ----------------------------------------- TEST_NAMES_RAW="$(cn1ss_list_tests "$TEST_LOG" 2>/dev/null | awk 'NF' | sort -u || true)" +FORM_READY_NAMES_RAW="$(grep -oE "CN1SS: form ready for screenshot -> .*" "$TEST_LOG" 2>/dev/null | sed 's/.*-> //' | sed 's/[^A-Za-z0-9_.-]/_/g' | awk 'NF' | sort -u || true)" declare -a TEST_NAMES=() if [ -n "$TEST_NAMES_RAW" ]; then while IFS= read -r name; do [ -n "$name" ] || continue TEST_NAMES+=("$name") done <<< "$TEST_NAMES_RAW" -else +fi +if [ -n "$FORM_READY_NAMES_RAW" ]; then + while IFS= read -r name; do + [ -n "$name" ] || continue + if [[ ! " ${TEST_NAMES[*]} " =~ " ${name} " ]]; then + TEST_NAMES+=("$name") + fi + done <<< "$FORM_READY_NAMES_RAW" +fi +if [ ${#TEST_NAMES[@]} -eq 0 ]; then TEST_NAMES+=("default") fi ra_log "Detected CN1SS test streams: ${TEST_NAMES[*]}" @@ -189,12 +209,15 @@ ra_log "Detected CN1SS test streams: ${TEST_NAMES[*]}" declare -A TEST_OUTPUTS=() declare -A TEST_SOURCES=() declare -A PREVIEW_OUTPUTS=() +declare -A LOG_OUTPUTS=() ensure_dir "$SCREENSHOT_PREVIEW_DIR" for test in "${TEST_NAMES[@]}"; do dest="$SCREENSHOT_TMP_DIR/${test}.png" - if source_label="$(cn1ss_decode_test_png "$test" "$dest" "${CN1SS_SOURCES[@]}")"; then + rm -f "$dest" 2>/dev/null || true + png_chunks="$(cn1ss_count_chunks "$TEST_LOG" "$test")"; png_chunks="${png_chunks//[^0-9]/}"; : "${png_chunks:=0}" + if [ "$png_chunks" -gt 0 ] && source_label="$(cn1ss_decode_test_png "$test" "$dest" "${CN1SS_SOURCES[@]}")"; then TEST_OUTPUTS["$test"]="$dest" TEST_SOURCES["$test"]="$source_label" ra_log "Decoded screenshot for '$test' (source=${source_label}, size: $(cn1ss_file_size "$dest") bytes)" @@ -206,15 +229,17 @@ for test in "${TEST_NAMES[@]}"; do rm -f "$preview_dest" 2>/dev/null || true fi else - ra_log "FATAL: Failed to extract/decode CN1SS payload for test '$test'" - RAW_B64_OUT="$SCREENSHOT_TMP_DIR/${test}.raw.b64" - if cn1ss_extract_base64 "$TEST_LOG" "$test" > "$RAW_B64_OUT" 2>/dev/null; then - if [ -s "$RAW_B64_OUT" ]; then - head -c 64 "$RAW_B64_OUT" | sed 's/^/[CN1SS-B64-HEAD] /' - ra_log "Partial base64 saved at: $RAW_B64_OUT" - fi - fi - exit 12 + ra_log "WARN: No screenshot payload decoded for '$test' (png_chunks=${png_chunks})" + fi + + log_dest="$SCREENSHOT_TMP_DIR/${test}.log" + log_chunks="$(cn1ss_count_chunks "$TEST_LOG" "$test" "LOG")"; log_chunks="${log_chunks//[^0-9]/}"; : "${log_chunks:=0}" + if [ "$log_chunks" -gt 0 ] && log_source="$(cn1ss_decode_test_log "$test" "$log_dest" "${CN1SS_SOURCES[@]}")"; then + LOG_OUTPUTS["$test"]="$log_dest" + ra_log "Decoded log for '$test' (source=${log_source}, size: $(cn1ss_file_size "$log_dest") bytes)" + else + rm -f "$log_dest" 2>/dev/null || true + ra_log "INFO: No CN1SS log payload available for '$test' (log_chunks=${log_chunks})" fi done @@ -222,8 +247,7 @@ done COMPARE_ARGS=() for test in "${TEST_NAMES[@]}"; do - dest="${TEST_OUTPUTS[$test]:-}" - [ -n "$dest" ] || continue + dest="${TEST_OUTPUTS[$test]:-$SCREENSHOT_TMP_DIR/${test}.png}" COMPARE_ARGS+=("--actual" "${test}=${dest}") done @@ -283,6 +307,10 @@ if [ -s "$SUMMARY_FILE" ]; then if [ -n "${preview_note:-}" ]; then ra_log " Preview note: ${preview_note}" fi + if [ -n "${LOG_OUTPUTS[$test]:-}" ] && [ -f "${LOG_OUTPUTS[$test]}" ]; then + cp -f "${LOG_OUTPUTS[$test]}" "$ARTIFACTS_DIR/${test}.log" 2>/dev/null || true + ra_log " -> Stored log artifact copy at $ARTIFACTS_DIR/${test}.log" + fi done < "$SUMMARY_FILE" fi From 58c38663ce968ac0a9802820e9920337066435e6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:29:28 +0200 Subject: [PATCH 2/4] Use CN1 crash protection instead of unsupported default handler --- .../device-runner-app/tests/Cn1ssDeviceRunnerHelper.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java index 564be9ec6a..fe63b1c808 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java @@ -73,6 +73,7 @@ static void ensureGlobalErrorTaps() { return; } globalErrorHooksInstalled = true; + Log.bindCrashProtection(true); Display.getInstance().addEdtErrorHandler(evt -> { Object source = evt.getSource(); if (source instanceof Throwable) { @@ -83,12 +84,6 @@ static void ensureGlobalErrorTaps() { println("CN1SS:ERR:edt event=" + source); } }); - Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { - if (throwable != null) { - Log.e(throwable); - } - println("CN1SS:ERR:thread name=" + thread.getName() + " throwable=" + throwable); - }); println("CN1SS:INFO:global error taps installed"); } From ea47af52c18b4622e182ca85a12b6cf801593821 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:57:02 +0200 Subject: [PATCH 3/4] Rotate CN1SS log files per test --- .../tests/Cn1ssDeviceRunnerHelper.java | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java index fe63b1c808..c98da82876 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java @@ -20,38 +20,36 @@ final class Cn1ssDeviceRunnerHelper { private static final String PREVIEW_CHANNEL = "PREVIEW"; private static final String LOG_CHANNEL = "LOG"; private static final String START_CHANNEL = "START"; - private static final String LOG_FILE_NAME = "cn1ss-device-runner.log"; + private static final String LOG_FILE_NAME_PREFIX = "cn1ss-device-runner-"; private static final int[] PREVIEW_QUALITIES = new int[] {60, 50, 40, 35, 30, 25, 20, 18, 16, 14, 12, 10, 8, 6, 5, 4, 3, 2, 1}; - private static final String LOG_FILE_PATH = initializeLogFile(); + private static final String LOG_FILE_BASE = initializeLogBasePath(); + private static String currentLogPath = initializeLogFile(); private static boolean globalErrorHooksInstalled; private Cn1ssDeviceRunnerHelper() { } - private static String initializeLogFile() { + private static String initializeLogBasePath() { FileSystemStorage storage = FileSystemStorage.getInstance(); - String path = storage.getAppHomePath() + LOG_FILE_NAME; - if (storage.exists(path)) { - storage.delete(path); - } - Log.getInstance().setFileURL(path); - Log.getInstance().setFileWriteEnabled(true); - Log.p("CN1SS logging initialized at " + path); + String base = storage.getAppHomePath() + LOG_FILE_NAME_PREFIX; + return base; + } + + private static String initializeLogFile() { + String path = LOG_FILE_BASE + "session.log"; + setupLogFile(path); return path; } static void resetLogCapture(String testName) { - if (LOG_FILE_PATH == null || LOG_FILE_PATH.length() == 0) { + if (LOG_FILE_BASE == null || LOG_FILE_BASE.length() == 0) { return; } String safeName = sanitizeTestName(testName); - FileSystemStorage storage = FileSystemStorage.getInstance(); - if (storage.exists(LOG_FILE_PATH)) { - storage.delete(LOG_FILE_PATH); - } - Log.getInstance().setFileURL(LOG_FILE_PATH); - Log.getInstance().setFileWriteEnabled(true); - println("CN1SS:INFO:test=" + safeName + " log_reset=true path=" + LOG_FILE_PATH); + String path = LOG_FILE_BASE + safeName + ".log"; + setupLogFile(path); + currentLogPath = path; + println("CN1SS:INFO:test=" + safeName + " log_reset=true path=" + path); } static void emitTestStartMarker(String testName) { @@ -278,16 +276,16 @@ private static void emitLogOutput(String safeName) { } private static byte[] readLogBytes() { - if (LOG_FILE_PATH == null || LOG_FILE_PATH.length() == 0) { + if (currentLogPath == null || currentLogPath.length() == 0) { return null; } FileSystemStorage storage = FileSystemStorage.getInstance(); - if (!storage.exists(LOG_FILE_PATH)) { + if (!storage.exists(currentLogPath)) { return null; } InputStream input = null; try { - input = storage.openInputStream(LOG_FILE_PATH); + input = storage.openInputStream(currentLogPath); return Util.readInputStream(input); } catch (IOException ex) { Log.e(ex); @@ -300,4 +298,14 @@ private static byte[] readLogBytes() { private static void println(String line) { System.out.println(line); } + + private static void setupLogFile(String path) { + FileSystemStorage storage = FileSystemStorage.getInstance(); + if (storage.exists(path)) { + storage.delete(path); + } + Log.getInstance().setFileURL(path); + Log.getInstance().setFileWriteEnabled(true); + Log.p("CN1SS logging initialized at " + path); + } } From bd39beb4690d6543bbc14da9262f3c9146715407 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 23 Nov 2025 04:04:42 +0200 Subject: [PATCH 4/4] Flush CN1SS logs when tests exit --- scripts/device-runner-app/tests/BaseTest.java | 12 ++++++++++++ .../device-runner-app/tests/Cn1ssDeviceRunner.java | 12 +++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/device-runner-app/tests/BaseTest.java b/scripts/device-runner-app/tests/BaseTest.java index e9d8c90d70..f0d7ec9deb 100644 --- a/scripts/device-runner-app/tests/BaseTest.java +++ b/scripts/device-runner-app/tests/BaseTest.java @@ -58,4 +58,16 @@ protected boolean waitForDone() { } return true; } + + String getCurrentScreenshotName() { + return currentScreenshotName; + } + + void ensureLogsFlushedOnExit() { + if (!logEmitted) { + Log.p("CN1SS: forcing log emission on exit for " + currentScreenshotName); + Cn1ssDeviceRunnerHelper.emitLogChannel(currentScreenshotName); + logEmitted = true; + } + } } \ No newline at end of file diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java index 09930fd171..b106170073 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunner.java @@ -26,12 +26,22 @@ public void runSuite() { Log.p("CN1SS: preparing test " + testClass); testClass.prepare(); testClass.runTest(); - testClass.cleanup(); Log.p("CN1SS: finished test " + testClass); log("CN1SS:INFO:suite finished test=" + testClass); } catch (Throwable t) { log("CN1SS:ERR:suite test=" + testClass + " failed=" + t); t.printStackTrace(); + } finally { + if (testClass instanceof BaseTest) { + BaseTest base = (BaseTest) testClass; + base.ensureLogsFlushedOnExit(); + } + try { + testClass.cleanup(); + } catch (Throwable t) { + log("CN1SS:ERR:suite cleanup failed for test=" + testClass + " err=" + t); + t.printStackTrace(); + } } } Log.p("CN1SS: device runner suite complete");