Skip to content

[test-improver] Add unit tests for TcpMessageHandler IOException-wrapped reset and bare-LF tolerance #11697

Closed

Description

@nohwnd-bot

Goal and rationale

TcpMessageHandler (the ServerMode JSON-RPC transport) had two untested branches found while sweeping the area after prior test-improver passes closed out ServerModeManager, the ServerControlMessage/WaitForServerControlRequest serializers, and PassiveNode:

  1. ReadAsync's connection-reset catch filter matches two exception shapes: a bare SocketException (already covered by ReadAsync_ConnectionReset_LogsFullExceptionAndReturnsNullWhenLoggerFails) and an IOException wrapping that SocketException — the shape NetworkStream actually throws for a real TCP reset. Only the bare-SocketException arm had coverage; the IOException-wrapped arm (the one that matters for real sockets) had none. Without it, a genuine reset could have escaped as an unhandled exception instead of the intended graceful null disconnect.
  2. ReadHeaderLineAsync's documented bare-LF tolerance. The method's remarks explicitly state it accepts a lone \n in addition to \r\n, but every existing test framed headers with \r\n only, so that documented behavior was never exercised.

Approach

Added two tests to the existing TcpMessageHandlerTests.cs, reusing its established ConnectedHandlers loopback-TCP fixture and a new minimal ThrowingStream helper (parallel to the existing ConnectionResetStream, but accepting an arbitrary exception instead of only a bare SocketException):

  • ReadAsync_IOExceptionWrappingConnectionReset_ReturnsNull
  • ReadAsync_BareLineFeedHeaderTerminator_IsTolerated

Coverage impact

Before After
Microsoft.Testing.Platform.UnitTests (net8.0) 2774 total, 0 failed, 22 skipped 2776 total, 0 failed, 22 skipped

Trade-offs

Small, additive, low-maintenance-burden change to an existing test file; no new test infrastructure beyond one small stream stub matching an existing pattern in the same file.

Reproducibility

./build.sh -restore
export PATH="$PWD/.dotnet:$PATH"
dotnet build test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj -c Debug -f net8.0
./artifacts/bin/Microsoft.Testing.Platform.UnitTests/Debug/net8.0/Microsoft.Testing.Platform.UnitTests

