Skip to content

Commit 00cb96c

Browse files
committed
Improved CI and started generating some screenshots that look close
1 parent b8a557b commit 00cb96c

5 files changed

Lines changed: 154 additions & 131 deletions

File tree

Ports/JavaScriptPort/STATUS.md

Lines changed: 34 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3,121 +3,54 @@
33
JavaScript Port Status (ParparVM)
44
=================================
55

6-
Last updated: 2026-04-07
6+
Last updated: 2026-04-09
77

88
Current State
99
-------------
1010

11-
- **WORKAROUND IN PLACE**: `ensureDisplayEdt()` in port.js creates synthetic EDT if missing.
12-
- **FIX**: `initImpl` shim in port.js guards against `getClass().getName()` failures during `Display.init()`.
13-
- In the ParparVM JS translation, `Object.getClass()` may return null or `Class.getName()` may return an underscore-separated name,
14-
causing `String.lastIndexOf('.')` to return -1 and a subsequent `substring(0, -1)` to throw.
15-
- The shim catches these errors, calls `init(m)` directly, and sets `packageName` from the bootstrap object's class metadata.
16-
- **FIX**: Simplified `ParparVMBootstrap.bootstrap()` to match `JavaScriptPortBootstrap.bootstrap()` structure.
17-
- Removed intermediate `hasNativeTheme()`/`installNativeTheme()` calls between `Display.init()` and `bootstrap.run()`.
18-
- These calls were triggering additional failures in the partially-initialized Display state.
19-
- **Removed**: Bogus root-level `ParparVMBootstrap.java` that had incorrect imports and structure.
20-
- **Remaining issue**: `IllegalStateException` still caught in fallback handlers even after EDT is set. May be unrelated to EDT (different code path).
21-
- The separate ParparVM Java test pipelines that were failing in CI (`job-logs2.txt`, `job-logs3.txt`) are now reproduced and fixed locally.
22-
23-
## Diagnostic Evidence (Updated 2026-04-06)
24-
25-
After EDT workaround, browser logs show:
26-
```
27-
PARPAR:DIAG:EDT_ENSURE:reusedMainThread=1
28-
PARPAR:DIAG:POST_EDT_ENSURE_formCtorTitleLayoutGlobal:displayClassExists=1:instance=present:edt=present:edtThreadName=main
29-
PARPAR:DIAG:PRE_formCtorLayout:displayClassExists=1:instance=present:edt=present:edtThreadName=main
30-
PARPAR:DIAG:ERR_formCtorLayout:displayClassExists=1:instance=present:edt=present:edtThreadName=main
31-
PARPAR:DIAG:FALLBACK:formCtorLayout:bypassIllegalState=1:detail=java_lang_IllegalStateException
32-
```
33-
34-
**Key observations**:
35-
- EDT is successfully set (`edt=present:edtThreadName=main`)
36-
- Tests pass (suite completes)
37-
- `IllegalStateException` still triggered but caught by fallback handler
38-
- Exception may originate from code path other than `Display.setCurrent()` EDT check
39-
40-
## Previous Issues (Historical)
41-
42-
What Was Fixed In This Round
43-
----------------------------
44-
45-
1. **Added EDT initialization workaround** in `port.js`:
46-
- `checkDisplayInitState()` -checks Display.INITANCE and EDT state
47-
- `ensureDisplayEdt()` - creates synthetic EDT thread or reuses main thread
48-
- Form constructor bypasses call `ensureDisplayEdt()` before construction
49-
- Verified workaround works: logs show `EDT_ENSURE:reusedMainThread=1`
50-
51-
2. **Rebuilt JavaScript bundle** with updated port.js:
52-
- Built with `SKIP_PARPARVM_BUILD=1 ./scripts/build-javascript-port-hellocodenameone.sh`
53-
- Bundle contains updated `port.js` with workaround
54-
55-
3. **Tests now pass**:
56-
- Suite completes with `CN1SS:SUITE:FINISHED`
57-
- Screenshots generated for MainActivity, graphics-draw-line, graphics-draw-rect, graphics-fill-rect, kotlin
58-
59-
1. Restored native categorization for JavaScript translation.
60-
- Reintroduced `vm/ByteCodeTranslator/src/com/codename1/tools/translator/JavascriptNativeRegistry.java`.
61-
- Restored registry-driven behavior in `JavascriptMethodGenerator.appendNativeStubIfNeeded(...)`:
62-
- runtime-implemented natives: no generic "Missing javascript native method" stubs.
63-
- host-hook natives: generated `jvm.invokeHostNative(...)` stubs.
64-
- unsupported natives: explicit unsupported error reasons.
65-
66-
2. Expanded host-hook coverage to the JavaScript port boundary.
67-
- Host-hook prefix now covers `cn1_com_codename1_impl_platform_js_`.
68-
- This fixes `JavaScriptPortHost` bridge symbols used by JavaScript port smoke tests.
69-
70-
3. Fixed `String.format(...)` runtime coercion bug.
71-
- File: `vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js`.
72-
- Changed format token conversion to use `runtimeFormatTokenValue(...)`.
73-
- This resolves the deterministic `JsJavaApiCoverageApp` mismatch (`expected 511, got 495`; missing bit `16`).
74-
75-
4. Added explicit PolyForm header text to missing JavaScriptPort boundary files.
76-
- Added `"PolyForm Noncommercial License 1.0.0"` markers to all Java/Markdown files previously flagged by `JavaScriptPortSmokeIntegrationTest`.
77-
78-
5. Reverted risky JS `INVOKESPECIAL` remap in codegen.
79-
- Removed the recent `Util.resolveInvokeSpecialOwner(...)` injection from JS invoke emission paths for now.
80-
- The broader regressions observed in CI are fixed without that change.
81-
82-
6. **Enhanced Form constructor error diagnostics**.
83-
- Added `checkDisplayInitState()` function to report Display.INSTANCE and EDT state.
84-
- Added `emitDisplayInitDiag()` calls before and after Form constructor execution.
85-
- Enhanced `stringifyThrowable()` to capture `messageOnly` separately.
86-
- Bypass handlers now log `PRE_` and `ERR_` Display state along with exception details.
87-
88-
Validated Locally
89-
-----------------
90-
91-
- `mvn -pl tests -am "-Dtest=JavascriptRuntimeSemanticsTest,JavascriptTargetIntegrationTest,JavaScriptPortSmokeIntegrationTest" -DfailIfNoTests=false -Dsurefire.failIfNoSpecifiedTests=false test`
92-
- Result: `Tests run: 313, Failures: 0, Errors: 0, Skipped: 0` (BUILD SUCCESS, finished 2026-04-06 10:47:59 +03:00).
11+
- Startup/protocol is no longer the primary blocker. Main remaining blockers are in screenshot correctness and screenshot pipeline throughput.
12+
- `ensureDisplayEdt()` and diagnostics remain active in `port.js`.
13+
- Form constructor `IllegalStateException` bypass now attempts recovery instead of returning `null`:
14+
- calls default `Form` constructor
15+
- reapplies layout
16+
- reapplies title (for title+layout constructor)
17+
- emits `PARPAR:DIAG:FALLBACK:formCtor*:recoverApplied=1` markers
18+
- CI artifact behavior and fresh local rebuild behavior are currently diverged (details below).
9319

