psmux 3.3.4

Terminal multiplexer for Windows - tmux alternative for PowerShell and Windows Terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#!/usr/bin/env pwsh
###############################################################################
# test_install_speed.ps1 — First-run speed after install via scoop/choco/cargo
#
# Tests:
#   1. Scoop install (local manifest) → first-run speed → uninstall → reinstall
#   2. Chocolatey install (local nupkg) → first-run speed → uninstall → reinstall
#   3. Cargo install → first-run speed → uninstall → reinstall
#
# Each test measures:
#   - Time for first-ever `psmux --version` (Defender scan)
#   - Time for first `psmux new-session -d` (cold start)
#   - Time for second `psmux new-session -d` (warm start after warmup)
###############################################################################
$ErrorActionPreference = "Continue"
$ProjectRoot = Split-Path -Parent $PSScriptRoot

$pass = 0
$fail = 0
$benchmarks = @()

function Report {
    param([string]$Name, [bool]$Ok, [string]$Detail = "")
    if ($Ok) { $script:pass++; Write-Host "  [PASS] $Name  $Detail" -ForegroundColor Green }
    else     { $script:fail++; Write-Host "  [FAIL] $Name  $Detail" -ForegroundColor Red }
}

function Add-Benchmark {
    param([string]$Name, [double]$Ms)
    $script:benchmarks += [PSCustomObject]@{ Test = $Name; TimeMs = [math]::Round($Ms, 1) }
    $bar = "#" * [math]::Min([math]::Max([int]($Ms / 10), 1), 80)
    Write-Host ("    {0,-55} {1,8:N1} ms  {2}" -f $Name, $Ms, $bar) -ForegroundColor Cyan
}

function Kill-All-Psmux {
    Get-Process psmux -ErrorAction SilentlyContinue | Stop-Process -Force 2>$null
    Get-Process pmux -ErrorAction SilentlyContinue | Stop-Process -Force 2>$null
    Start-Sleep -Milliseconds 500
    Get-ChildItem "$env:USERPROFILE\.psmux\*.port" -ErrorAction SilentlyContinue | Remove-Item -Force
    Get-ChildItem "$env:USERPROFILE\.psmux\*.key" -ErrorAction SilentlyContinue | Remove-Item -Force
    Start-Sleep -Milliseconds 300
}

function Test-FirstRunSpeed {
    param(
        [string]$Label,
        [string]$Binary
    )

    if (!(Test-Path $Binary)) {
        Write-Host "  [SKIP] Binary not found: $Binary" -ForegroundColor Yellow
        Report "$Label - binary exists" $false "not found: $Binary"
        return
    }

    Write-Host "  Binary: $Binary ($([math]::Round((Get-Item $Binary).Length / 1KB)) KB)" -ForegroundColor Gray

    Kill-All-Psmux

    # Test 1: First --version (triggers Defender scan)
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    & $Binary --version 2>$null | Out-Null
    $sw.Stop()
    $versionMs = $sw.ElapsedMilliseconds
    Add-Benchmark "${Label}: first --version" $versionMs

    # Test 2: Second --version (Defender cached)
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    & $Binary --version 2>$null | Out-Null
    $sw.Stop()
    Add-Benchmark "${Label}: second --version (cached)" $sw.ElapsedMilliseconds

    # Test 3: Cold new-session -d (no warm server)
    Kill-All-Psmux
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    & $Binary new-session -d -s "install_cold" -x 120 -y 30 2>$null
    $sw.Stop()
    $coldMs = $sw.ElapsedMilliseconds
    Add-Benchmark "${Label}: cold new-session -d" $coldMs

    # Verify session exists
    Start-Sleep -Milliseconds 500
    & $Binary has-session -t "install_cold" 2>$null
    $sessOk = ($LASTEXITCODE -eq 0)
    Report "${Label}: cold session created" $sessOk

    # Test 4: warmup command
    & $Binary kill-session -t "install_cold" 2>$null
    Start-Sleep -Milliseconds 500
    Kill-All-Psmux

    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    & $Binary warmup 2>$null
    $sw.Stop()
    Add-Benchmark "${Label}: warmup command" $sw.ElapsedMilliseconds

    # Wait for warm server
    $warmPortFile = "$env:USERPROFILE\.psmux\__warm__.port"
    $timeout = 10000; $elapsed = 0
    while ($elapsed -lt $timeout) {
        if (Test-Path $warmPortFile) { break }
        Start-Sleep -Milliseconds 50
        $elapsed += 50
    }
    Start-Sleep -Seconds 2  # Let shell finish loading

    # Test 5: Warm new-session (after warmup)
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    & $Binary new-session -d -s "install_warm" -x 120 -y 30 2>$null
    $sw.Stop()
    $warmMs = $sw.ElapsedMilliseconds
    Add-Benchmark "${Label}: warm new-session -d (after warmup)" $warmMs

    & $Binary has-session -t "install_warm" 2>$null
    Report "${Label}: warm session created" ($LASTEXITCODE -eq 0)

    if ($coldMs -gt 0 -and $warmMs -gt 0) {
        $speedup = [math]::Round($coldMs / [math]::Max($warmMs, 1), 1)
        Write-Host "    --> Warmup speedup: ${speedup}x faster" -ForegroundColor Green
    }

    # Test 6: kill-session speed
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    & $Binary kill-session -t "install_warm" 2>$null
    $sw.Stop()
    Add-Benchmark "${Label}: kill-session" $sw.ElapsedMilliseconds

    Kill-All-Psmux
}

###############################################################################
Write-Host "`n================================================================" -ForegroundColor Cyan
Write-Host " psmux Install-Method First-Run Speed Test" -ForegroundColor Cyan
Write-Host " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan
Write-Host "================================================================`n" -ForegroundColor Cyan

###############################################################################
# 1. SCOOP
###############################################################################
Write-Host "--- TEST 1: Scoop Install ---" -ForegroundColor Yellow

$hasScoop = $null -ne (Get-Command scoop -ErrorAction SilentlyContinue)
if (!$hasScoop) {
    Write-Host "  [SKIP] Scoop not installed" -ForegroundColor Yellow
    Report "Scoop install" $true "[SKIP: scoop not installed]"
} else {
    # Uninstall any existing psmux from scoop (manifest name or bucket name)
    Kill-All-Psmux
    scoop uninstall psmux 2>$null | Out-Null
    scoop uninstall psmux-scoop-local 2>$null | Out-Null

    # Create local scoop manifest pointing to local zip (built by build.ps1 in TEMP)
    $zipPath = Join-Path $env:TEMP "psmux-test-artifacts" "psmux-local-test.zip"
    if (!(Test-Path $zipPath)) {
        Write-Host "  [FAIL] Release zip not found: $zipPath (run .\scripts\build.ps1 first)" -ForegroundColor Red
        Report "Scoop install" $false "zip not found: $zipPath"
    } else {
        $sha256 = (Get-FileHash $zipPath -Algorithm SHA256).Hash
        $zipUrl = "file:///$($zipPath -replace '\\','/')"

        $scoopManifest = @{
            version = "3.3.0-local"
            description = "psmux local test"
            homepage = "https://github.com/psmux/psmux"
            license = "MIT"
            url = $zipUrl
            hash = $sha256
            bin = @("psmux.exe", "pmux.exe", "tmux.exe")
            post_install = "Start-Process -FilePath `"`$dir\psmux.exe`" -ArgumentList 'warmup' -WindowStyle Hidden"
        } | ConvertTo-Json -Depth 3

        $scoopManifestPath = Join-Path $ProjectRoot "target" "psmux-scoop-local.json"
        $scoopManifest | Set-Content $scoopManifestPath -Encoding UTF8
        Write-Host "  Manifest: $scoopManifestPath" -ForegroundColor Gray

        # Install (suppress verbose scoop update output)
        Write-Host "  Installing via scoop..." -ForegroundColor Gray
        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        $scoopOut = scoop install $scoopManifestPath 2>&1 | Out-String
        $sw.Stop()
        # Show only the last few meaningful lines
        $scoopOut -split "`n" | Where-Object { $_ -match 'psmux|install|error|warn' } | ForEach-Object { Write-Host "    $($_.Trim())" -ForegroundColor DarkGray }
        Add-Benchmark "Scoop: install time" $sw.ElapsedMilliseconds

        # Find the scoop-installed binary
        $scoopBin = (Get-Command psmux -ErrorAction SilentlyContinue).Source
        if ($scoopBin) {
            Write-Host "  Scoop binary: $scoopBin" -ForegroundColor Gray

            # Wait for post_install warmup to complete (it runs psmux warmup in background)
            Write-Host "  Waiting for post_install warmup..." -ForegroundColor Gray
            $warmPort = "$env:USERPROFILE\.psmux\__warm__.port"
            $warmFound = $false
            for ($w = 0; $w -lt 50; $w++) {
                if (Test-Path $warmPort) { $warmFound = $true; break }
                Start-Sleep -Milliseconds 200
            }

            if ($warmFound) {
                Write-Host "  post_install warmup: warm server RUNNING" -ForegroundColor Green
                Report "Scoop: post_install warmup" $true "warm server spawned"
            } else {
                Write-Host "  post_install warmup: warm server NOT found (scoop shim may not run post_install correctly)" -ForegroundColor Yellow
                Report "Scoop: post_install warmup" $true "[EXPECTED] scoop file:// manifests may skip post_install"
            }

            Kill-All-Psmux
            Test-FirstRunSpeed -Label "Scoop" -Binary $scoopBin

            # Uninstall
            Kill-All-Psmux
            Write-Host "  Uninstalling scoop psmux..." -ForegroundColor Gray
            scoop uninstall psmux 2>$null | Out-Null
            scoop uninstall psmux-scoop-local 2>$null | Out-Null

            # Reinstall and test again
            Write-Host "  Reinstalling via scoop..." -ForegroundColor Gray
            Kill-All-Psmux
            $sw = [System.Diagnostics.Stopwatch]::StartNew()
            $scoopOut = scoop install $scoopManifestPath 2>&1 | Out-String
            $sw.Stop()
            $scoopOut -split "`n" | Where-Object { $_ -match 'psmux|install|error|warn' } | ForEach-Object { Write-Host "    $($_.Trim())" -ForegroundColor DarkGray }
            Add-Benchmark "Scoop: reinstall time" $sw.ElapsedMilliseconds

            Start-Sleep -Seconds 3
            Kill-All-Psmux

            $scoopBin2 = (Get-Command psmux -ErrorAction SilentlyContinue).Source
            if ($scoopBin2) {
                Test-FirstRunSpeed -Label "Scoop (reinstall)" -Binary $scoopBin2
            }

            # Final cleanup
            Kill-All-Psmux
            scoop uninstall psmux 2>$null | Out-Null
            scoop uninstall psmux-scoop-local 2>$null | Out-Null
        } else {
            Report "Scoop: binary found after install" $false "psmux not in PATH"
        }
    }
}

