Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
aad27d0
Report the real port status, and fix the desktop-port defects it was …
shai-almog Jul 31, 2026
3c58dcc
Address review: validate before publishing, wire the suite gate, fix …
shai-almog Jul 31, 2026
a2cb05f
Fix the desktop-port file paths the louder IO errors exposed
shai-almog Jul 31, 2026
8dd4c6a
Implement the desktop crypto bridge, and fix the shared UTF-8 buffer …
shai-almog Jul 31, 2026
7cb8f3e
Name the test that wedges the suite instead of letting the run die qu…
shai-almog Jul 31, 2026
0c6c970
Give the Windows port real named-time-zone offsets
shai-almog Jul 31, 2026
19255eb
Report the wedging test through the harness, not a forbidden exit call
shai-almog Jul 31, 2026
902b535
Address the second review round across crypto, time zones and the sweep
shai-almog Jul 31, 2026
81fe244
Pad OAEP in the Windows port and encode ECDSA signatures as DER
shai-almog Jul 31, 2026
9ef1a7b
Pad OAEP in the Windows port and encode ECDSA signatures as DER
shai-almog Jul 31, 2026
879f159
Resolve Windows ICU at runtime so the clean target still links
shai-almog Aug 1, 2026
e04582e
Keep the wedge watchdog off the JavaScript port
shai-almog Aug 1, 2026
887e31c
Keep the Windows natives and the runner's lambda numbering intact
shai-almog Aug 1, 2026
3eee8ab
Address the third review round: OAEP, ECDSA DER and shared error state
shai-almog Aug 1, 2026
4244c0c
Deliver a health result on the EDT even to a listener that arrives late
shai-almog Aug 1, 2026
e78fa04
Address the fourth review round, and the health delivery fix's last c…
shai-almog Aug 1, 2026
880bc28
Give the Linux browser its JS bridge instead of navigating to codenam…
shai-almog Aug 1, 2026
445dfd3
Give LinuxBrowserComponent a copyright header and an accurate descrip…
shai-almog Aug 1, 2026
29dc949
Stop the browser test from accepting a blank frame as a rendered page
shai-almog Aug 1, 2026
9c37773
Add the Linux golden for the browser test's surrounding form
shai-almog Aug 1, 2026
d4da95c
Add the arm64 golden for the browser test
shai-almog Aug 1, 2026
fc39076
Refuse crypto names outside the advertised set, and read the raw offs…
shai-almog Aug 1, 2026
eeb59d1
Make a documented skip prove itself, and compare timestamps as instants
shai-almog Aug 1, 2026
2ff146c
Document encoder skips per port instead of failing the Apple runs
shai-almog Aug 1, 2026
1b30b06
Honour the requested UI-settle budget, and say when it runs out
shai-almog Aug 1, 2026
a3bbdd0
Keep identifier-ignorable characters out of identifier starts
shai-almog Aug 1, 2026
6e05a5c
Align the iOS OAEP mask with every other port, and check the key fami…
shai-almog Aug 1, 2026
e8bf68e
Require the exact key family, and let a dispatched run repair a stale…
shai-almog Aug 1, 2026
0f7eed2
Arm the Linux gates that were letting unrun tests pass as success
shai-almog Aug 2, 2026
3d4051f
Stop a closed MCP transport from stranding the process-wide registration
shai-almog Aug 2, 2026
34087aa
Name the test the Linux suite stops in
shai-almog Aug 2, 2026
5cdb9cc
Decide the MCP listener publication under the lock close() uses
shai-almog Aug 2, 2026
fb754a9
Remove the in-process wedge watchdog that deadlocked the Linux suite
shai-almog Aug 2, 2026
9ce6695
Standardise OAEP on SHA-256 for both halves, the only pairing every p…
shai-almog Aug 2, 2026
5d14fc4
Put getTimezoneOffset back on UTC fields, and match the OAEP name exa…
shai-almog Aug 2, 2026
ee68937
Mark each stage of a test's dispatch so a dead suite says where it st…
shai-almog Aug 2, 2026
63a2a31
Give the Linux suite the time its repaired tests now need
shai-almog Aug 2, 2026
f53b1bd
Line-buffer the generated main's stdout so a killed run's log is not …
shai-almog Aug 2, 2026
7d0ad8c
Ask Gradle for the stack when Android packaging fails
shai-almog Aug 2, 2026
39fa46b
Use _IONBF: the MSVC CRT fail-fasts on a line-buffered request
shai-almog Aug 3, 2026
a70e121
Revert the timeout inflation; my own evidence had already disproved it
shai-almog Aug 3, 2026
7f440dd
Photograph the Linux stall while it is stalled, not once it has settled
shai-almog Aug 3, 2026
af9944c
Separate each stall sample with a timestamped header
shai-almog Aug 3, 2026
1092abd
Address the review round: OAEP key sizes, DER strictness, verify erro…
shai-almog Aug 3, 2026
2d07450
Run every GtkClipboard call on the GTK main thread
shai-almog Aug 3, 2026
c3d1c98
Keep publishing the Linux port status when the suite fails
shai-almog Aug 3, 2026
6ff6b69
Serialise the iOS path-renderer's process-wide setup
shai-almog Aug 3, 2026
3213bd3
Fix the sweep's jq invocation and a rejected-transformation key leak
shai-almog Aug 3, 2026
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
41 changes: 38 additions & 3 deletions .github/workflows/linux-build-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ jobs:
cmake ninja-build pkg-config unzip xvfb fonts-dejavu-core \
libgtk-3-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev libglib2.0-dev \
libfontconfig1-dev libfreetype-dev \
libcurl4-openssl-dev \
libcurl4-openssl-dev libssl-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
libwebkit2gtk-4.1-dev libsecret-1-dev libnotify-dev libgeoclue-2-dev \
libepoxy-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri
Expand Down Expand Up @@ -219,6 +219,18 @@ jobs:
# Full app stdout/stderr -- the only evidence when the suite wedges
# mid-run (uploaded with the screenshot artifact below).
CN1_APP_LOG_TEE: ${{ github.workspace }}/artifacts/linux-port/raw/app-output.log
# Wait for the suite's own completion marker instead of stopping when
# screenshots go quiet. The stabilization exit fired while DesktopMode,
# the VideoIO grid, the VR scene and the 360 panorama were still queued
# behind the slow non-rendering API tail, so the suite was force-killed
# and every trailing test was published as "never run".
#
# This is read with Boolean.parseBoolean, which answers false for '1',
# so the gate it describes has never actually been armed -- the suite
# could be force-killed with trailing tests unrun and nothing failed.
# The Windows pipeline passes a real boolean, which is why its gate
# works. 'true' arms it here.
CN1_REQUIRE_SUITE: 'true'
Comment thread
shai-almog marked this conversation as resolved.
# Build the native ELF (and the demo) against an old glibc for portability.
CN1_CC: /usr/local/bin/cn1-zig-cc
# After the suite runs, the capture test relinks the same objects into a
Expand All @@ -238,6 +250,12 @@ jobs:
# Enable core dumps and post-mortem them into the artifact.
ulimit -c unlimited
echo '/tmp/cn1-cores/core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern >/dev/null
# Let the harness attach gdb to the still-running suite when it gives up
# waiting. Ubuntu ships yama ptrace_scope=1, which restricts attaching to
# descendants, and the harness is a sibling of the app -- so without this
# the hang dump comes back "Could not attach to process" and a hang (as
# opposed to a crash, which leaves a core) yields no evidence at all.
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope >/dev/null 2>&1 || true
mkdir -p /tmp/cn1-cores
rc=0
mvn -B clean package -pl JavaAPI -am -DskipTests
Expand Down Expand Up @@ -320,12 +338,13 @@ jobs:
-v "$GITHUB_WORKSPACE":/cn1 -w /cn1 \
-e CN1_SHOT_OUTPUT_DIR=/cn1/artifacts/linux-port/raw-musl \
-e CN1_APP_LOG_TEE=/cn1/artifacts/linux-port/raw-musl/app-output.log \
-e CN1_REQUIRE_SUITE=true \
-e LIBGL_ALWAYS_SOFTWARE=1 \
docker.io/library/alpine:3.20 sh -ec '
sed -i "s|^#\(.*/community\)|\1|" /etc/apk/repositories
apk add --no-cache build-base cmake samurai pkgconf bash git openjdk8 openjdk17 maven \
gtk+3.0-dev cairo-dev pango-dev gdk-pixbuf-dev glib-dev fontconfig-dev freetype-dev \
curl-dev libepoxy-dev mesa-dev mesa-gles mesa-egl mesa-gbm mesa-dri-gallium \
curl-dev openssl-dev libepoxy-dev mesa-dev mesa-gles mesa-egl mesa-gbm mesa-dri-gallium \
webkit2gtk-4.1-dev gstreamer-dev gst-plugins-base-dev \
libsecret-dev libnotify-dev geoclue-dev xvfb ttf-dejavu
# JDK 8 runs the translator/maven; JDK 17 is needed to compile the
Expand Down Expand Up @@ -362,7 +381,16 @@ jobs:
compare-comment:
name: screenshot-comment
needs: build-run
if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
# !cancelled() rather than a plain event check: without it GitHub skips this
# job whenever a build-run leg fails, and skipping it is precisely the wrong
# response to a failing suite. Normalization is what publishes the fail /
# not-run counts, so being skipped leaves the public table showing the last
# green report -- a failure masked as a pass. The workflow still goes red
# because build-run itself failed; this only keeps the reporting honest.
if: >-
!cancelled() &&
(github.event_name == 'pull_request' || github.event_name == 'push' ||
github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -415,6 +443,13 @@ jobs:
# Gate the Linux port (both arches): fail on any mismatch/error or a new
# screenshot that has no committed golden (missing_expected).
export CN1SS_FAIL_ON_MISMATCH=1
# A test that fails an assertion, or never runs at all, has to fail
# this workflow the way it already does on iOS, JavaScript and Mac.
# Without it a suite that stopped early published its trailing tests
# as "never run" and the job stayed green -- a result that reads as
# success while hiding both the tests that failed and the fact that
# they stopped running.
export CN1SS_FAIL_ON_TEST_PROBLEMS=1
export CN1SS_ALLOWED_MISSING=0
if [ "${{ github.event_name }}" != "pull_request" ]; then export CN1SS_SKIP_COMMENT=1; fi
for arch in x64 arm64; do
Expand Down
22 changes: 21 additions & 1 deletion .github/workflows/port-status-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ permissions:
contents: write

jobs:
# port-status-publish.yml only publishes a report when a workflow_run event
# reaches it, and those events never arrive for some producers -- the Linux
# and Windows suites had not landed a single report, so the public table
# served their checked-in fallback until it aged out and the columns rendered
# as unknown. This sweep publishes from the newest master run of every
# producing workflow and fails when a port has no report inside the
# contract's staleness window.
publish-latest-port-reports:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Publish the newest master report for every port
env:
GH_TOKEN: ${{ github.token }}
run: scripts/hellocodenameone/conformance/backfill_port_status.sh

build-javascript-app:
runs-on: ubuntu-latest
timeout-minutes: 60
Expand Down Expand Up @@ -81,8 +99,10 @@ jobs:
if-no-files-found: error

publish-browser-evidence:
# Ordered after the report sweep (and tolerant of it failing) so the site
# rebuild at the end of this job picks up everything published tonight.
if: always() && needs.build-javascript-app.result == 'success'
needs: [build-javascript-app, browser-lifecycle]
needs: [build-javascript-app, browser-lifecycle, publish-latest-port-reports]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/scripts-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ jobs:

env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-ios-app.sh writes it as a bundled resource so
# the GoogleWebMap screenshot test renders a live Google map; absent (e.g.
Expand Down Expand Up @@ -297,6 +302,11 @@ jobs:

env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-ios-app.sh writes it as a bundled resource so
# the GoogleWebMap screenshot test renders a live Google map; absent (e.g.
Expand Down Expand Up @@ -578,6 +588,11 @@ jobs:

env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-ios-app.sh writes it as a bundled resource so
# the GoogleWebMap screenshot test renders a live Google map; absent (e.g.
Expand Down Expand Up @@ -745,6 +760,11 @@ jobs:

env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}