9420
Next Steps
9521
----------
9622

97-
1. **Investigate remaining `IllegalStateException`**:
98-
- Exception occurs even after EDT is set
99-
- May be from different code path (not Display.setCurrent)
100-
- Add exception message logging to `ERR_` diagnostics
101-
102-
2. **Optional**: Investigate why Display.init() doesn't create EDT in JavaScript port:
103-
- Trace `Display.initNative()` call sequence
104-
- Check if thread creation is happening but not persisting
105-
106-
3. **Commit workaround**: The `ensureDisplayEdt()` fix allows tests to pass; consider committing as a workaround while root cause is investigated.
23+
1. Unify build provenance first:
24+
- Confirm CI bundle contains expected translated `Cn1ssDeviceRunner` variant.
25+
- In generated `translated_app.js`, verify whether runner uses `TEST_CLASSES` (old list path) or `DEFAULT_TEST_CLASSES/prependedTest` (new array path).
26+
- This directly changes failure mode and must be deterministic before further triage.
27+
2. Reduce EDT starvation in screenshot emission:
28+
- Current fresh local run advances to 11 tests, then stalls after `DrawImage` due very large `CN1SS:<name>:<chunk>` emissions.
29+
- Add bounded chunk/preview strategy for extremely large screenshots and move heavy conversion off hot EDT path where possible.
30+
3. Keep form-constructor fallback bounded:
31+
- `IllegalStateException` still occurs frequently.
32+
- Recovery now preserves object state better, but we need to reduce recursive/looping constructor retry behavior and make one-shot recovery per form instance.
33+
4. After throughput fix, run 3 repeated local runs and one CI run:
34+
- Exit gate is `CN1SS:SUITE:FINISHED` + 33 screenshot streams + no repeated stream collapse.
10735