Test Status

  • Build: succeeded, 0 warnings, 0 errors.
  • Full Microsoft.Testing.Platform.UnitTests net8.0 suite: 2776 total (was 2774), 0 failed, 22 skipped (pre-existing), no regressions.
  • dotnet format whitespace TestFx.slnx --verify-no-changes --include <file>: clean (expected harmless F#-project warning only).
  • git diff --check: clean.

Test Improver


Note

This was originally intended as a pull request, but the git push operation failed.

Original error: The process '/usr/bin/git' failed with exit code 128

Workflow Run: View run details and download bundle artifact

The bundle file is available in the agent artifact in the workflow run linked above.

Create the pull request manually
# Download the artifact from the workflow run
gh run download '36937877564' -n agent -D '/tmp/agent-36937877564'

# Resolve the bundle source ref, fetch it into a temporary ref, then create the local branch
bundle_path='/tmp/agent-36937877564/aw-test-assist-tcp-message-handler-tests.bundle'
temp_ref='refs/bundles/create-pr-test-assist-tcp-message-handler-tests-3e63052caceadca7-db23dcaa'
target_ref='refs/heads/test-assist/tcp-message-handler-tests-3e63052caceadca7'
bundle_source_ref=$(git bundle list-heads "$bundle_path" | awk '$2 ~ /^refs\/heads\// { print $2 }')
if [ -z "$bundle_source_ref" ]; then
  bundle_source_ref=$(git bundle list-heads "$bundle_path" | awk '$2 == "HEAD" { print $2 }')
fi
if [ "$(printf '%s\n' "$bundle_source_ref" | sed '/^$/d' | wc -l | tr -d ' ')" != "1" ]; then
  echo "Expected exactly one bundle source ref, found: $bundle_source_ref" >&2
  exit 1
fi
git fetch "$bundle_path" "${bundle_source_ref}:${temp_ref}"
git update-ref "$target_ref" "$temp_ref"
git checkout 'test-assist/tcp-message-handler-tests-3e63052caceadca7'
# Ensure the working tree matches the updated branch
git reset --hard
# Remove the temporary bundle ref
git update-ref -d "$temp_ref"

# Push the branch to the target remote
git push 'origin' 'test-assist/tcp-message-handler-tests-3e63052caceadca7'

# Create the pull request
gh pr create --title '[test-improver] Add unit tests for TcpMessageHandler IOException-wrapped reset and bare-LF tolerance' --base 'main' --head 'test-assist/tcp-message-handler-tests-3e63052caceadca7' --repo 'microsoft/testfx'

Warning

Firewall blocked 92 domains

The following domains were blocked by the firewall during workflow execution:

  • 0t3vsblobprodcus362.vsblob.vsassets.io
  • 11vvsblobprodcus336.vsblob.vsassets.io
  • 1javsblobprodcus364.vsblob.vsassets.io
  • 1k9vsblobprodcus379.vsblob.vsassets.io
  • 1oavsblobprodcus350.vsblob.vsassets.io
  • 1p7vsblobprodcus324.vsblob.vsassets.io
  • 1s1vsblobprodcus386.vsblob.vsassets.io
  • 2kmvsblobprodcus39.vsblob.vsassets.io
  • 2zrvsblobprodcus388.vsblob.vsassets.io
  • 37bvsblobprodcus311.vsblob.vsassets.io
  • 37cvsblobprodcus359.vsblob.vsassets.io
  • 4m6vsblobprodcus384.vsblob.vsassets.io
  • 4myvsblobprodcus32.vsblob.vsassets.io
  • 4vyvsblobprodcus361.vsblob.vsassets.io
  • 4zjvsblobprodcus390.vsblob.vsassets.io
  • 51yvsblobprodcus36.vsblob.vsassets.io
  • 5dkvsblobprodcus355.vsblob.vsassets.io
  • 5rqvsblobprodcus385.vsblob.vsassets.io
  • 6s7vsblobprodcus313.vsblob.vsassets.io
  • 7devsblobprodcus323.vsblob.vsassets.io
  • 7k6vsblobprodcus337.vsblob.vsassets.io
  • 7tjvsblobprodcus341.vsblob.vsassets.io
  • 80zvsblobprodcus35.vsblob.vsassets.io
  • 8xbvsblobprodcus382.vsblob.vsassets.io
  • 96bvsblobprodcus338.vsblob.vsassets.io
  • ajhvsblobprodcus363.vsblob.vsassets.io
  • bcnvsblobprodcus378.vsblob.vsassets.io
  • builds.dotnet.microsoft.com
  • c50vsblobprodcus330.vsblob.vsassets.io
  • c78vsblobprodcus322.vsblob.vsassets.io
  • cflvsblobprodcus383.vsblob.vsassets.io
  • ci.dot.net
  • ckzvsblobprodcus347.vsblob.vsassets.io
  • d0svsblobprodcus381.vsblob.vsassets.io
  • data.nuget.org
  • dlbvsblobprodcus316.vsblob.vsassets.io
  • e7bvsblobprodcus348.vsblob.vsassets.io
  • fdpvsblobprodcus345.vsblob.vsassets.io
  • frdvsblobprodcus327.vsblob.vsassets.io
  • gbsvsblobprodcus365.vsblob.vsassets.io
  • gervsblobprodcus329.vsblob.vsassets.io
  • h6tvsblobprodcus346.vsblob.vsassets.io
  • i1qvsblobprodcus353.vsblob.vsassets.io
  • imzvsblobprodcus368.vsblob.vsassets.io
  • jd4vsblobprodcus366.vsblob.vsassets.io
  • josvsblobprodcus372.vsblob.vsassets.io
  • jrqvsblobprodcus343.vsblob.vsassets.io
  • k0ivsblobprodcus356.vsblob.vsassets.io
  • k4kvsblobprodcus344.vsblob.vsassets.io
  • kgfvsblobprodcus314.vsblob.vsassets.io
  • kh4vsblobprodcus325.vsblob.vsassets.io
  • kijvsblobprodcus387.vsblob.vsassets.io
  • kmuvsblobprodcus389.vsblob.vsassets.io
  • kxqvsblobprodcus376.vsblob.vsassets.io
  • l49vsblobprodcus358.vsblob.vsassets.io
  • l7avsblobprodcus319.vsblob.vsassets.io
  • ljcvsblobprodcus317.vsblob.vsassets.io
  • lylvsblobprodcus31.vsblob.vsassets.io
  • m16vsblobprodcus374.vsblob.vsassets.io
  • m6xvsblobprodcus342.vsblob.vsassets.io
  • m8dvsblobprodcus37.vsblob.vsassets.io
  • mfjvsblobprodcus373.vsblob.vsassets.io
  • n3kvsblobprodcus335.vsblob.vsassets.io
  • nn8vsblobprodcus340.vsblob.vsassets.io
  • o3svsblobprodcus318.vsblob.vsassets.io
  • ofvvsblobprodcus315.vsblob.vsassets.io
  • p2ovsblobprodcus312.vsblob.vsassets.io
  • pc2vsblobprodcus360.vsblob.vsassets.io
  • pdfvsblobprodcus380.vsblob.vsassets.io
  • pe3vsblobprodcus354.vsblob.vsassets.io
  • pe4vsblobprodcus351.vsblob.vsassets.io
  • pkgs.dev.azure.com
  • pkvvsblobprodcus321.vsblob.vsassets.io
  • rcxvsblobprodcus328.vsblob.vsassets.io
  • s4uvsblobprodcus326.vsblob.vsassets.io
  • s8mvsblobprodcus38.vsblob.vsassets.io
  • se1vsblobprodcus349.vsblob.vsassets.io
  • sqdvsblobprodcus333.vsblob.vsassets.io
  • st8vsblobprodcus339.vsblob.vsassets.io
  • tphvsblobprodcus375.vsblob.vsassets.io
  • u3hvsblobprodcus371.vsblob.vsassets.io
  • u6ovsblobprodcus377.vsblob.vsassets.io
  • ukkvsblobprodcus352.vsblob.vsassets.io
  • uy6vsblobprodcus34.vsblob.vsassets.io
  • v53vsblobprodcus320.vsblob.vsassets.io
  • vb4vsblobprodcus33.vsblob.vsassets.io
  • vwvvsblobprodcus334.vsblob.vsassets.io
  • x3yvsblobprodcus370.vsblob.vsassets.io
  • xupvsblobprodcus332.vsblob.vsassets.io
  • yluvsblobprodcus367.vsblob.vsassets.io
  • yttvsblobprodcus357.vsblob.vsassets.io
  • ytvvsblobprodcus310.vsblob.vsassets.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "0t3vsblobprodcus362.vsblob.vsassets.io"
    - "11vvsblobprodcus336.vsblob.vsassets.io"
    - "1javsblobprodcus364.vsblob.vsassets.io"
    - "1k9vsblobprodcus379.vsblob.vsassets.io"
    - "1oavsblobprodcus350.vsblob.vsassets.io"
    - "1p7vsblobprodcus324.vsblob.vsassets.io"
    - "1s1vsblobprodcus386.vsblob.vsassets.io"
    - "2kmvsblobprodcus39.vsblob.vsassets.io"
    - "2zrvsblobprodcus388.vsblob.vsassets.io"
    - "37bvsblobprodcus311.vsblob.vsassets.io"
    - "37cvsblobprodcus359.vsblob.vsassets.io"
    - "4m6vsblobprodcus384.vsblob.vsassets.io"
    - "4myvsblobprodcus32.vsblob.vsassets.io"
    - "4vyvsblobprodcus361.vsblob.vsassets.io"
    - "4zjvsblobprodcus390.vsblob.vsassets.io"
    - "51yvsblobprodcus36.vsblob.vsassets.io"
    - "5dkvsblobprodcus355.vsblob.vsassets.io"
    - "5rqvsblobprodcus385.vsblob.vsassets.io"
    - "6s7vsblobprodcus313.vsblob.vsassets.io"
    - "7devsblobprodcus323.vsblob.vsassets.io"
    - "7k6vsblobprodcus337.vsblob.vsassets.io"
    - "7tjvsblobprodcus341.vsblob.vsassets.io"
    - "80zvsblobprodcus35.vsblob.vsassets.io"
    - "8xbvsblobprodcus382.vsblob.vsassets.io"
    - "96bvsblobprodcus338.vsblob.vsassets.io"
    - "ajhvsblobprodcus363.vsblob.vsassets.io"
    - "bcnvsblobprodcus378.vsblob.vsassets.io"
    - "builds.dotnet.microsoft.com"
    - "c50vsblobprodcus330.vsblob.vsassets.io"
    - "c78vsblobprodcus322.vsblob.vsassets.io"
    - "cflvsblobprodcus383.vsblob.vsassets.io"
    - "ci.dot.net"
    - "ckzvsblobprodcus347.vsblob.vsassets.io"
    - "d0svsblobprodcus381.vsblob.vsassets.io"
    - "data.nuget.org"
    - "dlbvsblobprodcus316.vsblob.vsassets.io"
    - "e7bvsblobprodcus348.vsblob.vsassets.io"
    - "fdpvsblobprodcus345.vsblob.vsassets.io"
    - "frdvsblobprodcus327.vsblob.vsassets.io"
    - "gbsvsblobprodcus365.vsblob.vsassets.io"
    - "gervsblobprodcus329.vsblob.vsassets.io"
    - "h6tvsblobprodcus346.vsblob.vsassets.io"
    - "i1qvsblobprodcus353.vsblob.vsassets.io"
    - "imzvsblobprodcus368.vsblob.vsassets.io"
    - "jd4vsblobprodcus366.vsblob.vsassets.io"
    - "josvsblobprodcus372.vsblob.vsassets.io"
    - "jrqvsblobprodcus343.vsblob.vsassets.io"
    - "k0ivsblobprodcus356.vsblob.vsassets.io"
    - "k4kvsblobprodcus344.vsblob.vsassets.io"
    - "kgfvsblobprodcus314.vsblob.vsassets.io"
    - "kh4vsblobprodcus325.vsblob.vsassets.io"
    - "kijvsblobprodcus387.vsblob.vsassets.io"
    - "kmuvsblobprodcus389.vsblob.vsassets.io"
    - "kxqvsblobprodcus376.vsblob.vsassets.io"
    - "l49vsblobprodcus358.vsblob.vsassets.io"
    - "l7avsblobprodcus319.vsblob.vsassets.io"
    - "ljcvsblobprodcus317.vsblob.vsassets.io"
    - "lylvsblobprodcus31.vsblob.vsassets.io"
    - "m16vsblobprodcus374.vsblob.vsassets.io"
    - "m6xvsblobprodcus342.vsblob.vsassets.io"
    - "m8dvsblobprodcus37.vsblob.vsassets.io"
    - "mfjvsblobprodcus373.vsblob.vsassets.io"
    - "n3kvsblobprodcus335.vsblob.vsassets.io"
    - "nn8vsblobprodcus340.vsblob.vsassets.io"
    - "o3svsblobprodcus318.vsblob.vsassets.io"
    - "ofvvsblobprodcus315.vsblob.vsassets.io"
    - "p2ovsblobprodcus312.vsblob.vsassets.io"
    - "pc2vsblobprodcus360.vsblob.vsassets.io"
    - "pdfvsblobprodcus380.vsblob.vsassets.io"
    - "pe3vsblobprodcus354.vsblob.vsassets.io"
    - "pe4vsblobprodcus351.vsblob.vsassets.io"
    - "pkgs.dev.azure.com"
    - "pkvvsblobprodcus321.vsblob.vsassets.io"
    - "rcxvsblobprodcus328.vsblob.vsassets.io"
    - "s4uvsblobprodcus326.vsblob.vsassets.io"
    - "s8mvsblobprodcus38.vsblob.vsassets.io"
    - "se1vsblobprodcus349.vsblob.vsassets.io"
    - "sqdvsblobprodcus333.vsblob.vsassets.io"
    - "st8vsblobprodcus339.vsblob.vsassets.io"
    - "tphvsblobprodcus375.vsblob.vsassets.io"
    - "u3hvsblobprodcus371.vsblob.vsassets.io"
    - "u6ovsblobprodcus377.vsblob.vsassets.io"
    - "ukkvsblobprodcus352.vsblob.vsassets.io"
    - "uy6vsblobprodcus34.vsblob.vsassets.io"
    - "v53vsblobprodcus320.vsblob.vsassets.io"
    - "vb4vsblobprodcus33.vsblob.vsassets.io"
    - "vwvvsblobprodcus334.vsblob.vsassets.io"
    - "x3yvsblobprodcus370.vsblob.vsassets.io"
    - "xupvsblobprodcus332.vsblob.vsassets.io"
    - "yluvsblobprodcus367.vsblob.vsassets.io"
    - "yttvsblobprodcus357.vsblob.vsassets.io"
    - "ytvvsblobprodcus310.vsblob.vsassets.io"

See Network Configuration for more information.

🤖 Automated content by GitHub Copilot. Generated by the Test Improver workflow. · copilot · auto · 209.3 AIC · ⌖ 11.5 AIC · ⊞ 20.5K · [◷]( · ◷)
Comment /test-assist to run again

Add this agentic workflow to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/test-improver.md@main

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions