diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..af67f950 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Normalize line endings to LF in every working tree, Windows included. Without this, +# `actions/checkout` honours Windows' default `core.autocrlf=true` and checks out CRLF, so tests +# that compare formatter output (LF) against a `stripMargin` source literal (CRLF) fail — e.g. +# JSONTest and YAMLFormatterTest on the Scala Native (Windows) CI job. +* text=auto eol=lf diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94eade00..1221d91b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -205,18 +205,10 @@ jobs: echo "LIB=$libDir;$env:LIB" >> $env:GITHUB_ENV echo "$root\bin" >> $env:GITHUB_PATH shell: pwsh - # `testOnly` still links the whole native test binary, so both of uni's C shims get compiled and - # linked for Windows here; running the http suite then exercises uni_socket_shim.c (WSAStartup / - # WSAPoll / closesocket) and uni_curl_shim.c (GetProcAddress over EnumProcessModules) for real. - # - # Deliberately not the full `projectNative/test` yet. Six tests outside http fail on Windows, all - # of them predating this job and none socket-related: IOSymlinkTest (3 — uni itself raises - # `createSymlink is not supported on Scala Native + Windows`, so they want skipping there), - # IOPathTest's `resolve child path` (a Windows-naive expectation), and JSONTest / - # YAMLFormatterTest (their `stripMargin` literals arrive CRLF, as the repo has no .gitattributes, - # while the formatters emit LF). Fix those, then widen this step to projectNative/test. - - name: Scala Native test (http) - run: JVM_OPTS=-Xmx4g ./sbt "uniNative/testOnly wvlet.uni.http.*" + # Exercises uni_socket_shim.c (WSAStartup / WSAPoll / closesocket) and uni_curl_shim.c + # (GetProcAddress over EnumProcessModules) at runtime, not merely at compile time. + - name: Scala Native test + run: JVM_OPTS=-Xmx4g ./sbt "projectNative/test" shell: bash - name: Publish Test Report uses: mikepenz/action-junit-report@v6 diff --git a/adr/2026-07-08-native-socket-shim.md b/adr/2026-07-08-native-socket-shim.md index 6a51252b..99d94fd2 100644 --- a/adr/2026-07-08-native-socket-shim.md +++ b/adr/2026-07-08-native-socket-shim.md @@ -128,18 +128,20 @@ same on both platforms. `winsock2.h` also supplies `POLLERR`, `POLLHUP` and now what would have caught v2026.1.17's `` regression, and it exercises both shims at runtime. `NativeServerTest` passes 21/21 there, keep-alive and read timeouts (the `WSAPoll` timeout paths) and WebSocket upgrade included. -- That job runs `uniNative/testOnly wvlet.uni.http.*`, not the full - `projectNative/test`. `testOnly` still links the entire test binary, so both - shims are compiled and linked for Windows either way. Six tests outside `http` - fail there, all predating the job and none socket-related — they are what stands - between this and the full suite: - - `IOSymlinkTest` (3): uni raises `createSymlink is not supported on Scala Native - + Windows`; the tests need skipping on that platform. - - `IOPathTest.resolve child path`: the expectation is Windows-naive. - - `JSONTest` / `YAMLFormatterTest`: their `stripMargin` literals arrive CRLF (the - repo has no `.gitattributes`, and `actions/checkout` leaves Windows' - `core.autocrlf=true`), while the formatters emit LF. `* text=auto eol=lf` would - fix both. +- That job runs the full `projectNative/test`. It first landed scoped to + `wvlet.uni.http.*`, because six non-socket tests failed on Windows — all + predating the job, surfaced only once the binary could run there. They were then + fixed so the job could widen: + - `IOSymlinkTest` (3): symlinks are unsupported on Scala Native + Windows (uni's + own `createSymlink` / `readSymlink` throw there), so the tests `skip` when + `IOPath.isWindows`. + - `IOPathTest.resolve child path`: the expectation mixed a literal `/` prefix with + `IOPath.separator`; `.path` renders every separator with the platform's, so on + Windows it is `\home\user\...`. Built the expectation with `separator` + throughout, matching the sibling assertion. + - `JSONTest` / `YAMLFormatterTest`: their `stripMargin` literals arrived CRLF + (`actions/checkout` honours Windows' `core.autocrlf=true`) while the formatters + emit LF. A repo `.gitattributes` (`* text=auto eol=lf`) forces LF everywhere. - `NativeSocket` no longer imports `scalanative.posix.poll` or `scalanative.posix.unistd`. Re-adding either would re-break Windows; the CI job is what notices. diff --git a/uni/src/test/scala/wvlet/uni/io/IOPathTest.scala b/uni/src/test/scala/wvlet/uni/io/IOPathTest.scala index 6d2bc7bf..c69cd560 100644 --- a/uni/src/test/scala/wvlet/uni/io/IOPathTest.scala +++ b/uni/src/test/scala/wvlet/uni/io/IOPathTest.scala @@ -59,7 +59,11 @@ class IOPathTest extends UniTest: test("resolve child path") { val base = IOPath.parse("/home/user") val child = base.resolve("docs/file.txt") - child.path shouldBe s"/home/user${IOPath.separator}docs${IOPath.separator}file.txt" + // `.path` renders every separator with the platform one, the leading segment included, so build + // the expectation the same way — on Windows this is `\home\user\docs\file.txt`. `.posixPath` is + // the forward-slash form. + val sep = IOPath.separator + child.path shouldBe s"${sep}home${sep}user${sep}docs${sep}file.txt" child.posixPath shouldBe "/home/user/docs/file.txt" } diff --git a/uni/src/test/scala/wvlet/uni/io/IOSymlinkTest.scala b/uni/src/test/scala/wvlet/uni/io/IOSymlinkTest.scala index adbf694c..d3f04130 100644 --- a/uni/src/test/scala/wvlet/uni/io/IOSymlinkTest.scala +++ b/uni/src/test/scala/wvlet/uni/io/IOSymlinkTest.scala @@ -20,7 +20,15 @@ class IOSymlinkTest extends UniTest: private lazy val testDir: IOPath = FileSystem.createTempDirectory("symlink-test") + // Scala Native on Windows has no POSIX symlink support: FileSystem.createSymlink / readSymlink + // throw UnsupportedOperationException there by design (MSVC's CRT exports no `symlink`/`readlink`). + // `inline` so `skip`'s TestSource resolves to the calling test, not this helper. + private inline def requireSymlinkSupport(): Unit = + if IOPath.isWindows then + skip("Symlinks are not supported on Scala Native + Windows") + test("createSymlink and readSymlink for a file") { + requireSymlinkSupport() val target = testDir / "target.txt" val link = testDir / "link.txt" @@ -33,6 +41,7 @@ class IOSymlinkTest extends UniTest: } test("info reports SymbolicLink type") { + requireSymlinkSupport() val target = testDir / "info-target.txt" val link = testDir / "info-link.txt" @@ -44,6 +53,7 @@ class IOSymlinkTest extends UniTest: } test("createSymlink for a directory") { + requireSymlinkSupport() val targetDir = testDir / "target-dir" val linkDir = testDir / "link-dir"