10836
Important Notes
10937
--------------
11038

111-
- The CI breakage in the ParparVM Java test pipelines was real and not just screenshot-noise; it is now addressed by the translator/runtime fixes above.
112-
- The screenshot problem remains a separate rendering/lifecycle issue after startup/protocol recovery.
113-
- Error messages are now extracted separately from the full throwable detail for easier parsing.
39+
- Fresh local rebuild (`/tmp/cn1-js-fresh*.zip`) now translates current runner code and shows 11 unique tests before timeout.
40+
- Existing CI artifact in `javascript-ui-tests/HelloCodenameOne-js/translated_app.js` still shows old list-based runner (`TEST_CLASSES`) and produces only 5 screenshot streams with suite completion.
41+
- This means there are at least two active failure modes:
42+
1. old-runner path: suite finishes with only 5 streams (wrong collapse)
43+
2. new-runner path: progresses further but times out during heavy screenshot emission
11444

11545
Known Important Context
11646
-----------------------
11747

118-
- This file supersedes older status notes that referenced initial JSBody/static-wrapper bootstrap issues as the primary blocker.
119-
- Current bottleneck is no longer "suite timeout"; it is "suite passes but screenshots are wrong".
120-
- Existing local tree also includes ongoing debug-oriented changes in:
48+
- Useful diagnostics to grep:
49+
- `PARPAR:DIAG:FALLBACK:lambdaBridge:capturedTest=...:capturedIndex=...`
50+
- `PARPAR:DIAG:FALLBACK:formCtorLayout:bypassIllegalState=1`
51+
- `PARPAR:DIAG:FALLBACK:formCtorLayout:recoverApplied=1`
52+
- `CN1SS:INFO:suite starting test=...`
53+
- `CN1SS:SUITE:FINISHED`
54+
- Current local patch set touches:
12155
- `Ports/JavaScriptPort/src/main/webapp/port.js`
122-
- `Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java`
123-
These are part of the active CI recovery/debugging workflow.
56+
- `scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java`

