Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/android/tests/Cn1ssChunkTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private static void runTests(String[] args) throws IOException {
}
Path path = Path.of(args[0]);
List<String> 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);
}
Expand Down
31 changes: 31 additions & 0 deletions scripts/device-runner-app/tests/BaseTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
});
}
Expand All @@ -32,11 +43,31 @@ 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;
}

String getCurrentScreenshotName() {
return currentScreenshotName;
}

void ensureLogsFlushedOnExit() {
if (!logEmitted) {
Log.p("CN1SS: forcing log emission on exit for " + currentScreenshotName);
Cn1ssDeviceRunnerHelper.emitLogChannel(currentScreenshotName);
logEmitted = true;
}
}
}
17 changes: 16 additions & 1 deletion scripts/device-runner-app/tests/Cn1ssDeviceRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,18 +19,32 @@ 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();
} 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");
log("CN1SS:SUITE:FINISHED");
TestReporting.getInstance().testExecutionFinished(getClass().getName());
}
Expand Down
125 changes: 125 additions & 0 deletions scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,16 +11,52 @@

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_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_BASE = initializeLogBasePath();
private static String currentLogPath = initializeLogFile();
private static boolean globalErrorHooksInstalled;

private Cn1ssDeviceRunnerHelper() {
}

private static String initializeLogBasePath() {
FileSystemStorage storage = FileSystemStorage.getInstance();
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_BASE == null || LOG_FILE_BASE.length() == 0) {
return;
}
String safeName = sanitizeTestName(testName);
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) {
String safeName = sanitizeTestName(testName);
emitChannel(toUtf8Bytes("start:" + safeName), safeName, START_CHANNEL);
}

static void runOnEdtSync(Runnable runnable) {
Display display = Display.getInstance();
if (display.isEdt()) {
Expand All @@ -29,6 +66,25 @@ static void runOnEdtSync(Runnable runnable) {
}
}

static void ensureGlobalErrorTaps() {
if (globalErrorHooksInstalled) {
return;
}
globalErrorHooksInstalled = true;
Log.bindCrashProtection(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);
}
});
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);
Expand All @@ -39,6 +95,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;
}
Expand All @@ -52,12 +109,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;
}
Expand All @@ -66,6 +125,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;
}
Expand All @@ -81,17 +141,25 @@ 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 {
screenshot.dispose();
}
}

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;
Expand Down Expand Up @@ -157,6 +225,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;
Expand All @@ -180,7 +264,48 @@ 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 (currentLogPath == null || currentLogPath.length() == 0) {
return null;
}
FileSystemStorage storage = FileSystemStorage.getInstance();
if (!storage.exists(currentLogPath)) {
return null;
}
InputStream input = null;
try {
input = storage.openInputStream(currentLogPath);
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);
}

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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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";
Expand All @@ -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) {
Expand Down
11 changes: 11 additions & 0 deletions scripts/lib/cn1ss.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading