mirror of
https://github.com/psmux/psmux.git
synced 2026-06-19 09:46:56 +08:00
- ClaimSession now calls load_config() after rename so prefix keys and chained commands from user config work on warm-server sessions - send-keys C-c uses GenerateConsoleCtrlEvent via AttachConsole for reliable Ctrl-C delivery on Windows (ConPTY doesn't translate \x03) - Paste writes use 512-byte chunked writes with yields to prevent pipe buffer overflow on large pastes (350+ lines) - Fix Get-FreshDump in test_github_issues to drain stale TCP push frames before reading dump-state response - Test assertion improvements for robustness in detached ConPTY mode All 1387 tests across 75 suites pass (0 failures).
334 lines
12 KiB
PowerShell
334 lines
12 KiB
PowerShell
# psmux GitHub Issues Reproduction Script
|
|
# Tests bugs reported in issues #25 and #19
|
|
#
|
|
# Bug 1 (Issue #25): Active window tab color not updating after select-window
|
|
# Bug 2 (Issue #19): bind-key from command prompt not working (flag stripping)
|
|
# Bug 3 (Issue #19): Status bar colors not configurable (hardcoded yellow)
|
|
#
|
|
# Run: pwsh -NoProfile -ExecutionPolicy Bypass -File tests\test_github_issues.ps1
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
$script:TestsPassed = 0
|
|
$script:TestsFailed = 0
|
|
|
|
function Write-Pass { param($msg) Write-Host "[PASS] $msg" -ForegroundColor Green; $script:TestsPassed++ }
|
|
function Write-Fail { param($msg) Write-Host "[FAIL] $msg" -ForegroundColor Red; $script:TestsFailed++ }
|
|
function Write-Skip { param($msg) Write-Host "[SKIP] $msg" -ForegroundColor Yellow }
|
|
function Write-Info { param($msg) Write-Host "[INFO] $msg" -ForegroundColor Cyan }
|
|
function Write-Test { param($msg) Write-Host "[TEST] $msg" -ForegroundColor White }
|
|
|
|
$PSMUX = (Resolve-Path "$PSScriptRoot\..\target\release\psmux.exe" -ErrorAction SilentlyContinue).Path
|
|
if (-not $PSMUX) { $PSMUX = (Resolve-Path "$PSScriptRoot\..\target\debug\psmux.exe" -ErrorAction SilentlyContinue).Path }
|
|
if (-not $PSMUX) { Write-Error "psmux binary not found"; exit 1 }
|
|
Write-Info "Using: $PSMUX"
|
|
|
|
# Kill everything first
|
|
Write-Info "Cleaning up existing sessions..."
|
|
& $PSMUX kill-server 2>$null
|
|
Start-Sleep -Seconds 3
|
|
Remove-Item "$env:USERPROFILE\.psmux\*.port" -Force -ErrorAction SilentlyContinue
|
|
Remove-Item "$env:USERPROFILE\.psmux\*.key" -Force -ErrorAction SilentlyContinue
|
|
|
|
$SESSION = "issuetest"
|
|
|
|
function New-TestSession {
|
|
Start-Process -FilePath $PSMUX -ArgumentList "new-session -s $SESSION -d" -WindowStyle Hidden
|
|
Start-Sleep -Seconds 3
|
|
& $PSMUX has-session -t $SESSION 2>$null
|
|
if ($LASTEXITCODE -ne 0) { Write-Host "FATAL: Cannot create test session" -ForegroundColor Red; exit 1 }
|
|
}
|
|
|
|
function Connect-TCP {
|
|
$portFile = "$env:USERPROFILE\.psmux\$SESSION.port"
|
|
$keyFile = "$env:USERPROFILE\.psmux\$SESSION.key"
|
|
$port = [int](Get-Content $portFile).Trim()
|
|
$key = (Get-Content $keyFile).Trim()
|
|
|
|
$tcp = [System.Net.Sockets.TcpClient]::new("127.0.0.1", $port)
|
|
$tcp.NoDelay = $true
|
|
$tcp.ReceiveTimeout = 10000
|
|
$stream = $tcp.GetStream()
|
|
$enc = [System.Text.UTF8Encoding]::new($false)
|
|
$reader = [System.IO.StreamReader]::new($stream, $enc, $false, 131072)
|
|
$writer = [System.IO.StreamWriter]::new($stream, $enc, 4096)
|
|
$writer.NewLine = "`n"
|
|
$writer.AutoFlush = $false
|
|
|
|
$writer.WriteLine("AUTH $key"); $writer.Flush()
|
|
$auth = $reader.ReadLine()
|
|
if ($auth -ne "OK") { Write-Host "AUTH FAILED"; $tcp.Close(); exit 1 }
|
|
|
|
$writer.WriteLine("PERSISTENT"); $writer.Flush()
|
|
|
|
return @{ tcp = $tcp; reader = $reader; writer = $writer }
|
|
}
|
|
|
|
function Send-Fire($conn, $cmd) {
|
|
$conn.writer.WriteLine($cmd)
|
|
$conn.writer.Flush()
|
|
}
|
|
|
|
function Get-Dump($conn) {
|
|
$conn.writer.WriteLine("dump-state")
|
|
$conn.writer.Flush()
|
|
return $conn.reader.ReadLine()
|
|
}
|
|
|
|
function Get-FreshDump($conn) {
|
|
# The server pushes frames asynchronously to PERSISTENT connections
|
|
# (event-driven rendering). After Send-Fire commands, stale pushed
|
|
# frames may sit in the TCP read buffer ahead of our dump-state
|
|
# response. The real psmux client drains all frames keeping only
|
|
# the latest (client.rs line 582-610); we must do the same.
|
|
#
|
|
# Strategy: send dump-state, then drain ALL available lines (stale
|
|
# push frames + response), keeping only the last valid frame.
|
|
for ($attempt = 0; $attempt -lt 10; $attempt++) {
|
|
$conn.writer.WriteLine("dump-state")
|
|
$conn.writer.Flush()
|
|
$best = $null
|
|
$oldTimeout = $conn.tcp.ReceiveTimeout
|
|
# Use a generous timeout for the first read (server needs time
|
|
# to process), then switch to a short timeout to drain remaining
|
|
# queued frames without blocking.
|
|
$conn.tcp.ReceiveTimeout = 2000
|
|
for ($j = 0; $j -lt 200; $j++) {
|
|
try {
|
|
$line = $conn.reader.ReadLine()
|
|
} catch {
|
|
break # timeout - no more data
|
|
}
|
|
if ($null -eq $line) { break }
|
|
if ($line -ne "NC" -and $line.Length -gt 100) {
|
|
$best = $line
|
|
}
|
|
# After first valid frame, drain remaining quickly
|
|
if ($best) { $conn.tcp.ReceiveTimeout = 50 }
|
|
}
|
|
$conn.tcp.ReceiveTimeout = $oldTimeout
|
|
if ($best) { return $best }
|
|
Start-Sleep -Milliseconds 100
|
|
}
|
|
return $null
|
|
}
|
|
|
|
# ============================================================
|
|
Write-Host ""
|
|
Write-Host ("=" * 70)
|
|
Write-Host "GITHUB ISSUE REPRODUCTION TESTS"
|
|
Write-Host ("=" * 70)
|
|
|
|
# ============================================================
|
|
# BUG 1: Issue #25 - Active window tab not updating after select-window
|
|
# ============================================================
|
|
Write-Host ""
|
|
Write-Host ("=" * 70)
|
|
Write-Host "BUG 1: Active window tab not updating (Issue #25)"
|
|
Write-Host ("=" * 70)
|
|
|
|
New-TestSession
|
|
$conn = Connect-TCP
|
|
|
|
# Query base-index so we use correct window numbers regardless of config
|
|
$baseIdx = & $PSMUX show-options -t $SESSION -v base-index 2>&1
|
|
$baseIdx = [int]($baseIdx.ToString().Trim())
|
|
if ($baseIdx -lt 0 -or $baseIdx -gt 100) { $baseIdx = 0 }
|
|
Write-Info "base-index=$baseIdx (windows will be $baseIdx, $($baseIdx+1), $($baseIdx+2))"
|
|
|
|
# Create 3 windows
|
|
Send-Fire $conn "new-window"
|
|
Start-Sleep -Seconds 2
|
|
Send-Fire $conn "new-window"
|
|
Start-Sleep -Seconds 2
|
|
|
|
# Get initial state - should show last window as active (array[2])
|
|
$state1 = Get-FreshDump $conn
|
|
$json1 = $state1 | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
$activeWindows1 = @($json1.windows | Where-Object { $_.active -eq $true })
|
|
|
|
Write-Test "Three windows created, third is active"
|
|
if ($activeWindows1.Count -eq 1) {
|
|
Write-Pass "Exactly 1 active window before switch"
|
|
} else {
|
|
Write-Fail "Expected 1 active window, got $($activeWindows1.Count)"
|
|
}
|
|
|
|
# Switch to first window via select-window (base-index aware)
|
|
$firstWin = $baseIdx
|
|
Write-Test "select-window $firstWin updates active flag in dump-state"
|
|
Send-Fire $conn "select-window $firstWin"
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
# Get new state
|
|
$state2 = Get-FreshDump $conn
|
|
$json2 = $state2 | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
$activeWindows2 = @($json2.windows | Where-Object { $_.active -eq $true })
|
|
|
|
if ($activeWindows2.Count -eq 1) {
|
|
$activeIdx = 0
|
|
for ($i = 0; $i -lt $json2.windows.Count; $i++) {
|
|
if ($json2.windows[$i].active) { $activeIdx = $i }
|
|
}
|
|
if ($activeIdx -eq 0) {
|
|
Write-Pass "First window (array[0]) is now active after select-window $firstWin"
|
|
} else {
|
|
Write-Fail "Active is array[$activeIdx], expected array[0] after select-window $firstWin"
|
|
}
|
|
} else {
|
|
Write-Fail "Expected 1 active window after select-window, got $($activeWindows2.Count)"
|
|
}
|
|
|
|
# Switch to second window (array index 1)
|
|
$secondWin = $baseIdx + 1
|
|
Write-Test "select-window $secondWin updates active flag"
|
|
Send-Fire $conn "select-window $secondWin"
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
$state3 = Get-FreshDump $conn
|
|
$json3 = $state3 | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
$activeIdx3 = -1
|
|
for ($i = 0; $i -lt $json3.windows.Count; $i++) {
|
|
if ($json3.windows[$i].active) { $activeIdx3 = $i }
|
|
}
|
|
if ($activeIdx3 -eq 1) {
|
|
Write-Pass "Second window (array[1]) is active after select-window $secondWin"
|
|
} else {
|
|
Write-Fail "Active is array[$activeIdx3], expected array[1] after select-window $secondWin"
|
|
}
|
|
|
|
try { $conn.tcp.Close() } catch {}
|
|
& $PSMUX kill-session -t $SESSION 2>$null
|
|
Start-Sleep 2
|
|
|
|
# ============================================================
|
|
# BUG 2: Issue #19 - bind-key parsing strips command flags
|
|
# ============================================================
|
|
Write-Host ""
|
|
Write-Host ("=" * 70)
|
|
Write-Host "BUG 2: bind-key flag stripping bug (Issue #19)"
|
|
Write-Host ("=" * 70)
|
|
|
|
New-TestSession
|
|
$conn = Connect-TCP
|
|
|
|
# Test: bind-key with command that has flags
|
|
Write-Test "bind-key r split-window -h preserves -h flag"
|
|
Send-Fire $conn "bind-key r split-window -h"
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
# Use CLI list-keys (reads all output correctly) instead of raw TCP ReadLine
|
|
$keys = & $PSMUX list-keys -t $SESSION 2>&1 | Out-String
|
|
|
|
if ("$keys" -match "split-window -h" -or "$keys" -match "split-window.*-h") {
|
|
Write-Pass "bind-key r: command includes -h flag"
|
|
} else {
|
|
Write-Fail "bind-key r: -h flag was stripped! Got: $keys"
|
|
}
|
|
|
|
# Test: bind-key with dash as key
|
|
Write-Test "bind-key - split-window -v (dash as key)"
|
|
Send-Fire $conn "bind-key - split-window -v"
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
$keys2 = & $PSMUX list-keys -t $SESSION 2>&1 | Out-String
|
|
|
|
if ("$keys2" -match 'split-window.*-v') {
|
|
Write-Pass "bind-key -: dash key is recognized"
|
|
} else {
|
|
Write-Fail "bind-key -: dash key was treated as a flag and dropped! Got: $keys2"
|
|
}
|
|
|
|
# Test: bind-key with -T and command flags
|
|
Write-Test "bind-key -T prefix v split-window -v"
|
|
Send-Fire $conn "bind-key -T prefix v split-window -v"
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
$keys3 = & $PSMUX list-keys -t $SESSION 2>&1 | Out-String
|
|
|
|
if ("$keys3" -match "split-window -v" -or "$keys3" -match "split-window.*-v") {
|
|
Write-Pass "bind-key with -T: command -v flag preserved"
|
|
} else {
|
|
Write-Fail "bind-key with -T: -v flag was stripped! Got: $keys3"
|
|
}
|
|
|
|
try { $conn.tcp.Close() } catch {}
|
|
& $PSMUX kill-session -t $SESSION 2>$null
|
|
Start-Sleep 2
|
|
|
|
# ============================================================
|
|
# BUG 3: Issue #19 - Status bar colors stuck on yellow/green
|
|
# ============================================================
|
|
Write-Host ""
|
|
Write-Host ("=" * 70)
|
|
Write-Host "BUG 3: Status bar colors not configurable (Issue #19)"
|
|
Write-Host ("=" * 70)
|
|
|
|
New-TestSession
|
|
$conn = Connect-TCP
|
|
|
|
# Get initial dump-state to check what style fields are available
|
|
$state = Get-FreshDump $conn
|
|
$json = $state | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
|
|
Write-Test "dump-state includes window-status-current-style"
|
|
$hasWscStyle = $json.PSObject.Properties.Name -contains "wsc_style"
|
|
$hasWsStyle = $json.PSObject.Properties.Name -contains "ws_style"
|
|
$hasWscstyle2 = $json.PSObject.Properties.Name -contains "window_status_current_style"
|
|
if ($hasWscStyle -or $hasWscstyle2) {
|
|
Write-Pass "dump-state has window-status-current-style field"
|
|
} else {
|
|
Write-Fail "dump-state MISSING window-status-current-style field (client can't style tabs!)"
|
|
Write-Info "Available fields: $($json.PSObject.Properties.Name -join ', ')"
|
|
}
|
|
|
|
Write-Test "dump-state includes window-status-style"
|
|
if ($hasWsStyle -or ($json.PSObject.Properties.Name -contains "window_status_style")) {
|
|
Write-Pass "dump-state has window-status-style field"
|
|
} else {
|
|
Write-Fail "dump-state MISSING window-status-style field"
|
|
}
|
|
|
|
# Test setting status-style and checking it appears
|
|
Write-Test "set status-style is reflected in dump-state"
|
|
Send-Fire $conn 'set status-style "bg=colour235 fg=colour136"'
|
|
Start-Sleep -Milliseconds 500
|
|
$state2 = Get-FreshDump $conn
|
|
$json2 = $state2 | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
if ($json2.status_style -match "colour235" -or $json2.status_style -match "235") {
|
|
Write-Pass "status-style updated in dump-state: $($json2.status_style)"
|
|
} else {
|
|
Write-Fail "status-style NOT updated in dump-state. Got: $($json2.status_style)"
|
|
}
|
|
|
|
try { $conn.tcp.Close() } catch {}
|
|
& $PSMUX kill-session -t $SESSION 2>$null
|
|
Start-Sleep 2
|
|
|
|
# ============================================================
|
|
# CLEANUP
|
|
# ============================================================
|
|
Write-Host ""
|
|
Write-Info "Final cleanup..."
|
|
& $PSMUX kill-server 2>$null
|
|
Start-Sleep 2
|
|
|
|
# ============================================================
|
|
# SUMMARY
|
|
# ============================================================
|
|
Write-Host ""
|
|
Write-Host ("=" * 70)
|
|
Write-Host "GITHUB ISSUES TEST SUMMARY" -ForegroundColor White
|
|
Write-Host ("=" * 70)
|
|
Write-Host "Passed: $($script:TestsPassed)" -ForegroundColor Green
|
|
Write-Host "Failed: $($script:TestsFailed)" -ForegroundColor $(if ($script:TestsFailed -gt 0) { "Red" } else { "Green" })
|
|
Write-Host "Total: $($script:TestsPassed + $script:TestsFailed)"
|
|
Write-Host ""
|
|
Write-Host "Bugs identified:" -ForegroundColor Yellow
|
|
Write-Host " 1. SelectWindow handler missing meta_dirty=true -> stale tab colors"
|
|
Write-Host " 2. bind-key TCP parser strips ALL '-' prefixed args including command flags"
|
|
Write-Host " 3. window-status-current-style not sent in dump-state -> client hardcodes yellow"
|
|
Write-Host ("=" * 70)
|
|
|
|
if ($script:TestsFailed -gt 0) { exit 1 }
|
|
exit 0
|