From 1960cd1e9e01aad8e7f0450c5a512e9b41c3729d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 13:26:01 +0000 Subject: [PATCH 1/2] fix: use TcpListener(0) and StartAsync in XmlExtensions tests to avoid Windows CI failures On Windows CI, Hyper-V and Docker reserve ranges of ports that are not visible via GetActiveTcpListeners(). The previous random-port approach could pick a port in one of these excluded ranges, causing Kestrel to fail with SocketException (10013: WSAEACCES) when binding. Replace with the same TcpListener(0) approach already used in Http.fs: ask the OS to bind port 0, read the assigned port (guaranteed not excluded), then release the listener before Kestrel binds it. Also switch from RunAsync (fire-and-forget) to StartAsync, which returns only after the server is listening. This removes the need for Thread.Sleep(100) in every test and makes server readiness deterministic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/FSharp.Data.Core.Tests/XmlExtensions.fs | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/tests/FSharp.Data.Core.Tests/XmlExtensions.fs b/tests/FSharp.Data.Core.Tests/XmlExtensions.fs index 4520a060a..02b4344d9 100644 --- a/tests/FSharp.Data.Core.Tests/XmlExtensions.fs +++ b/tests/FSharp.Data.Core.Tests/XmlExtensions.fs @@ -9,7 +9,7 @@ open FSharp.Data.HttpRequestHeaders open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Http open System.Threading.Tasks -open System.Net.NetworkInformation +open System.Net.Sockets open System.IO open System.Text @@ -50,29 +50,31 @@ let startXmlHttpLocalServer() = } |> Async.StartAsTask :> Task )) |> ignore + // Use TcpListener(0) to ask the OS for a free port, then release it. + // This avoids Windows excluded port ranges (reserved by Hyper-V/Docker) that + // caused intermittent SocketException (10013: WSAEACCES) on Windows CI. let freePort = - let random = new System.Random() - let mutable port = random.Next(10000, 65000) - while - IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners() - |> Array.map (fun x -> x.Port) - |> Array.contains port do - port <- random.Next(10000, 65000) + let listener = new TcpListener(System.Net.IPAddress.Loopback, 0) + listener.Start() + let port = (listener.LocalEndpoint :?> System.Net.IPEndPoint).Port + listener.Stop() port let baseAddress = $"http://127.0.0.1:{freePort}" - let workerTask = app.RunAsync(baseAddress) + + // Use StartAsync so the server is guaranteed ready before we return + app.Urls.Add(baseAddress) + app.StartAsync() |> Async.AwaitTask |> Async.RunSynchronously { new ITestHttpServer with - member this.Dispose() = - app.StopAsync() |> Async.AwaitTask |> ignore - member this.WorkerTask = workerTask - member this.BaseAddress = baseAddress } + member _.Dispose() = + app.StopAsync() |> Async.AwaitTask |> Async.RunSynchronously + member _.WorkerTask = Task.CompletedTask + member _.BaseAddress = baseAddress } [] let ``XElement.Request sends XML via POST by default`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) // Let server start let xml = XElement(XName.Get("test"), "sample content") let response = xml.Request(localServer.BaseAddress + "/echo") @@ -85,7 +87,6 @@ let ``XElement.Request sends XML via POST by default`` () = [] let ``XElement.Request with custom HTTP method`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test"), "content") let response = xml.Request(localServer.BaseAddress + "/test/PUT", httpMethod = HttpMethod.Put) @@ -98,7 +99,6 @@ let ``XElement.Request with custom HTTP method`` () = [] let ``XElement.Request includes default User-Agent header`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let response = xml.Request(localServer.BaseAddress + "/echo") @@ -109,7 +109,6 @@ let ``XElement.Request includes default User-Agent header`` () = [] let ``XElement.Request with custom headers`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let customHeaders = [("X-Custom-Header", "test-value")] @@ -120,7 +119,6 @@ let ``XElement.Request with custom headers`` () = [] let ``XElement.Request preserves existing User-Agent when provided`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let customHeaders = [UserAgent "CustomAgent/1.0"] @@ -131,7 +129,6 @@ let ``XElement.Request preserves existing User-Agent when provided`` () = [] let ``XElement.Request includes XML content type header`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let response = xml.Request(localServer.BaseAddress + "/echo") @@ -142,7 +139,6 @@ let ``XElement.Request includes XML content type header`` () = [] let ``XElement.Request with complex XML structure`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("root"), @@ -163,7 +159,6 @@ let ``XElement.Request with complex XML structure`` () = [] let ``XElement.RequestAsync sends XML via POST by default`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test"), "async content") let response = xml.RequestAsync(localServer.BaseAddress + "/echo") |> Async.RunSynchronously @@ -176,7 +171,6 @@ let ``XElement.RequestAsync sends XML via POST by default`` () = // [] // let ``XElement.RequestAsync with custom HTTP method`` () = // use localServer = startXmlHttpLocalServer() -// System.Threading.Thread.Sleep(100) // let xml = XElement(XName.Get("test")) // let response = xml.RequestAsync(localServer.BaseAddress + "/test/PUT", httpMethod = HttpMethod.Put) |> Async.RunSynchronously @@ -189,7 +183,6 @@ let ``XElement.RequestAsync sends XML via POST by default`` () = [] let ``XElement.RequestAsync with custom headers`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let customHeaders = [("X-Async-Header", "async-value")] @@ -200,7 +193,6 @@ let ``XElement.RequestAsync with custom headers`` () = [] let ``XElement.RequestAsync includes default User-Agent header`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let response = xml.RequestAsync(localServer.BaseAddress + "/echo") |> Async.RunSynchronously @@ -210,7 +202,6 @@ let ``XElement.RequestAsync includes default User-Agent header`` () = [] let ``XElement.RequestAsync preserves existing User-Agent when provided`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("test")) let customHeaders = [UserAgent "AsyncAgent/1.0"] @@ -221,7 +212,6 @@ let ``XElement.RequestAsync preserves existing User-Agent when provided`` () = [] let ``XElement with namespaces serializes correctly`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let ns = XNamespace.Get("http://example.com/test") let xml = XElement(ns + "root", XAttribute(XNamespace.Xmlns + "test", ns.NamespaceName), "content") @@ -235,7 +225,6 @@ let ``XElement with namespaces serializes correctly`` () = [] let ``XElement serialization disables formatting`` () = use localServer = startXmlHttpLocalServer() - System.Threading.Thread.Sleep(100) let xml = XElement(XName.Get("root"), From 103989eaf18847ab803686b59bd5b11ce8b947aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 8 May 2026 13:26:06 +0000 Subject: [PATCH 2/2] ci: trigger checks