From fb03a7d314bd4ca7c3b8fc97f8ea73255ea9cae7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 10:42:24 +0000 Subject: [PATCH 1/2] Write the calibration paste block with LF line endings on every platform CalibrationReport.appendPasteBlock wrote its three value lines with String.format("...%n"), the platform line separator, between lines that end in \n. On Windows the block meant for a pom.xml held 3x CRLF and 3x LF, and renderXml_rendersOnePasteBlockPerModelInOrder failed there; the Linux CI never saw it. All lines end in \n now. A new CalibrationReportTest case renders the block in a child JVM started with -Dline.separator=\r\n (the platform separator is fixed at JVM start, so no in-process test can see it): red without the fix on Linux too, green with it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2 --- CHANGELOG.md | 7 +++ .../srcmorph/engine/CalibrationReport.java | 6 +- .../engine/CalibrationReportTest.java | 61 +++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91442a..70b1da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ The release procedure (prompt template and step-by-step instructions) lives in [ ## [Unreleased] +### Fixed +- **The `calibrate` goal's paste block mixed line endings on Windows.** `CalibrationReport` wrote its three + value lines with `String.format("...%n")`, the platform line separator, between lines ending in `\n`, so on + Windows the `` block meant for a `pom.xml` carried 3x CRLF and 3x LF. All lines end in `\n` + now. `CalibrationReportTest` renders the block in a child JVM started with `-Dline.separator=\r\n`, so the + Linux CI sees what only a Windows build showed before (red without the fix). + ### Changed - Build tooling bumped in step with the sibling repositories: `anthropics/claude-code-action` v1.0.258 and `github/codeql-action` v4.39.1. maven-clean-plugin 3.5.0, maven-resources-plugin 3.5.0, diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java index 0bfa139..f5c4883 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java @@ -240,11 +240,11 @@ private static void appendPasteBlock( out.append("\n"); out.append(String.format( Locale.ROOT, - " %.1f%n", + " %.1f\n", m.prefillTokensPerSecond())); out.append(String.format( - Locale.ROOT, " %.1f%n", m.decodeTokensPerSecond())); - out.append(String.format(Locale.ROOT, " %.2f%n", m.charsPerToken())); + Locale.ROOT, " %.1f\n", m.decodeTokensPerSecond())); + out.append(String.format(Locale.ROOT, " %.2f\n", m.charsPerToken())); out.append("\n"); } diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java index ef98146..eed1b09 100644 --- a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java @@ -9,6 +9,11 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -87,6 +92,62 @@ public void renderXml_rendersOnePasteBlockPerModelInOrder() { assertThat(report.renderXml(), is(expected)); } + @Test + public void renderXml_endsEveryLineWithLineFeedOnACrLfPlatform() throws IOException, InterruptedException { + // String.format's %n is the platform line separator, fixed when the JVM starts, so only a JVM + // whose separator is CRLF (Windows) shows a mixed block; on Linux this test starts one. + final List command = new ArrayList<>(); + command.add(Paths.get(System.getProperty("java.home"), "bin", "java").toString()); + if (!"\r\n".equals(System.lineSeparator())) { + command.add("-Dline.separator=\r\n"); + } + command.add("-cp"); + command.add(System.getProperty("java.class.path")); + command.add(RenderXmlInThisJvm.class.getName()); + final Process process = new ProcessBuilder(command).start(); + final byte[] stdout = readAll(process.getInputStream()); + final byte[] stderr = readAll(process.getErrorStream()); + final int exit = process.waitFor(); + + assertThat(new String(stderr, StandardCharsets.UTF_8), exit, is(0)); + final String xml = new String(stdout, StandardCharsets.UTF_8); + assertThat(xml, not(containsString("\r"))); + assertThat(xml, is(RenderXmlInThisJvm.EXPECTED)); + } + + private static byte[] readAll(final InputStream in) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final byte[] buffer = new byte[8192]; + for (int n; (n = in.read(buffer)) != -1; ) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + /** Renders one block in a JVM started with a CRLF line separator; exit 2 when it is not one. */ + static final class RenderXmlInThisJvm { + + static final String EXPECTED = "\n" + + "\n" + + " 1000.5\n" + + " 200.3\n" + + " 4.57\n" + + "\n"; + + private RenderXmlInThisJvm() {} + + public static void main(final String[] args) throws IOException { + if (!"\r\n".equals(System.lineSeparator())) { + System.exit(2); + } + final CalibrationReport report = + new CalibrationReport(Collections.singletonList(new CalibrationReport.ModelMeasurement( + "model-a", new AiCalibrationMeasurement(1.234d, 1000.5d, 200.3d, 4.57d, 900.0d, 128)))); + System.out.write(report.renderXml().getBytes(StandardCharsets.UTF_8)); + System.out.flush(); + } + } + @Test public void toString_containsModelKey() { final CalibrationReport report = new CalibrationReport(Arrays.asList(new CalibrationReport.ModelMeasurement( From 0b01a97d29e647b6b527db4a7ed2b88ebacc8289 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 11:23:17 +0000 Subject: [PATCH 2/2] Fail the CRLF child JVM with an exception instead of System.exit The child of renderXml_endsEveryLineWithLineFeedOnACrLfPlatform guarded against a separator that did not take effect with System.exit(2). It throws an IllegalStateException now (review on #236): the child still ends with a failure status, and the parent's assertion shows the reason from stderr instead of a bare exit code. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2 --- .../ladenthin/srcmorph/engine/CalibrationReportTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java index eed1b09..4db2574 100644 --- a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java @@ -124,7 +124,7 @@ private static byte[] readAll(final InputStream in) throws IOException { return out.toByteArray(); } - /** Renders one block in a JVM started with a CRLF line separator; exit 2 when it is not one. */ + /** Renders one block in a JVM started with a CRLF line separator; fails when it is not one. */ static final class RenderXmlInThisJvm { static final String EXPECTED = "\n" @@ -138,7 +138,9 @@ private RenderXmlInThisJvm() {} public static void main(final String[] args) throws IOException { if (!"\r\n".equals(System.lineSeparator())) { - System.exit(2); + // Without the CRLF separator the test would pass vacuously; the exception exits the + // child with a failure status and its message reaches the parent on stderr. + throw new IllegalStateException("the line separator of this JVM is not CRLF"); } final CalibrationReport report = new CalibrationReport(Collections.singletonList(new CalibrationReport.ModelMeasurement(