psmux 3.3.4

Terminal multiplexer for Windows - tmux alternative for PowerShell and Windows Terminal
# Issue #244: capture-pane -S -N / -S - do not read scrollback history
# Tests that PROVE the bug exists by generating output larger than the visible
# pane and verifying that capture-pane with negative -S values (or -S -)
# fails to return scrollback content.

$ErrorActionPreference = "Continue"
$PSMUX = (Get-Command psmux -EA Stop).Source
$SESSION = "test_issue244"
$psmuxDir = "$env:USERPROFILE\.psmux"
$script:TestsPassed = 0
$script:TestsFailed = 0

function Write-Pass($msg) { Write-Host "  [PASS] $msg" -ForegroundColor Green; $script:TestsPassed++ }
function Write-Fail($msg) { Write-Host "  [FAIL] $msg" -ForegroundColor Red; $script:TestsFailed++ }

function Cleanup {
    & $PSMUX kill-session -t $SESSION 2>&1 | Out-Null
    Start-Sleep -Milliseconds 500
    Remove-Item "$psmuxDir\$SESSION.*" -Force -EA SilentlyContinue
}

function Send-TcpCommand {
    param([string]$Session, [string]$Command)
    $portFile = "$psmuxDir\$Session.port"
    $keyFile = "$psmuxDir\$Session.key"
    if (-not (Test-Path $portFile) -or -not (Test-Path $keyFile)) { return "NO_FILES" }
    $port = (Get-Content $portFile -Raw).Trim()
    $key = (Get-Content $keyFile -Raw).Trim()
    $tcp = [System.Net.Sockets.TcpClient]::new("127.0.0.1", [int]$port)
    $tcp.NoDelay = $true
    $stream = $tcp.GetStream()
    $writer = [System.IO.StreamWriter]::new($stream)
    $reader = [System.IO.StreamReader]::new($stream)
    $writer.Write("AUTH $key`n"); $writer.Flush()
    $authResp = $reader.ReadLine()
    if ($authResp -ne "OK") { $tcp.Close(); return "AUTH_FAILED" }
    $writer.Write("$Command`n"); $writer.Flush()
    $stream.ReadTimeout = 10000
    $lines = [System.Collections.ArrayList]::new()
    try {
        while ($true) {
            $line = $reader.ReadLine()
            if ($null -eq $line) { break }
            [void]$lines.Add($line)
        }
    } catch {}
    $tcp.Close()
    return ($lines -join "`n")
}

# ============================================================
# SETUP: Create session, set high history-limit, generate output
# ============================================================
Cleanup

# Create detached session with explicit geometry (80x24 visible pane)
& $PSMUX new-session -d -s $SESSION -x 80 -y 24
Start-Sleep -Seconds 3

# Verify session exists
& $PSMUX has-session -t $SESSION 2>$null
if ($LASTEXITCODE -ne 0) {
    Write-Fail "Session creation failed, cannot proceed"
    exit 1
}

# Set a large history-limit so scrollback is retained
& $PSMUX set-option -g history-limit 100000 -t $SESSION 2>&1 | Out-Null
Start-Sleep -Milliseconds 500

# Generate 200 uniquely numbered lines (way more than 24 visible rows)
# Each line has a known marker so we can search for specific lines
& $PSMUX send-keys -t $SESSION '1..200 | ForEach-Object { Write-Host "SCROLLTEST-LINE-$_" }' Enter
Start-Sleep -Seconds 5

# Let the shell finish outputting
& $PSMUX send-keys -t $SESSION '' Enter
Start-Sleep -Seconds 2

Write-Host "`n=== Issue #244: capture-pane scrollback tests ===" -ForegroundColor Cyan

# ============================================================
# TEST 1: Baseline: default capture-pane returns ~24 visible lines
# ============================================================
Write-Host "`n[Test 1] Baseline: default capture-pane returns only visible lines" -ForegroundColor Yellow
$defaultCapture = & $PSMUX capture-pane -t $SESSION -p 2>&1
$defaultLines = ($defaultCapture | Out-String).Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
$defaultCount = $defaultLines.Count
Write-Host "    Default capture found $defaultCount SCROLLTEST lines"

if ($defaultCount -le 30) {
    Write-Pass "Default capture returns limited visible lines ($defaultCount lines with markers)"
} else {
    Write-Pass "Default capture returns $defaultCount lines (might have large visible area)"
}