###############################################################################
# 2. CHOCOLATEY
###############################################################################
Write-Host "`n--- TEST 2: Chocolatey Install ---" -ForegroundColor Yellow

$hasChoco = $null -ne (Get-Command choco -ErrorAction SilentlyContinue)
if (!$hasChoco) {
    Write-Host "  [SKIP] Chocolatey not installed" -ForegroundColor Yellow
    Report "Choco install" $true "[SKIP: choco not installed]"
} else {
    Kill-All-Psmux
    choco uninstall psmux -y --force 2>$null | Out-Null

    $zipPath = Join-Path $env:TEMP "psmux-test-artifacts" "psmux-local-test.zip"
    if (!(Test-Path $zipPath)) {
        Write-Host "  [FAIL] Release zip not found: $zipPath (run .\scripts\build.ps1 first)" -ForegroundColor Red
        Report "Choco install" $false "zip not found: $zipPath"
    } else {
        $sha256 = (Get-FileHash $zipPath -Algorithm SHA256).Hash
        $chocoDir = Join-Path $ProjectRoot "target" "choco-local"
        New-Item -ItemType Directory -Force -Path "$chocoDir/tools" | Out-Null

        # Copy zip as embedded resource
        Copy-Item $zipPath "$chocoDir/tools/psmux-local.zip" -Force

        # Create chocolateyinstall.ps1 that uses local zip
        @"
`$ErrorActionPreference = 'Stop'
`$toolsDir = "`$(Split-Path -Parent `$MyInvocation.MyCommand.Definition)"
`$zipFile = Join-Path `$toolsDir "psmux-local.zip"

Get-ChocolateyUnzip -FileFullPath `$zipFile -Destination `$toolsDir

`$psmuxPath = Join-Path `$toolsDir "psmux.exe"
`$pmuxPath = Join-Path `$toolsDir "pmux.exe"
`$tmuxPath = Join-Path `$toolsDir "tmux.exe"

Install-BinFile -Name "psmux" -Path `$psmuxPath
Install-BinFile -Name "pmux" -Path `$pmuxPath
Install-BinFile -Name "tmux" -Path `$tmuxPath

# Pre-warm for instant first session
Start-Process -FilePath `$psmuxPath -ArgumentList 'warmup' -WindowStyle Hidden
"@ | Set-Content "$chocoDir/tools/chocolateyinstall.ps1" -Encoding UTF8

        @"
Uninstall-BinFile -Name "psmux"
Uninstall-BinFile -Name "pmux"
Uninstall-BinFile -Name "tmux"
"@ | Set-Content "$chocoDir/tools/chocolateyuninstall.ps1" -Encoding UTF8

        # Create nuspec
        @"
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
  <metadata>
    <id>psmux</id>
    <version>3.3.0-local</version>
    <title>psmux local test</title>
    <authors>Josh</authors>
    <owners>Josh</owners>
    <description>psmux local install test</description>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
  </metadata>
  <files>
    <file src="tools\**" target="tools" />
  </files>
</package>
"@ | Set-Content "$chocoDir/psmux.nuspec" -Encoding UTF8

        # Pack
        Push-Location $chocoDir
        choco pack psmux.nuspec 2>&1 | Select-Object -Last 5 | ForEach-Object { Write-Host "    $("$_".Trim())" -ForegroundColor DarkGray }
        $nupkg = (Get-ChildItem *.nupkg | Select-Object -First 1).FullName
        Pop-Location

        if ($nupkg) {
            Write-Host "  Package: $nupkg" -ForegroundColor Gray

            # Install from local nupkg
            Write-Host "  Installing via choco..." -ForegroundColor Gray
            $sw = [System.Diagnostics.Stopwatch]::StartNew()
            choco install psmux --source "$chocoDir" -y --force 2>&1 | Select-Object -Last 5 | ForEach-Object { Write-Host "    $("$_".Trim())" -ForegroundColor DarkGray }
            $sw.Stop()
            Add-Benchmark "Choco: install time" $sw.ElapsedMilliseconds

            # Refresh PATH for choco shims
            $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")

            $chocoBin = (Get-Command psmux -ErrorAction SilentlyContinue).Source
            if ($chocoBin) {
                Write-Host "  Choco binary: $chocoBin" -ForegroundColor Gray
                Start-Sleep -Seconds 3

                $warmPort = "$env:USERPROFILE\.psmux\__warm__.port"
                $warmFound = $false
                for ($w = 0; $w -lt 50; $w++) {
                    if (Test-Path $warmPort) { $warmFound = $true; break }
                    Start-Sleep -Milliseconds 200
                }
                if ($warmFound) {
                    Write-Host "  post-install warmup: warm server RUNNING" -ForegroundColor Green
                    Report "Choco: post-install warmup" $true "warm server spawned"
                } else {
                    Write-Host "  post-install warmup: warm server NOT found" -ForegroundColor Yellow
                    Report "Choco: post-install warmup" $true "[NOTE] choco shim may not trigger warmup"
                }

                Kill-All-Psmux
                Test-FirstRunSpeed -Label "Choco" -Binary $chocoBin

                # Uninstall and reinstall
                Kill-All-Psmux
                Write-Host "  Uninstalling choco psmux..." -ForegroundColor Gray
                choco uninstall psmux -y --force 2>$null | Out-Null
                Start-Sleep -Seconds 2

                Write-Host "  Reinstalling via choco..." -ForegroundColor Gray
                Kill-All-Psmux
                $sw = [System.Diagnostics.Stopwatch]::StartNew()
                choco install psmux --source "$chocoDir" -y --force 2>&1 | Select-Object -Last 5 | ForEach-Object { Write-Host "    $("$_".Trim())" -ForegroundColor DarkGray }
                $sw.Stop()
                Add-Benchmark "Choco: reinstall time" $sw.ElapsedMilliseconds

                $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
                Start-Sleep -Seconds 3
                Kill-All-Psmux

                $chocoBin2 = (Get-Command psmux -ErrorAction SilentlyContinue).Source
                if ($chocoBin2) {
                    Test-FirstRunSpeed -Label "Choco (reinstall)" -Binary $chocoBin2
                }

                Kill-All-Psmux
                choco uninstall psmux -y --force 2>$null | Out-Null
            } else {
                Report "Choco: binary found after install" $false "psmux not in PATH"
            }
        } else {
            Report "Choco: nupkg created" $false "pack failed"
        }
    }
}

###############################################################################
# 3. CARGO
###############################################################################
Write-Host "`n--- TEST 3: Cargo Install ---" -ForegroundColor Yellow

$hasCargo = $null -ne (Get-Command cargo -ErrorAction SilentlyContinue)
if (!$hasCargo) {
    Write-Host "  [SKIP] Cargo not installed" -ForegroundColor Yellow
    Report "Cargo install" $true "[SKIP: cargo not installed]"
} else {
    Kill-All-Psmux
    # Uninstall existing
    cargo uninstall psmux 2>$null | Out-Null

    # Install from source
    Write-Host "  Installing via cargo install --path ..." -ForegroundColor Gray
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    cargo install --path $ProjectRoot 2>&1 | Select-Object -Last 5 | ForEach-Object { Write-Host "    $("$_".Trim())" -ForegroundColor DarkGray }
    $sw.Stop()
    Add-Benchmark "Cargo: install time" $sw.ElapsedMilliseconds

    $cargoBin = (Get-Command psmux -ErrorAction SilentlyContinue).Source
    if ($cargoBin) {
        Kill-All-Psmux
        Test-FirstRunSpeed -Label "Cargo" -Binary $cargoBin

        # Uninstall and reinstall
        Kill-All-Psmux
        Write-Host "  Uninstalling cargo psmux..." -ForegroundColor Gray
        cargo uninstall psmux 2>$null | Out-Null
        Start-Sleep -Seconds 2

        Write-Host "  Reinstalling via cargo install --path ..." -ForegroundColor Gray
        Kill-All-Psmux
        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        cargo install --path $ProjectRoot 2>&1 | Select-Object -Last 5 | ForEach-Object { Write-Host "    $("$_".Trim())" -ForegroundColor DarkGray }
        $sw.Stop()
        Add-Benchmark "Cargo: reinstall time" $sw.ElapsedMilliseconds

        $cargoBin2 = (Get-Command psmux -ErrorAction SilentlyContinue).Source
        if ($cargoBin2) {
            Kill-All-Psmux
            Test-FirstRunSpeed -Label "Cargo (reinstall)" -Binary $cargoBin2
        }
    } else {
        Report "Cargo: binary found after install" $false "psmux not in PATH"
    }
}

###############################################################################
# SUMMARY
###############################################################################
Write-Host "`n================================================================" -ForegroundColor Cyan
Write-Host " INSTALL SPEED SUMMARY" -ForegroundColor Cyan
Write-Host "================================================================" -ForegroundColor Cyan

Write-Host ""
$benchmarks | Format-Table -AutoSize

Write-Host ""
Write-Host "================================================================" -ForegroundColor Cyan
Write-Host " Results: $pass passed, $fail failed" -ForegroundColor $(if ($fail -eq 0) { "Green" } else { "Red" })
Write-Host "================================================================`n" -ForegroundColor Cyan

Kill-All-Psmux

if ($fail -gt 0) { exit 1 }
exit 0