Ports/JavaScriptPort/src/main/webapp/port.js

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,69 @@ const formCtorLayoutMethodId = "cn1_com_codename1_ui_Form___INIT___com_codename1
16201620
const formCtorTitleLayoutMethodId = "cn1_com_codename1_ui_Form___INIT___java_lang_String_com_codename1_ui_layouts_Layout";
16211621
const formCtorLayoutOriginal = typeof global[formCtorLayoutMethodId] === "function" ? global[formCtorLayoutMethodId] : null;
16221622
const formCtorTitleLayoutOriginal = typeof global[formCtorTitleLayoutMethodId] === "function" ? global[formCtorTitleLayoutMethodId] : null;
1623+
const formDefaultCtorMethodId = "cn1_com_codename1_ui_Form___INIT__";
1624+
const formSetTitleMethodId = "cn1_com_codename1_ui_Form_setTitle_java_lang_String";
1625+
const containerSetLayoutMethodId = "cn1_com_codename1_ui_Container_setLayout_com_codename1_ui_layouts_Layout";
1626+
1627+
function* recoverFormCtorIllegalState(self, title, layout, marker) {
1628+
if (!self || !self.__class) {
1629+
emitDiagLine("PARPAR:DIAG:FALLBACK:" + marker + ":recoverSkipped=noSelf");
1630+
return null;
1631+
}
1632+
if (self.__cn1FormCtorRecovering) {
1633+
emitDiagLine("PARPAR:DIAG:FALLBACK:" + marker + ":recoverSkipped=reentry");
1634+
return null;
1635+
}
1636+
self.__cn1FormCtorRecovering = true;
1637+
let ctorApplied = false;
1638+
try {
1639+
const defaultCtor = global[formDefaultCtorMethodId + "__impl"] || global[formDefaultCtorMethodId];
1640+
if (typeof defaultCtor === "function") {
1641+
try {
1642+
yield* defaultCtor(self);
1643+
ctorApplied = true;
1644+
} catch (ctorErr) {
1645+
emitDiagLine("PARPAR:DIAG:FALLBACK:" + marker + ":recoverCtorError=" + String(ctorErr && ctorErr.__class ? ctorErr.__class : ctorErr));
1646+
}
1647+
}
1648+
if (layout && layout.__class) {
1649+
let layoutApplied = false;
1650+
try {
1651+
const setLayout = jvm.resolveVirtual(self.__class, containerSetLayoutMethodId);
1652+
yield* setLayout(self, layout);
1653+
layoutApplied = true;
1654+
} catch (_setLayoutErr) {
1655+
// Fall through to direct field patch.
1656+
}
1657+
if (!layoutApplied) {
1658+
self["cn1_com_codename1_ui_Container_layout"] = layout;
1659+
}
1660+
}
1661+
if (title && title.__class === "java_lang_String") {
1662+
let titleApplied = false;
1663+
try {
1664+
const setTitle = jvm.resolveVirtual(self.__class, formSetTitleMethodId);
1665+
yield* setTitle(self, title);
1666+
titleApplied = true;
1667+
} catch (_setTitleErr) {
1668+
// Fall through to direct field patch.
1669+
}
1670+
if (!titleApplied) {
1671+
self["cn1_com_codename1_ui_Form_title"] = title;
1672+
}
1673+
}
1674+
self.__cn1FormCtorRecovered = true;
1675+
} finally {
1676+
self.__cn1FormCtorRecovering = false;
1677+
}
1678+
emitDiagLine(
1679+
"PARPAR:DIAG:FALLBACK:" + marker + ":recoverApplied=1"
1680+
+ ":ctor=" + (ctorApplied ? "1" : "0")
1681+
+ ":layout=" + (layout && layout.__class ? "1" : "0")
1682+
+ ":title=" + (title && title.__class === "java_lang_String" ? "1" : "0")
1683+
);
1684+
return null;
1685+
}
16231686

