Uh oh!
There was an error while loading. Please reload this page.
[tests] Use local servers for networking tests - #12022
Conversation
Replace httpbin.org and badssl.com dependencies in networking tests with local loopback HTTP/HTTPS servers, including self-signed TLS coverage. Update the LinkDesc HttpClient regression to post to a local listener. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates networking-related tests to stop depending on external internet endpoints (httpbin.org/badssl.com) by introducing local loopback HTTP/HTTPS test servers and switching existing redirect/decompression/SSL-validation tests to use them. This improves test reliability in constrained or offline environments and reduces external flakiness.
Changes:
- Added reusable local HTTP/HTTPS test server helpers for Mono.Android.NET-Tests.
- Migrated AndroidMessageHandler redirect/decompression and invalid-certificate tests to use local servers instead of external hosts.
- Updated the LinkDesc HttpClient regression app/test to POST to a local HttpListener endpoint and adjusted the manifest to allow cleartext traffic + INTERNET permission.
Show a summary per file
| File | Description |
|---|---|
| tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | Enables cleartext + INTERNET permission for the LinkDesc regression app so it can talk to local HTTP endpoints. |
| tests/MSBuildDeviceIntegration/Resources/LinkDescTest/HttpClientTest.cs | Replaces external POST target with a local HttpListener-based loopback server. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/LocalTestServers.cs | Introduces local HTTP and HTTPS server helpers for test scenarios (compression, redirects, self-signed certs). |
| tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs | Switches decompression, redirect, and SSL callback tests to local servers. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidHandlerTestBase.cs | Updates redirect tests to use local loopback servers. |
| tests/Mono.Android-Tests/Mono.Android-Tests/System.Net/SslTest.cs | Removes httpbin from the external URL list to reduce dependency on that service. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj | Adds the new LocalTestServers.cs to the test project build. |
Copilot's findings
- Files reviewed: 7/7 changed files
- Comments generated: 4
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Bind local HTTP listeners to loopback, dispose LinkDesc HTTP resources, backdate self-signed certificates further, and assert policy errors in the HTTPS redirect callback test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Share local test server exception handling and shutdown logic, and add intent-level helpers for redirect and OK responses to remove repeated listener setup in networking tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add local HTTP test-server endpoint helpers for the networking tests and LinkDesc HttpClient regression. Scope the generated app's cleartext allowance to localhost via network security config instead of enabling app-wide cleartext traffic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use declaration-style using statements in the local HTTP test helpers and redirect tests to reduce nesting. Opt the generated LinkTestLib project into C# 8 so its embedded helper can use using declarations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the extra LocalHttpTestServer wrapper from Mono.Android networking tests. LocalTestServer now owns shared lifecycle/error handling, LocalHttpServer owns HTTP listener behavior and test endpoints, and LocalHttpsServer remains the HTTPS/self-signed transport helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the Mono.Android HttpListener-based LocalHttpServer with a TCP-based request parser/router shared by LocalHttpsServer. HTTPS now exposes the same /ok and /redirect-to endpoints as HTTP, with TLS layered via SslStream. Rename the generated LinkDesc helper to LinkDescHttpServer to avoid confusion with the Mono.Android helpers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use BaseAddress with httpbin-style relative redirect endpoints in networking tests, name the HTTPS certificate host argument, and rename the generated LinkDesc helper back to LocalHttpServer with a /post endpoint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the duplicate LinkDesc local HTTP server and compile the shared Mono.Android LocalTestServers helper into the generated LinkTestLib project. Make the shared helper netstandard2.0-compatible for the LinkDesc library while keeping HTTPS/Brotli support for the Mono.Android tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
commented
Jul 9, 2026
/review |
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
🤖 Code Review — ⚠️ Needs Changes
Nice cleanup. Replacing httpbin.org / badssl.com with in-process loopback servers removes a real source of external-network flakiness, and LocalTestServers.cs is well thought through:
- Per-response
Connection: closecorrectly forces a fresh TLS handshake, which is what keepsServerCertificateCustomValidationCallback_RedirectsassertingcallbackCounter == 2. - Handler exceptions are captured off the accept loop and surfaced via
AssertNoUnhandledExceptions(), and dispose-timeIOException/ObjectDisposedExceptionare correctly ignored once the handler completed. - Self-signed cert generation with SANs and the HTTPS host pinned to
localhost(matching the cert) is a sensible design.
CI: ✅ All dotnet-android checks are green on bd7911c. mergeable_state: blocked reflects the pending maintainer review, not a CI failure.
Findings (see inline):
⚠️ 1 warning — LinkDescnetwork_security_config.xmlwhitelists cleartext forlocalhost, butLocalHttpServeruses127.0.0.1; the LinkDescHttpClientTestwill hitCleartext ... not permittedonceCustomLinkDescriptionPreserveis un-ignored.- 💡 2 suggestions — port-selection race in
GetAvailablePort()(re-introduces a flaky window); reduced fidelity ofNoServerCertificateCustomValidationCallback_ThrowsWhenThereIsCertificateHostnameMismatch.
Worth double-checking before merge: the two tests that actually exercise the loopback servers end-to-end aren't part of the green CI legs — CustomLinkDescriptionPreserve is ignored for all runtime variants (per the PR description), and the SSL-category AndroidMessageHandlerTests run on a physical device rather than the emulator legs. So the green checks above don't fully validate the new cleartext/loopback paths.
Prioritized: the network-security-config vs. server-host mismatch is the one to fix; the other two are optional.
Generated by Android PR Reviewer for #12022 · 209.6 AIC · ⌖ 27.5 AIC · ⊞ 6.8K
Comment /review to run again
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Bind local test listeners directly to ephemeral ports and align the loopback cleartext configuration. Trust the local certificate before asserting a hostname mismatch so the test isolates name validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9721cbaf-7e53-4fb0-b12c-6c9de7483f9d
Uh oh!
There was an error while loading. Please reload this page.
> Re-opened as a direct PR (supersedes #12098) so the `dotnet-android` pipeline runs automatically without `/azp run` approval. ### Context Following up on #12022 (which migrated most on-device networking tests off live external URLs to the loopback `LocalHttpServer`/`LocalHttpsServer`) and #12058 (which handles `SslTest.HttpsShouldWork`), a few on-device tests were still reaching out to the public internet. Those requests made the tests flaky whenever the external endpoints were slow, rate-limited, or unavailable. This migrates the remaining tests to local loopback servers. ### Tests migrated | Test | Was hitting | | ---- | ----------- | | `SslTest.SslWithinTasksShouldWork` | `dotnet.microsoft.com` | | `ProxyTest.QuoteInvalidQuoteUrlsShouldWork` | `msftconnecttest.com` | | `AndroidMessageHandlerTests.ServerCertificateCustomValidationCallback_ApproveRequest` | `www.microsoft.com` | | `AndroidMessageHandlerTests.ServerCertificateCustomValidationCallback_RejectRequest` | `www.microsoft.com` | | `AndroidMessageHandlerTests.AndroidMessageHandlerSendsClientCertificate` | `corefx-net-tls.azurewebsites.net` | | `AndroidMessageHandlerIntegrationTests.GetString_Many` | `google.com` | | `HttpClientHandlerTestBase.Disposed` | `google.com` | | `WebSocketTests.TestSocketConnection` | `echo.websocket.org` (was `[Ignore]`d) | `ProxyTest` keeps its original point — an unescaped `|` in the query string (`?query&foo|bar`) — now exercised against the local `/ok` endpoint. `WebSocketTests` was previously disabled because the public echo server no longer exists; it is re-enabled against a local server. ### Test infrastructure (`LocalTestServers.cs`) - **Client certificates (mutual TLS)**: `LocalHttpsServer.Start (requestClientCertificate: true)` now requests a client certificate during the TLS handshake, and a new `/echo-client-certificate` endpoint returns the certificate the server received (base64 DER). This replaces the external `EchoClientCertificate.ashx` endpoint. - **`LocalWebSocketServer`**: a loopback WebSocket echo server that performs the upgrade handshake and echoes messages back (uses `WebSocket.CreateFromStream` after a manual `101 Switching Protocols` response). ### Notes - `HttpsShouldWork` is intentionally left to #12058. - The `http://10.255.255.1` cancellation/timeout tests deliberately use an unroutable address (not the public internet) and are unchanged. - Cosmetic external hostnames that were never actually contacted (`CancelRequestViaProxy`'s base address behind a dead proxy, and `DisallowAutoRedirect`'s redirect target) were pointed at `localhost`. The new server logic (HTTP, HTTPS, mutual-TLS echo, and WebSocket echo) was verified end-to-end against `HttpClient` and `ClientWebSocket` on desktop .NET.
Summary
Validation
make all./dotnet-local.sh build -t:Install -c Debug tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -p:Device=R58Y30HZ65V -p:IncludeCategories=SSL(cd tests/Mono.Android-Tests/Mono.Android-Tests && ../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj --no-build -c Debug --report-trx --results-directory ../../../bin/TestDebug/TestResults -p:Device=R58Y30HZ65V -p:IncludeCategories=SSL)— 25 passed./dotnet-local.sh test bin/TestDebug/MSBuildDeviceIntegration/net10.0/MSBuildDeviceIntegration.dll --filter "FullyQualifiedName~InstallAndRunTests.CustomLinkDescriptionPreserve"— 4 skipped (test is currently ignored for all runtime variants)