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..4db2574 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,64 @@ 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; fails 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())) { + // 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( + "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(