steps:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/scripts-javascript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ jobs:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/javascript-ui-tests
# CN1_JS_TIMEOUT_SECONDS guards the per-suite SUITE:FINISHED wait.
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/scripts-mac-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ jobs:

env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# Optional: when set, build-mac-native-app.sh writes it as a bundled
# resource so the GoogleWebMap screenshot test renders a live Google map;
Expand Down
70 changes: 70 additions & 0 deletions CodenameOne/src/com/codename1/impl/health/EdtResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
package com.codename1.impl.health;

import com.codename1.ui.Display;
import com.codename1.util.AsyncResource;
import com.codename1.util.EasyThread;
import com.codename1.util.SuccessCallback;

/// The resource every public health operation hands back: one outcome,
/// delivered on the EDT.
Expand Down Expand Up @@ -65,6 +68,73 @@ public void error(Throwable t) {
Display.getInstance().callSerially(new Deliver<T>(this, null, t));
}

/// Completing on the EDT is only half of the guarantee. `AsyncResource`
/// runs a callback registered against an already-finished resource
/// immediately, on whichever thread registered it, so the outcome landing
/// on the EDT does not mean the callback does.
///
/// That is the ordinary case for the operations that resolve before they
/// return -- the facade's `openHealthSettings` and `openProviderSetup`
/// complete inside the call -- where the delivery thread came down to
/// whether the EDT had drained the hop above before the caller got as far
/// as `onResult`. The same call arrived on the EDT or off it from one run
/// to the next.
///
/// Wrapping every callback closes that half. A callback already reached on
/// the EDT sees `isEdt()` and runs inline, so this costs a branch and
/// never an extra queued runnable. Both arities funnel through the
/// `EasyThread` overloads, so overriding these two covers `ready`,
/// `except` and `onResult` alike.
///
/// A caller who names an `EasyThread` is asking for delivery there
/// specifically, which is the point of that overload, so those are left
/// alone -- the default is the EDT, not an override of an explicit choice.
@Override
public AsyncResource<T> ready(SuccessCallback<T> callback, EasyThread t) {
return super.ready(t == null ? new OnEdt<T>(callback) : callback, t);
}