16241687
function installGlobalIllegalStateBypass(symbol, marker) {
16251688
const original = global[symbol];
@@ -1705,7 +1768,7 @@ bindCiFallback("Form.layoutCtorIllegalStateBypass", [
17051768
if (messageOnly) {
17061769
emitDiagLine("PARPAR:DIAG:FALLBACK:formCtorLayout:messageOnly=" + messageOnly);
17071770
}
1708-
return null;
1771+
return yield* recoverFormCtorIllegalState(__cn1ThisObject, null, layout, "formCtorLayout");
17091772
}
17101773
throw err;
17111774
}
@@ -1749,7 +1812,7 @@ bindCiFallback("Form.titleLayoutCtorIllegalStateBypass", [
17491812
if (messageOnly) {
17501813
emitDiagLine("PARPAR:DIAG:FALLBACK:formCtorTitleLayout:messageOnly=" + messageOnly);
17511814
}
1752-
return null;
1815+
return yield* recoverFormCtorIllegalState(__cn1ThisObject, title, layout, "formCtorTitleLayout");
17531816
}
17541817
throw err;
17551818
}
@@ -1762,6 +1825,7 @@ const cn1ssCompleteMethodId = "cn1_com_codenameone_examples_hellocodenameone_tes
17621825
const cn1ssEmitChannelMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitChannel_byte_1ARRAY_java_lang_String_java_lang_String";
17631826
const baseTestRegisterReadyCallbackMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_registerReadyCallback_com_codename1_ui_Form_java_lang_Runnable";
17641827
const cn1ssRunnerClassId = "com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunner";
1828+
const cn1ssRunnerListGetMethodId = "cn1_java_util_List_get_int_R_java_lang_Object";
17651829
function collectCn1ssRunnerLambdaMethodIds() {
17661830
const ids = [];
17671831
if (!jvm || !jvm.classes || !jvm.classes[cn1ssRunnerClassId] || !jvm.classes[cn1ssRunnerClassId].methods) {
@@ -1866,6 +1930,33 @@ bindCiFallback("Cn1ssDeviceRunner.lambdaRunNextTestBridge", cn1ssLambdaBridgeMet
18661930
}
18671931
}
18681932
const effectiveIndex = capturedIndex != null ? (capturedIndex | 0) : (index | 0);
1933+
// Prefer runner static test list over lambda captures when available. Captured
1934+
// lambda arguments have been observed to drift after initial indices in ParparVM.
1935+
let indexedTestObject = null;
1936+
if (runner && runner.__class === cn1ssRunnerClassId) {
1937+
try {
1938+
const runnerClass = jvm.classes[cn1ssRunnerClassId];
1939+
const testClasses = runnerClass && runnerClass.staticFields
1940+
? runnerClass.staticFields["TEST_CLASSES"]
1941+
: null;
1942+
if (testClasses && testClasses.__class) {
1943+
const listGetMethod = jvm.resolveVirtual(testClasses.__class, cn1ssRunnerListGetMethodId);
1944+
indexedTestObject = yield* listGetMethod(testClasses, effectiveIndex);
1945+
}
1946+
} catch (_err) {
1947+
indexedTestObject = null;
1948+
}
1949+
}
1950+
if (jvm.instanceOf(indexedTestObject, "com_codenameone_examples_hellocodenameone_tests_BaseTest")) {
1951+
if (!effectiveTestObject || !effectiveTestObject.__class || effectiveTestObject.__class !== indexedTestObject.__class) {
1952+
emitLambdaBridgeDiag(
1953+
"PARPAR:DIAG:FALLBACK:lambdaBridge:indexedOverride:index=" + String(effectiveIndex)
1954+
+ ":from=" + (effectiveTestObject && effectiveTestObject.__class ? effectiveTestObject.__class : "null")
1955+
+ ":to=" + indexedTestObject.__class
1956+
);
1957+
}
1958+
effectiveTestObject = indexedTestObject;
1959+
}
18691960
emitLambdaBridgeDiag(
18701961
"PARPAR:DIAG:FALLBACK:lambdaBridge:capturedRunner="
18711962
+ (runner && runner.__class ? runner.__class : "null")

scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Base64NativePerformanceTest.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,22 @@ public boolean runTest() {
5252
return false;
5353
}
5454

55-
int encodedLen = ((payloadBytes.length + 2) / 3) * 4;
56-
byte[] cn1EncodedBytes = new byte[encodedLen];
57-
int encodedWritten = Base64.encodeNoNewline(payloadBytes, cn1EncodedBytes);
58-
if (encodedWritten != encodedLen) {
59-
fail("CN1 preallocated Base64 encode returned unexpected length");
55+
byte[] cn1EncodedBytes;
56+
try {
57+
cn1EncodedBytes = cn1Encoded.getBytes("UTF-8");
58+
} catch (Exception ex) {
59+
fail("Failed to encode CN1 Base64 output to UTF-8 bytes: " + ex);
6060
return false;
6161
}
62-
byte[] cn1DecodedBuffer = new byte[payloadBytes.length];
6362

6463
if (!isIos()) {
65-
warmup(nativeBase64, payload, payloadBytes, nativeEncoded, cn1EncodedBytes, cn1DecodedBuffer);
64+
warmup(nativeBase64, payload, payloadBytes, nativeEncoded, cn1EncodedBytes);
6665
}
6766

6867
long nativeEncodeMs = measureNativeEncode(nativeBase64, payload);
69-
long cn1EncodeMs = measureCn1Encode(payloadBytes, cn1EncodedBytes);
68+
long cn1EncodeMs = measureCn1Encode(payloadBytes);
7069
long nativeDecodeMs = measureNativeDecode(nativeBase64, nativeEncoded);
71-
long cn1DecodeMs = measureCn1Decode(cn1EncodedBytes, cn1DecodedBuffer);
70+
long cn1DecodeMs = measureCn1Decode(cn1EncodedBytes);
7271

7372
double encodeRatio = cn1EncodeMs / Math.max(1.0, (double) nativeEncodeMs);
7473
double decodeRatio = cn1DecodeMs / Math.max(1.0, (double) nativeDecodeMs);
@@ -85,12 +84,12 @@ public boolean runTest() {
8584
return true;
8685
}
8786

88-
private static void warmup(Base64Native nativeBase64, String payload, byte[] payloadBytes, String nativeEncoded, byte[] cn1EncodedBytes, byte[] cn1DecodedBuffer) {
87+
private static void warmup(Base64Native nativeBase64, String payload, byte[] payloadBytes, String nativeEncoded, byte[] cn1EncodedBytes) {
8988
for (int i = 0; i < 40; i++) {
9089
nativeBase64.encodeUtf8(payload);
91-
Base64.encodeNoNewline(payloadBytes, cn1EncodedBytes);
90+
Base64.encodeNoNewline(payloadBytes);
9291
nativeBase64.decodeToUtf8(nativeEncoded);
93-
Base64.decode(cn1EncodedBytes, cn1DecodedBuffer);
92+
Base64.decode(cn1EncodedBytes);
9493
}
9594
}
9695

@@ -102,10 +101,10 @@ private static long measureNativeEncode(Base64Native nativeBase64, String payloa
102101
return System.currentTimeMillis() - start;
103102
}
104103

105-
private static long measureCn1Encode(byte[] payloadBytes, byte[] outputBuffer) {
104+
private static long measureCn1Encode(byte[] payloadBytes) {
106105
long start = System.currentTimeMillis();
107106
for (int i = 0; i < ITERATIONS; i++) {
108-
Base64.encodeNoNewline(payloadBytes, outputBuffer);
107+
Base64.encodeNoNewline(payloadBytes);
109108
}
110109
return System.currentTimeMillis() - start;
111110
}
@@ -118,10 +117,10 @@ private static long measureNativeDecode(Base64Native nativeBase64, String encode
118117
return System.currentTimeMillis() - start;
119118
}
120119

121-
private static long measureCn1Decode(byte[] encoded, byte[] outputBuffer) {
120+
private static long measureCn1Decode(byte[] encoded) {
122121
long start = System.currentTimeMillis();
123122
for (int i = 0; i < ITERATIONS; i++) {
124-
Base64.decode(encoded, outputBuffer);
123+
Base64.decode(encoded);
125124
}
126125
return System.currentTimeMillis() - start;
127126
}

0 commit comments

Comments
 (0)