# ============================================================
# TEST 2: capture-pane -S -100 should return MORE than visible
# ============================================================
Write-Host "`n[Test 2] capture-pane -p -S -100 should include scrollback" -ForegroundColor Yellow
$scrollCapture = & $PSMUX capture-pane -t $SESSION -p -S -100 2>&1
$scrollLines = ($scrollCapture | Out-String).Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
$scrollCount = $scrollLines.Count
Write-Host "    -S -100 capture found $scrollCount SCROLLTEST lines"

# BUG PROOF: If -S -100 returns the same or fewer lines as the default,
# the scrollback is NOT being read. With 200 lines output and -S -100,
# we should see at LEAST ~100 scrollback lines + ~24 visible = ~124 lines.
# If we only see ~24, the bug is confirmed.
if ($scrollCount -le $defaultCount + 5) {
    Write-Fail "BUG CONFIRMED: -S -100 returned only $scrollCount marker lines (same as default $defaultCount). Scrollback NOT read."
} else {
    Write-Pass "-S -100 returned $scrollCount marker lines (more than default $defaultCount). Scrollback IS being read."
}

# ============================================================
# TEST 3: capture-pane -S - (entire scrollback) should return ALL lines
# ============================================================
Write-Host "`n[Test 3] capture-pane -p -S - should return entire scrollback" -ForegroundColor Yellow
$fullCapture = & $PSMUX capture-pane -t $SESSION -p "-S" "-" 2>&1
$fullLines = ($fullCapture | Out-String).Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
$fullCount = $fullLines.Count
Write-Host "    -S - capture found $fullCount SCROLLTEST lines"

# BUG PROOF: With -S -, we should see all 200 SCROLLTEST lines.
# If we only see ~24, the bug is confirmed.
if ($fullCount -le $defaultCount + 5) {
    Write-Fail "BUG CONFIRMED: -S - returned only $fullCount marker lines (same as default $defaultCount). Full scrollback NOT read."
} else {
    # Check if we got close to all 200 lines
    if ($fullCount -ge 180) {
        Write-Pass "-S - returned $fullCount marker lines (close to all 200). Full scrollback IS being read."
    } else {
        Write-Fail "PARTIAL BUG: -S - returned $fullCount marker lines, expected ~200. Scrollback only partially read."
    }
}

# ============================================================
# TEST 4: Specific line verification: can we find early lines?
# ============================================================
Write-Host "`n[Test 4] Verify specific early lines are recoverable with -S -" -ForegroundColor Yellow
$fullCaptureText = ($fullCapture | Out-String)

$foundLine1 = $fullCaptureText -match "SCROLLTEST-LINE-1\b"
$foundLine10 = $fullCaptureText -match "SCROLLTEST-LINE-10\b"
$foundLine50 = $fullCaptureText -match "SCROLLTEST-LINE-50\b"
$foundLine100 = $fullCaptureText -match "SCROLLTEST-LINE-100\b"

Write-Host "    Line 1 found: $foundLine1"
Write-Host "    Line 10 found: $foundLine10"
Write-Host "    Line 50 found: $foundLine50"
Write-Host "    Line 100 found: $foundLine100"

if (-not $foundLine1 -and -not $foundLine10 -and -not $foundLine50) {
    Write-Fail "BUG CONFIRMED: Early lines (1, 10, 50) are NOT recoverable via -S -. Scrollback content is lost."
} elseif ($foundLine1 -and $foundLine10 -and $foundLine50 -and $foundLine100) {
    Write-Pass "All early lines (1, 10, 50, 100) are recoverable via -S -."
} else {
    Write-Fail "PARTIAL BUG: Some early lines missing. L1=$foundLine1 L10=$foundLine10 L50=$foundLine50 L100=$foundLine100"
}

# ============================================================
# TEST 5: capture-pane -S -50 -E -1 (sub-range in scrollback)
# ============================================================
Write-Host "`n[Test 5] capture-pane -p -S -50 -E -1 should return 50 scrollback lines" -ForegroundColor Yellow
$rangeCapture = & $PSMUX capture-pane -t $SESSION -p -S -50 -E -1 2>&1
$rangeText = ($rangeCapture | Out-String)
$rangeLines = $rangeText.Split("`n") | Where-Object { $_.Trim().Length -gt 0 }
$rangeCount = $rangeLines.Count
Write-Host "    -S -50 -E -1 returned $rangeCount non-empty lines"

# BUG PROOF: -S -50 -E -1 should return 50 scrollback lines.
# With the current bug, negative -E clamps to 0 and negative -S clamps to 0,
# so we get at most 1 line (row 0 to row 0).
if ($rangeCount -le 2) {
    Write-Fail "BUG CONFIRMED: -S -50 -E -1 returned only $rangeCount lines. Both negative S and E clamped to 0."
} elseif ($rangeCount -ge 40) {
    Write-Pass "-S -50 -E -1 returned $rangeCount lines (expected ~50 from scrollback)."
} else {
    Write-Fail "PARTIAL: -S -50 -E -1 returned $rangeCount lines, expected ~50."
}

# ============================================================
# TEST 6: TCP path: verify same bug exists over raw TCP
# ============================================================
Write-Host "`n[Test 6] TCP path: capture-pane -p -S -100 via raw TCP" -ForegroundColor Yellow
$tcpResult = Send-TcpCommand -Session $SESSION -Command "capture-pane -p -S -100"
$tcpLines = $tcpResult.Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
$tcpCount = $tcpLines.Count
Write-Host "    TCP -S -100 found $tcpCount SCROLLTEST lines"

if ($tcpCount -le $defaultCount + 5) {
    Write-Fail "BUG CONFIRMED (TCP): -S -100 via TCP returned only $tcpCount marker lines. TCP handler also lacks scrollback."
} else {
    Write-Pass "TCP: -S -100 returned $tcpCount marker lines. Scrollback works via TCP."
}

# ============================================================
# TEST 7: TCP path: capture-pane -p -S - via raw TCP
# ============================================================
Write-Host "`n[Test 7] TCP path: capture-pane -p -S - via raw TCP" -ForegroundColor Yellow
$tcpFullResult = Send-TcpCommand -Session $SESSION -Command "capture-pane -p -S -"
$tcpFullLines = $tcpFullResult.Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
$tcpFullCount = $tcpFullLines.Count
Write-Host "    TCP -S - found $tcpFullCount SCROLLTEST lines"

if ($tcpFullCount -le $defaultCount + 5) {
    Write-Fail "BUG CONFIRMED (TCP): -S - via TCP returned only $tcpFullCount marker lines. Entire scrollback NOT read via TCP."
} else {
    Write-Pass "TCP: -S - returned $tcpFullCount marker lines."
}

# ============================================================
# TEST 8: Second TCP handler (persistent connection) also lacks -S - parsing
# ============================================================
Write-Host "`n[Test 8] Persistent TCP handler: capture-pane -S - parsing" -ForegroundColor Yellow
# The second handler at connection.rs:2501 does .parse::<i32>() on the -S arg,
# which fails for "-" (not a number), so -S is silently ignored.
$portFile = "$psmuxDir\$SESSION.port"
$keyFile = "$psmuxDir\$SESSION.key"
if ((Test-Path $portFile) -and (Test-Path $keyFile)) {
    $port = (Get-Content $portFile -Raw).Trim()
    $key = (Get-Content $keyFile -Raw).Trim()
    try {
        $tcp = [System.Net.Sockets.TcpClient]::new("127.0.0.1", [int]$port)
        $tcp.NoDelay = $true; $tcp.ReceiveTimeout = 5000
        $stream = $tcp.GetStream()
        $writer = [System.IO.StreamWriter]::new($stream)
        $reader = [System.IO.StreamReader]::new($stream)
        $writer.Write("AUTH $key`n"); $writer.Flush()
        $null = $reader.ReadLine()
        $writer.Write("PERSISTENT`n"); $writer.Flush()

        # Send capture-pane with -S - via persistent handler
        $writer.Write("capture-pane -p -S -`n"); $writer.Flush()
        Start-Sleep -Milliseconds 500
        $stream.ReadTimeout = 2000
        $persistLines = [System.Collections.ArrayList]::new()
        $readCount = 0
        try {
            while ($readCount -lt 500) {
                $line = $reader.ReadLine()
                if ($null -eq $line) { break }
                [void]$persistLines.Add($line)
                $readCount++
                # After getting some lines, reduce timeout to avoid hanging
                if ($readCount -gt 5) { $stream.ReadTimeout = 500 }
            }
        } catch [System.IO.IOException] {
            # Timeout is expected after all data is read
        } catch {}
        $tcp.Close()

        $persistText = $persistLines -join "`n"
        $persistMarkers = $persistText.Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
        $persistCount = $persistMarkers.Count
        Write-Host "    Persistent -S - found $persistCount SCROLLTEST lines"

        if ($persistCount -le $defaultCount + 5) {
            Write-Fail "BUG CONFIRMED (Persistent TCP): -S - via persistent handler also lacks scrollback ($persistCount marker lines)."
        } else {
            Write-Pass "Persistent TCP: -S - returned $persistCount marker lines."
        }
    } catch {
        Write-Fail "Persistent TCP connection error: $_"
    }
} else {
    Write-Fail "Cannot test persistent handler (port/key files missing)"
}