@Override
public AsyncResource<T> except(SuccessCallback<Throwable> callback, EasyThread t) {
return super.except(t == null ? new OnEdt<Throwable>(callback) : callback, t);
}

/// Named rather than anonymous so the hop carries no synthetic reference
/// to anything enclosing (SpotBugs `SIC_INNER_SHOULD_BE_STATIC_ANON`).
private static final class OnEdt<V> implements SuccessCallback<V> {

private final SuccessCallback<V> delegate;

OnEdt(SuccessCallback<V> delegate) {
this.delegate = delegate;
}

@Override
public void onSucess(V value) {
if (Display.getInstance().isEdt()) {
delegate.onSucess(value);
return;
}
Display.getInstance().callSerially(new Invoke<V>(delegate, value));
}
}

private static final class Invoke<V> implements Runnable {

private final SuccessCallback<V> delegate;
private final V value;

Invoke(SuccessCallback<V> delegate, V value) {
this.delegate = delegate;
this.value = value;
}

@Override
public void run() {
delegate.onSucess(value);
}
}

/// Named rather than anonymous so the hop carries no synthetic reference
/// to anything enclosing (SpotBugs `SIC_INNER_SHOULD_BE_STATIC_ANON`).
private static final class Deliver<T> implements Runnable {
Expand Down
63 changes: 56 additions & 7 deletions CodenameOne/src/com/codename1/mcp/MCPLoopbackSocketTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,25 @@ public void open() throws IOException {
}
active = this;
}
// open() runs on the server's reader thread, so a stop() from another thread can
// already have closed this transport, or close it while we are binding. Two
// things have to be true afterwards whichever order those land in: the
// process-wide registration must not stay claimed by a transport that never
// listens (every later open() would then be refused with "already open on port
// N" for the life of the process), and no listener may be left bound with
// nobody holding a reference to stop it -- an orphan keeps the port and its
// connection callbacks still consult `active`, so it could hand a connection to
// a later transport.
//
// Publishing `listening` under the same lock close() uses is what makes that
// decidable. The bind itself stays outside the lock -- Socket.listenLoopback is
// a platform call and holding a lock the connection callback also takes across
// it invites a deadlock -- so the check is "did close() win?" immediately after,
// and the loser cleans up. Either close() sees a published listener and stops
// it, or we see closed and stop it ourselves.
Socket.StopListening bound;
try {
listening = Socket.listenLoopback(port, Connection.class);
bound = Socket.listenLoopback(port, Connection.class);
} catch (RuntimeException ex) {
// Two things go wrong if this escapes. The process-wide registration would stay
// pointing at a transport that never started listening, so every later open()
Expand All @@ -132,6 +149,21 @@ public void open() throws IOException {
failure.initCause(ex);
throw failure;
}
boolean closedWhileBinding;
synchronized (lock) {
closedWhileBinding = closed;
if (!closedWhileBinding) {
listening = bound;
}
}
if (closedWhileBinding) {
// No null check on `bound`: the only way past the try above is with a
// listener in hand, and SpotBugs flags the redundant test. Stop before
// releasing the slot, for the reason close() gives.
bound.stop();
Comment thread
shai-almog marked this conversation as resolved.
clearActiveIfOurs();
throw new IOException("This MCP socket transport was closed before it began listening");
}
}

/// Releases the process-wide registration, but only when it is still this transport's.
Expand All @@ -149,10 +181,21 @@ void attach(InputStream is, OutputStream os) {
InputStream previousIn; // NOPMD closed below, deliberately outside the lock
OutputStream previousOut; // NOPMD closed below, outside the lock
synchronized (lock) {
previousIn = in;
previousOut = out;
in = is;
out = os;
if (closed) {
// A listener retired by close() can still have a connection in
// flight. Adopting it would serve a client that dialled a port
// this transport no longer owns, so hand the streams back to be
// closed rather than wiring them to a dead session.
previousIn = is;
previousOut = os;
is = null;
os = null;
} else {
previousIn = in;
previousOut = out;
in = is;
out = os;
}
lock.notifyAll();
}
// Dropping the previous client means closing its streams, not just forgetting
Expand Down Expand Up @@ -369,11 +412,17 @@ public void close() {
out = null;
lock.notifyAll();
}
// Only if it is still ours: a transport opened after this one keeps its slot.
clearActiveIfOurs();
// Stop the listener BEFORE releasing the registration, not after. A
// connection this listener already accepted resolves `active` inside its
// callback, so releasing first leaves a window where a replacement
// transport has taken the slot and the retired listener hands it a client
// that dialled the old port. Stopping first means any in-flight callback
// still finds this transport, and attach() below refuses it because we
// are closed.
if (l != null) {
l.stop();
}
clearActiveIfOurs();
// Closing the output as well as the input: forgetting the field is not enough,
// because a writer that already captured it would go on writing to a session that
// has ended, and the socket would stay open until the connection callback unwound.
Expand Down
Loading
Loading