Backend: Spring-style test framework, hellocodenameone e2e server, API cleanup - #5932
shai-almog wants to merge 14 commits into
Conversation
…I cleanup - Public backend API is now what an app writes against: the Spring aliases and settings annotations are gone (application.properties is compiled in instead) and the generated-code plumbing moved to com.codename1.impl.backend, which the backend javadoc excludes. - codenameone-backend-test: @backendtest, MockMvc, TestRestTemplate, @TestConfiguration and @MockitoBean. The same JUnit 5 tests run on the JVM and, with -Dcn1.backend.compiledTests=true, translated into a native test binary. - CORS, multipart/@RequestPart and gzip in the server. - scripts/hellocodenameone/backend replaces demo/cn1ss and the legacy Java server as the single CI server: the screenshot websocket plus the REST surface new device tests in common drive. - Archetype, initializr template, docs and CI wired to all of the above. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They are touched only to mark them @SharedWithBackend, which pulls them into the diff-scoped header check; they keep JCraft's BSD notice. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and-cleanup #5922 moved the plugin's processors and the backend packager into maven/build-engine. Resolved by moving this branch's work onto that layout: - The backend test pass (BackendTestGenerator, BackendTests) lives in the build engine, on its Log/BuildExecutionException types. - The compiled test run is BackendTestPackager, a BackendPackager subclass in the engine, over extension hooks added to BackendPackager. BackendTestMojo is a thin Maven front for it, as BackendPackageMojo is for BackendPackager; the AbstractBackendNativeMojo this branch had is gone. - Gradle backends get the same support: codenameone-backend-test and JUnit 5 on the test classpath, a JVM per test class, the test pass after compileTestJava, and a backendTest task for the compiled run. The Gradle backend template and the initializr's Gradle downloads carry the sample tests, and gradle-smoke runs them. - The Gradle Greeter template uses @component, not the removed @service. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Blog prose gate✅ No net-new prose findings introduced by this PR. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
|
Compared 12 screenshots: 12 matched. |
Native fidelity (Android, Material 3)54 pairs compared -- median 95.6%, worst 91.3% ( Distribution --
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Side-by-side comparisons (worst first)
|
Cloudflare Preview
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 172 screenshots: 172 matched. Benchmark ResultsDetailed Performance Metrics
ParparVM vs HotSpot (JDK 25): Windows x64Runner CPU: Intel64 Family 6 Model 173 Stepping 1, GenuineIntel (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in
Result: no regression |
Native fidelity (ios-27-metal)68 pairs compared -- median 94.6%, worst 74.5% ( Distribution --
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Side-by-side comparisons (worst first)
|
|
Compared 172 screenshots: 172 matched. Benchmark ResultsDetailed Performance Metrics
ParparVM vs HotSpot (JDK 25): Windows arm64Runner CPU: ARMv8 (64-bit) Family 8 Model D49 Revision 0, MICROSOFT CORPORATION (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in
Result: no regression |
Native fidelity (iOS Modern, Metal)68 pairs compared -- median 95.0%, worst 83.5% ( Distribution --
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Side-by-side comparisons (worst first)
|
|
Compared 223 screenshots: 223 matched. |
|
Compared 150 screenshots: 150 matched. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f282a303d5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
CI:
- The packaged archetype server refused to start on the compiled-in
${DATABASE_URL}: an unset reference no longer counts as a configured database
when nothing needs one (Config.resolves); code that needs one still fails loudly.
- The Gradle conversion kept the archetype's untouched backend once it carried the
sample tests; the generated tests and their dependencies are part of the skeleton.
- oteltest used Builder.management(), now internal; BackendOtelTest counted a
bridge method's parameter type as McpServer being linked.
- The device legs' backend tests: a failed request threw instead of failing (the
suite timed out), and a step that hears nothing now fails by name; Android allows
cleartext to the CI host; the CI JS, Linux and Windows launchers instantiate the
generated bootstraps a real build does.
- Ports the tests found broken: the Linux and Windows ports ignored the HTTP method,
the connect and read timeouts, and (Windows) followed redirects natively; watchOS
and tvOS had no HTTP at all (an NSURLSession path); the JS port answered null for
every response header and stored binary bytes, and String.getBytes, unsigned.
- LanguageTool terms.
Review:
- Compression and CORS no longer mutate a Response a handler shares.
- An explicit gzip entry in Accept-Encoding wins over *.
- The compiled runner: unannotated overrides hide inherited tests, package-private
base-class methods are reached through a helper in their package, it runs the
classes Surefire selects, and duplicate injected field names are a build error.
- MockMvc: a lower-case Host is kept, and Content-Type is visible as a header.
- Gradle: Kotlin tests get the test pass, and the properties files are task inputs.
- server.sh installs and digests the build engine.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac8d117962
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
CI: - The timeout probe set setFailSilently(true), and NetworkManager hands a fail-silent request's exception to nobody, overridden handleException included: a timed-out request ended with no callback on every port without native timeouts. The JS port also ignored timeouts; its synchronous XHR now gets xhr.timeout and reports a TimeoutError as an IOException. - The musl leg built the plugin's whole dependency tree inside its container to start the test server, past the suite's wait. prepare-suite now builds a server copy both Linux legs run (CN1SS_SERVER_DIST), and a server that does not start now fails with its own output. Review: - @backendtest holds a JUnit resource lock, so parallel execution never runs two classes that would stop each other's application. - An @BeforeAll assumption that does not hold skips the class in a compiled run. - application.properties and its profile files are compiled in as UTF-8, and a profile file is found without a base file. - Multipart: the part header limit is for the whole block, and a multipart body with no valid boundary is refused. - CORS answers carry Vary: Origin whenever the policy names origins. - MockMvc: queryParam on a multipart builder stays in the URL. - Generated test context and runner names that would collide are a build error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bdc853cf7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 172 screenshots: 172 matched. Benchmark ResultsDetailed Performance Metrics
|
- SurefireSelection honours negative -Dtest entries (!SlowTest). - The compiled test run refuses Kotlin test sources by name instead of running without them; the Gradle task passes the Kotlin test directory so it can tell. - Linux and Windows ports: an explicit GET or POST overrides the post flag, and an explicit GET sends no body. - Multipart: a part ends only at a complete delimiter, so data that contains the boundary followed by more is not cut there. - The compiled runner reports a teardown failure over an earlier assumption abort. - MockMvc drops the body of a HEAD, 1xx, 204, 205 or 304 answer, as the HTTP writers do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Android and the browser dropped the timeout step: the request did time out, but the app's Lifecycle.handleNetworkError consumes every network error (and shows a modal "Connection Error" dialog), and NetworkManager calls a request's own handleException only when no listener consumed it. The probe now carries a ReportsOwnErrors marker that the app's handler leaves alone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d472a33d49
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 157 screenshots: 157 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 172 screenshots: 172 matched. ParparVM vs HotSpot (JDK 25): Linux x64Runner CPU: AMD EPYC 7763 64-Core Processor (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in
Result: no regression |
|
Compared 172 screenshots: 172 matched. ParparVM vs HotSpot (JDK 25): Linux arm64Runner CPU: Neoverse-N2 (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in
Result: no regression |
|
Compared 166 screenshots: 166 matched. Benchmark Results
Detailed Performance Metrics
ParparVM vs HotSpot (JDK 25): macOS arm64Runner CPU: Apple M1 (Virtual) (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 15% (time) / 15% (RAM) away from its baseline in
Result: no regression |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d472a33d49
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 154 screenshots: 154 matched. Benchmark Results
Detailed Performance Metrics
|
The browser leg still dropped the timeout step: ConnectionRequest calls handleException on the network thread, and the probe ran the next step and finished the test from there. Error callbacks now go to the EDT, and the backend client tests get a budget their 12s step watchdog fits inside. Review fixes: - Linux sends a buffered body for any method but GET and HEAD (a DELETE with a body went out empty). - The compiled runner collects default @test and lifecycle methods from implemented interfaces, and an unannotated method suppresses an inherited test only when it really overrides it (package-private methods in another package do not). - @backendtest is @inherited; an abstract base's concrete subclasses each get a generated context. - -Dtest=Class#method narrows the compiled run to the same methods. - Mockito is detected in code, not in comments or strings. - A multipart @RequestParam that is not UTF-8 is a 400, not a 500. - A 206 / Content-Range response is never compressed. - A CORS origin with a path, query or fragment is refused at start. - The shim compares nested primitive arrays by content. - jsonPath exists()/doesNotExist() treat JSON null as absent, as Spring. - getBody() is documented as Spring's string converter behaves: a binary body is decoded when it is valid UTF-8. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Compared 155 screenshots: 155 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 155 screenshots: 155 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06d0d87102
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
CI: - The browser timeout probe: a timed-out synchronous XHR can come back from send() without throwing, and reading on reached responseText, which an arraybuffer request throws for -- ending the network thread with no callback at all. send() now reports status 0 with no response as a failure, and responseText is never read on an arraybuffer request. - Developer guide: a sentence Vale's write-good rule refused. Review fixes: - Request.param throws for a malformed multipart body instead of reading it as "no field"; the generated guard answers that, and a non-UTF-8 field, with 400. - A @RequestBody binding answers a binary body that is not UTF-8 with 400 instead of a 500. - An eligible response varies by Accept-Encoding even when it went out uncompressed. - A gzip request body is decoded member by member; trailing bytes that are not another member are refused. - andExpectAll reports a matcher that throws and runs the rest. - Linux, Windows and JS advertise the read timeouts they now honour. - WebEnvironment.NONE keeps its loopback listener, documented with why; AssertionError(Object) keeps "null", which is what the JDK does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4005c8a041
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The browser's backend timeout probe was not a networking bug. The JS translator's dead-case pass kept a try/catch table entry's handler pc only when ",t:" followed it, so every catch-all handler -- a finally, a synchronized block's release -- lost its case label. An exception unwinding through one was dispatched to a pc with no case, fell to "default: return" and vanished: ConnectionRequest.performOperationComplete returned normally from a timed-out request and nothing heard of it. Reproduced against the CI bundle in headless Chromium, confirmed by restoring the one label, and covered by a translation test that fails without the fix (score 14, not 31). Review fixes: - bodyContent refuses a body over the cap (413), so MockMvc refuses what the listener does. - An empty body declared multipart is malformed, not "no parts". - Windows reports a failed body read (a stall past the read timeout) as an IOException instead of end of stream. - jsonPath isEmpty() needs the path to exist. - HttpStatusCode.isError() is 4xx or 5xx only. - Only a directly nested @TestConfiguration applies to its test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Compared 193 screenshots: 193 matched. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e86e16f00a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ParparVM: a try statement with several catch clauses registers one try
block per clause, and throwException popped only the block that matched.
When that handler threw -- a rethrow or a new exception -- its sibling
handler for the same try was still registered and caught it:
`catch (IOException e) { throw e; } catch (Exception e) {}` swallowed the
rethrow on every native target. Each handler entry now resets
tryBlockOffset to the depth at its label, as JUMP_TO does for a jump.
Found when the JUnit stand-in's assertAll, which rethrows an
OutOfMemoryError ahead of catch (Throwable), behaved differently compiled.
CatchRethrowIntegrationTest covers it on every compiler configuration
(RESULT 104 without the fix, 127 with), and SelfTest's long-skipped
nested-finally probe -- already working, as the same test shows -- now
runs as an ordinary check.
Review fixes:
- Vary given as a List is extended element by element, without duplicates.
- A close delimiter must end at padding, CRLF or the end of the body.
- The boundary search is linear (KMP).
- Form decoding keeps a supplementary character beside + and %.
- A strong ETag keeps its response uncompressed, as Tomcat does.
- assertThrows builds a supplier message only on failure; assertAll
rethrows an OutOfMemoryError; assertIterableEquals recurses.
- Composed @test (and lifecycle) annotations are recognized.
- The compiled run names only the selected tests to javac, finding what
they use through -sourcepath; a non-strict run with nothing to compile
(all Mockito, or nothing selected) warns and skips.
- Gradle runs the test pass over the Java and Kotlin test outputs as one
hierarchy, weaving each class where its file is.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>




















































































































































































































































































































































































Summary
API cleanup. The public backend API is now what an application writes against.
@Service,@Repository,@RequestScope,@SessionScope,@EnableAsync,@EnableScheduling,@EnableMcpServer) and the settings annotations (@ServerConfig,@DataSourceConfig,@SessionConfig,@StaticFilesConfig). The build now compilesapplication.propertiesinto the server instead.Wiring,JsonCodec,AsyncTask,Scheduler, the MCP internals,Backend.Application/Environment, ...) moved tocom.codename1.impl.backend, behind a singleBackendAccessbridge.check-backend-javadoc-internals.shkeeps it out of the backend reference.Test framework (
codenameone-backend-test).@BackendTestwith MOCK, RANDOM_PORT, DEFINED_PORT and NONE web environments,MockMvcwith Spring's builders and matchers,TestRestTemplate,@LocalServerPort,@TestConfigurationand@MockitoBean.process-test-annotations), so nothing is looked up by reflection.-Dcn1.backend.compiledTests=truethey are also translated into a native test binary (cn1:backend-test). That binary compiles against a JUnit subset shim and writesTEST-*-compiled.xml.DATABASE_URLorPORTcan't redirect a test.Server gaps.
cn1.cors.*).multipart/form-datawith@RequestPart, and form fields through@RequestParam.cn1.server.compression.*) and gzip request bodies, using the core JZlib, now@SharedWithBackend.cn1.server.address.hellocodenameone backend.
scripts/hellocodenameone/backendreplacesvm/backend/demo/cn1ssandscripts/common/java/Cn1ssScreenshotServer.javaas the one CI server. It serves the screenshot websocket plus a REST surface.common/.../tests/backenddrive that REST surface throughRest,ConnectionRequest, multipart and a generated@RestClient.Wiring.
backend-compiled-testsjob runs both runners, andbackend-testis added to thepr.ymlgates. Path filters and Windows jobs now point at the new server.Verified locally
core-unittestsline included): 0 findings.maven/backend: 470 tests. Plugin: 2674 tests.vm/tests:BackendHttpIntegrationTest(73),BackendWebSocketIntegrationTest(7, now against the translated hellocodenameone backend) andBackendRuntimeSelfTest.Not verified locally
server.shinstall fallback on a runner with nothing preinstalled.BackendJavaSeRuntimeTestfailed one check ("an oversized header block is refused") on this machine. That is a known artifact of the substitute local JDK 8u372; CI's Temurin 8 is unaffected.🤖 Generated with Claude Code