# ============================================================
# TEST 9: capture-pane -e -S -100 (styled) also misses scrollback
# ============================================================
Write-Host "`n[Test 9] capture-pane -e -S -100 (styled) should include scrollback" -ForegroundColor Yellow
$styledCapture = & $PSMUX capture-pane -t $SESSION -p -e -S -100 2>&1
$styledText = ($styledCapture | Out-String)
# Strip ANSI escape sequences for line counting
$stripped = $styledText -replace '\x1b\[[0-9;]*m', ''
$styledLines = $stripped.Split("`n") | Where-Object { $_ -match "SCROLLTEST-LINE-\d+" }
$styledCount = $styledLines.Count
Write-Host "    -e -S -100 found $styledCount SCROLLTEST lines (after stripping ANSI)"

if ($styledCount -le $defaultCount + 5) {
    Write-Fail "BUG CONFIRMED (styled): -e -S -100 returned only $styledCount marker lines. Styled capture also lacks scrollback."
} else {
    Write-Pass "Styled: -e -S -100 returned $styledCount marker lines."
}

# ============================================================
# TEST 10: Positive -S/-E still works (no regression)
# ============================================================
Write-Host "`n[Test 10] Positive -S/-E range still works (regression guard)" -ForegroundColor Yellow
$posCapture = & $PSMUX capture-pane -t $SESSION -p -S 0 -E 5 2>&1
$posLines = ($posCapture | Out-String).Split("`n")
$posNonEmpty = $posLines | Where-Object { $_.Trim().Length -gt 0 }
$posCount = $posNonEmpty.Count
Write-Host "    -S 0 -E 5 returned $posCount non-empty lines"

if ($posCount -ge 1 -and $posCount -le 10) {
    Write-Pass "Positive -S 0 -E 5 returns expected line count ($posCount)."
} else {
    Write-Fail "Positive -S 0 -E 5 returned unexpected count: $posCount"
}

# ============================================================
# TEST 11: Quantify the exact line count gap
# ============================================================
Write-Host "`n[Test 11] Quantify scrollback gap" -ForegroundColor Yellow
Write-Host "    Default capture lines with markers: $defaultCount"
Write-Host "    -S -100 capture lines with markers: $scrollCount"
Write-Host "    -S - capture lines with markers:    $fullCount"
Write-Host "    Expected with -S -100:              ~124+ lines"
Write-Host "    Expected with -S -:                 ~200 lines"

$gapS100 = [Math]::Max(0, 124 - $scrollCount)
$gapSAll = [Math]::Max(0, 180 - $fullCount)

if ($gapS100 -gt 50 -or $gapSAll -gt 50) {
    Write-Fail "MASSIVE GAP: Missing ~$gapS100 lines for -S -100, ~$gapSAll lines for -S -. Scrollback is completely inaccessible."
} elseif ($gapS100 -gt 0 -or $gapSAll -gt 0) {
    Write-Fail "GAP EXISTS: Missing ~$gapS100 lines for -S -100, ~$gapSAll lines for -S -."
} else {
    Write-Pass "No scrollback gap detected."
}

# ============================================================
# TEARDOWN
# ============================================================
Cleanup

Write-Host "`n=== Results ===" -ForegroundColor Cyan
Write-Host "  Passed: $($script:TestsPassed)" -ForegroundColor Green
Write-Host "  Failed: $($script:TestsFailed)" -ForegroundColor $(if ($script:TestsFailed -gt 0) { "Red" } else { "Green" })

if ($script:TestsFailed -gt 0) {
    Write-Host "`n  VERDICT: Issue #244 is CONFIRMED. capture-pane does not read scrollback history." -ForegroundColor Red
} else {
    Write-Host "`n  VERDICT: Issue #244 appears to be FIXED. Scrollback is accessible." -ForegroundColor Green
}

exit $script:TestsFailed