browser4-cli 4.12.0 → 4.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/browser4-cli-darwin-arm64 +0 -0
- package/bin/browser4-cli-darwin-x64 +0 -0
- package/bin/browser4-cli-linux-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-x64 +0 -0
- package/bin/browser4-cli-linux-x64 +0 -0
- package/bin/browser4-cli-win32-x64.exe +0 -0
- package/package.json +1 -1
- package/scripts/README.md +4 -3
- package/scripts/build-all-platforms.ps1 +104 -104
- package/scripts/enumerate-help.ps1 +382 -382
- package/scripts/install-browser4-cli.ps1 +812 -812
- package/scripts/tests/install-browser4-cli.tests.ps1 +476 -462
|
@@ -1,462 +1,476 @@
|
|
|
1
|
-
#!/usr/bin/env pwsh
|
|
2
|
-
|
|
3
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
4
|
-
# CROSS-PLATFORM: This script must run on Linux, macOS, and Windows.
|
|
5
|
-
# - Use $IsWindows / $IsLinux / $IsMacOS for platform detection.
|
|
6
|
-
# - Use "($IsWindows -or $env:OS -eq 'Windows_NT')" for PS 5.1 compat.
|
|
7
|
-
# - Avoid Windows-only env vars ($env:TEMP) — use $env:TMPDIR fallback.
|
|
8
|
-
# - Guard "chcp" and other Windows-only commands behind platform checks.
|
|
9
|
-
# - Paths: use Join-Path / Split-Path; never bake \ or / as literal.
|
|
10
|
-
# - [System.IO.Path]::IsPathRooted is platform-aware — C:\foo is NOT
|
|
11
|
-
# rooted on Linux; test with platform-appropriate absolute paths.
|
|
12
|
-
# ═══════════════════════════════════════════════════════════════════
|
|
13
|
-
|
|
14
|
-
<#
|
|
15
|
-
.SYNOPSIS
|
|
16
|
-
Tests for install-browser4-cli.ps1
|
|
17
|
-
PowerShell 5.1+ only — zero external dependencies.
|
|
18
|
-
Run: powershell -NoProfile -ExecutionPolicy Bypass -File install-browser4-cli.tests.ps1
|
|
19
|
-
#>
|
|
20
|
-
|
|
21
|
-
$ErrorActionPreference = "Stop"
|
|
22
|
-
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
23
|
-
$installScript = Join-Path (Split-Path $scriptDir -Parent) "install-browser4-cli.ps1"
|
|
24
|
-
|
|
25
|
-
$pass = 0
|
|
26
|
-
$fail = 0
|
|
27
|
-
|
|
28
|
-
function Test($name, [ScriptBlock]$block) {
|
|
29
|
-
try {
|
|
30
|
-
$null = & $block
|
|
31
|
-
$script:pass++
|
|
32
|
-
Write-Host " PASS $name" -ForegroundColor Green
|
|
33
|
-
} catch {
|
|
34
|
-
$script:fail++
|
|
35
|
-
Write-Host " FAIL $name" -ForegroundColor Red
|
|
36
|
-
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function RunScript([string]$scriptArgs, [ref]$exitCode) {
|
|
41
|
-
# Use -Command to control stderr redirect (2> path must come before script args,
|
|
42
|
-
# which -File would pass as literal arguments to the script).
|
|
43
|
-
$tmpErr = [System.IO.Path]::GetTempFileName()
|
|
44
|
-
$cmd = "& '$installScript' $scriptArgs 2>'$tmpErr'"
|
|
45
|
-
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
46
|
-
$psi.FileName = if ($IsWindows -or ($env:OS -eq 'Windows_NT')) { "powershell.exe" } else { "pwsh" }
|
|
47
|
-
$psi.Arguments = "-NoProfile -ExecutionPolicy Bypass -Command `"$cmd`""
|
|
48
|
-
$psi.RedirectStandardOutput = $true
|
|
49
|
-
$psi.UseShellExecute = $false
|
|
50
|
-
$psi.CreateNoWindow = $true
|
|
51
|
-
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
52
|
-
$out = $proc.StandardOutput.ReadToEnd()
|
|
53
|
-
$proc.WaitForExit()
|
|
54
|
-
$exitCode.Value = $proc.ExitCode
|
|
55
|
-
$err = if (Test-Path $tmpErr) { Get-Content $tmpErr -Raw -ErrorAction SilentlyContinue; Remove-Item $tmpErr -Force -ErrorAction SilentlyContinue } else { "" }
|
|
56
|
-
return [PSCustomObject]@{ Output = $out; Error = $err; ExitCode = $proc.ExitCode }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
Write-Host "============================================" -ForegroundColor Cyan
|
|
60
|
-
Write-Host " install-browser4-cli.ps1 Test Suite" -ForegroundColor Cyan
|
|
61
|
-
Write-Host "============================================" -ForegroundColor Cyan
|
|
62
|
-
Write-Host ""
|
|
63
|
-
|
|
64
|
-
# ── Pre-flight ──
|
|
65
|
-
Write-Host "--- Pre-flight ---" -ForegroundColor Cyan
|
|
66
|
-
|
|
67
|
-
Test "file exists" {
|
|
68
|
-
if (-not (Test-Path $installScript)) { throw "Not found: $installScript" }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
Test "file is readable" {
|
|
72
|
-
$null = Get-Content $installScript -Raw -ErrorAction Stop
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
Test "no non-ASCII bytes" {
|
|
76
|
-
$bytes = [System.IO.File]::ReadAllBytes($installScript)
|
|
77
|
-
$nonAscii = @($bytes | Where-Object { $_ -gt 127 })
|
|
78
|
-
if ($nonAscii.Count -gt 0) {
|
|
79
|
-
throw "Found $($nonAscii.Count) non-ASCII bytes"
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
Test "AST parses without errors" {
|
|
84
|
-
$parseErrors = $null
|
|
85
|
-
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
|
|
86
|
-
$installScript, [ref]$null, [ref]$parseErrors
|
|
87
|
-
)
|
|
88
|
-
if ($parseErrors.Count -gt 0) {
|
|
89
|
-
$msg = ($parseErrors | ForEach-Object { "L$($_.Extent.StartLineNumber): $($_.Message)" }) -join "; "
|
|
90
|
-
throw $msg
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
Write-Host ""
|
|
95
|
-
|
|
96
|
-
# ── Param block (via AST) ──
|
|
97
|
-
Write-Host "--- Param block ---" -ForegroundColor Cyan
|
|
98
|
-
|
|
99
|
-
$content = Get-Content $installScript -Raw
|
|
100
|
-
$parseErrors = $null
|
|
101
|
-
$ast = [System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$null, [ref]$parseErrors)
|
|
102
|
-
|
|
103
|
-
Test "param block defines all expected parameters" {
|
|
104
|
-
$expected = @('Version', 'InstallDir', 'Source', 'AddToPath',
|
|
105
|
-
'Silent', 'DryRun', 'SkipIfInstalled', 'SkipLocal', 'Locate')
|
|
106
|
-
# Extract param block text via AST extents
|
|
107
|
-
$paramAst = $ast.ParamBlock
|
|
108
|
-
if (-not $paramAst) { throw "Could not find param block AST" }
|
|
109
|
-
$txt = $content.Substring($paramAst.Extent.StartOffset, $paramAst.Extent.EndOffset - $paramAst.Extent.StartOffset)
|
|
110
|
-
foreach ($p in $expected) {
|
|
111
|
-
if ($txt -notmatch ('\$' + $p + '\b')) {
|
|
112
|
-
throw "Missing parameter: $p"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
Test "Source has ValidateSet with empty string" {
|
|
118
|
-
if ($content -notmatch 'ValidateSet\("",\s*"github",\s*"oss"\)') {
|
|
119
|
-
throw "ValidateSet must include empty string for iex compatibility"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
Write-Host ""
|
|
124
|
-
|
|
125
|
-
# ── Locate mode ──
|
|
126
|
-
Write-Host "--- Locate mode ---" -ForegroundColor Cyan
|
|
127
|
-
|
|
128
|
-
$ec = 0
|
|
129
|
-
$r = RunScript -scriptArgs "-Locate" -exitCode ([ref]$ec)
|
|
130
|
-
|
|
131
|
-
Test "-Locate exits with code 0" {
|
|
132
|
-
if ($ec -ne 0) { throw "Exit code: $ec" }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
Test "-Locate shows platform key" {
|
|
136
|
-
if ($r.Output -notmatch 'Platform key') {
|
|
137
|
-
throw "Missing 'Platform key' in: $($r.Output.Substring(0, [Math]::Min(500, $r.Output.Length)))"
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
Test "-Locate shows binary name" {
|
|
142
|
-
if ($r.Output -notmatch 'Binary name') {
|
|
143
|
-
throw "Missing 'Binary name' in output (len=$($r.Output.Length))"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
Test "-Locate shows download order" {
|
|
148
|
-
if ($r.Output -notmatch 'Download order') {
|
|
149
|
-
throw "Missing 'Download order' in output"
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
Write-Host ""
|
|
154
|
-
|
|
155
|
-
# ── Download URLs (via -Locate output) ──
|
|
156
|
-
Write-Host "--- Download URLs ---" -ForegroundColor Cyan
|
|
157
|
-
|
|
158
|
-
Test "locate shows correct GitHub latest/download URL" {
|
|
159
|
-
if ($r.Output -notmatch 'github\.com/platonai/Browser4/releases/latest/download/') {
|
|
160
|
-
$lines = ($r.Output -split '\n' | Where-Object { $_ -match 'ownload' }) -join '; '
|
|
161
|
-
throw "GitHub latest URL not found. Download lines: $lines"
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
Test "locate shows correct OSS download/latest URL" {
|
|
166
|
-
if ($r.Output -notmatch 'oss-cn-beijing.*?releases/download/latest/') {
|
|
167
|
-
$lines = ($r.Output -split '\n' | Where-Object { $_ -match 'ownload' }) -join '; '
|
|
168
|
-
throw "OSS latest URL not found. Download lines: $lines"
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
# Test versioned URLs
|
|
173
|
-
$ec2 = 0
|
|
174
|
-
$r2 = RunScript -scriptArgs "-Version v4.11.0 -Locate" -exitCode ([ref]$ec2)
|
|
175
|
-
|
|
176
|
-
Test "-Version shows tag-based URLs" {
|
|
177
|
-
if ($r2.Output -notmatch 'releases/download/v4\.11\.0/') {
|
|
178
|
-
throw "Versioned URL not in output"
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
Write-Host ""
|
|
183
|
-
|
|
184
|
-
# ── Parameter acceptance ──
|
|
185
|
-
Write-Host "--- Parameter acceptance ---" -ForegroundColor Cyan
|
|
186
|
-
|
|
187
|
-
Test "-SkipIfInstalled flag accepted" {
|
|
188
|
-
$ec3 = 0; $r3 = RunScript -scriptArgs "-SkipIfInstalled -DryRun" -exitCode ([ref]$ec3)
|
|
189
|
-
if ($ec3 -ne 0) { throw "Exit code: $ec3, output: $($r3.Output)" }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
Test "-SkipLocal flag accepted" {
|
|
193
|
-
$ec4 = 0; $r4 = RunScript -scriptArgs "-SkipLocal -DryRun" -exitCode ([ref]$ec4)
|
|
194
|
-
if ($ec4 -ne 0) { throw "Exit code: $ec4, output: $($r4.Output)" }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
Test "-Force rejected (replaced by -SkipIfInstalled)" {
|
|
198
|
-
$ec5 = 0; $r5 = RunScript -scriptArgs "-Force -DryRun" -exitCode ([ref]$ec5)
|
|
199
|
-
if ($r5.Error -notmatch 'Force') {
|
|
200
|
-
throw "-Force should be rejected, got: $($r5.Error)"
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
Test "-Source oss accepted" {
|
|
205
|
-
$ec6 = 0; $r6 = RunScript -scriptArgs "-Source oss -DryRun" -exitCode ([ref]$ec6)
|
|
206
|
-
if ($ec6 -ne 0) { throw "Exit code: $ec6, output: $($r6.Output)" }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
Test "-Source github accepted" {
|
|
210
|
-
$ec7 = 0; $r7 = RunScript -scriptArgs "-Source github -DryRun" -exitCode ([ref]$ec7)
|
|
211
|
-
if ($ec7 -ne 0) { throw "Exit code: $ec7, output: $($r7.Output)" }
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
Test "-Source invalid rejected" {
|
|
215
|
-
$ec8 = 0; $r8 = RunScript -scriptArgs "-Source invalid -DryRun" -exitCode ([ref]$ec8)
|
|
216
|
-
if ($r8.Error -notmatch 'Source|invalid|parameter') {
|
|
217
|
-
throw "-Source invalid should be rejected, got: $($r8.Error)"
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
Test "-Silent flag accepted" {
|
|
222
|
-
$ec9 = 0; $r9 = RunScript -scriptArgs "-Silent -DryRun" -exitCode ([ref]$ec9)
|
|
223
|
-
if ($ec9 -ne 0) { throw "Exit code: $ec9, output: $($r9.Output)" }
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
Test "-Version flag accepted" {
|
|
227
|
-
$ec10 = 0; $r10 = RunScript -scriptArgs "-Version v4.11.0 -DryRun" -exitCode ([ref]$ec10)
|
|
228
|
-
if ($ec10 -ne 0) { throw "Exit code: $ec10, output: $($r10.Output)" }
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
Write-Host ""
|
|
232
|
-
|
|
233
|
-
# ── Functions via dot-source ──
|
|
234
|
-
Write-Host "--- Functions ---" -ForegroundColor Cyan
|
|
235
|
-
|
|
236
|
-
# Strip trailing Main call and dot-source for function-level tests
|
|
237
|
-
$scriptContent = Get-Content $installScript -Raw
|
|
238
|
-
$scriptContent = $scriptContent -replace '\r?\nMain\s*$', ''
|
|
239
|
-
$scriptContent = $scriptContent -replace '\$ErrorActionPreference\s*=\s*"Stop"', ''
|
|
240
|
-
$sb = [ScriptBlock]::Create($scriptContent)
|
|
241
|
-
|
|
242
|
-
& {
|
|
243
|
-
# Suppress output
|
|
244
|
-
$Silent = $true
|
|
245
|
-
$DryRun = $false
|
|
246
|
-
$SkipLocal = $false
|
|
247
|
-
$Locate = $false
|
|
248
|
-
$Source = ""
|
|
249
|
-
$Version = ""
|
|
250
|
-
$InstallDir = ""
|
|
251
|
-
$AddToPath = $true
|
|
252
|
-
|
|
253
|
-
. $sb
|
|
254
|
-
|
|
255
|
-
Test "Get-PlatformKey returns valid format" {
|
|
256
|
-
$key = Get-PlatformKey
|
|
257
|
-
if ($key -notmatch '^(win32|linux|darwin)-(x64|arm64)$' -and
|
|
258
|
-
$key -notmatch '^linux-musl-(x64|arm64)$') {
|
|
259
|
-
throw "Unexpected platform key: $key"
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
Test "Get-BinaryName includes .exe on win32" {
|
|
264
|
-
$name = Get-BinaryName -PlatformKey "win32-x64"
|
|
265
|
-
if ($name -ne "browser4-cli-win32-x64.exe") { throw "Got: $name" }
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
Test "Get-BinaryName excludes .exe on linux" {
|
|
269
|
-
$name = Get-BinaryName -PlatformKey "linux-x64"
|
|
270
|
-
if ($name -ne "browser4-cli-linux-x64") { throw "Got: $name" }
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
Test "Get-BinaryName excludes .exe on darwin" {
|
|
274
|
-
$name = Get-BinaryName -PlatformKey "darwin-arm64"
|
|
275
|
-
if ($name -ne "browser4-cli-darwin-arm64") { throw "Got: $name" }
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
Test "Get-DefaultInstallDir returns non-empty" {
|
|
279
|
-
$dir = Get-DefaultInstallDir
|
|
280
|
-
if ([string]::IsNullOrEmpty($dir)) { throw "Empty install dir" }
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
Test "Test-ChinaLocale returns [bool]" {
|
|
284
|
-
$result = Test-ChinaLocale
|
|
285
|
-
if ($result -isnot [bool]) { throw "Expected [bool], got $($result.GetType())" }
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
Test "Find-LocalBinary returns null for non-existent" {
|
|
289
|
-
$result = Find-LocalBinary -BinaryName "nonexistent-file-xyz.exe"
|
|
290
|
-
if ($result -ne $null) { throw "Expected null, got: $result" }
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
Test "Test-LocalBinary returns false for empty string" {
|
|
294
|
-
if (Test-LocalBinary -Path "") { throw "Should be false" }
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
Test "Test-LocalBinary returns false for null" {
|
|
298
|
-
if (Test-LocalBinary -Path $null) { throw "Should be false" }
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
Write-Host ""
|
|
303
|
-
|
|
304
|
-
# ── New-Symlinks (b4 link logic) ──
|
|
305
|
-
Write-Host "--- New-Symlinks ---" -ForegroundColor Cyan
|
|
306
|
-
|
|
307
|
-
& {
|
|
308
|
-
$Silent = $true
|
|
309
|
-
$DryRun = $false
|
|
310
|
-
$SkipLocal = $false
|
|
311
|
-
$Locate = $false
|
|
312
|
-
$Source = ""
|
|
313
|
-
$Version = ""
|
|
314
|
-
$InstallDir = ""
|
|
315
|
-
$AddToPath = $true
|
|
316
|
-
|
|
317
|
-
. $sb
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
|
|
3
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
4
|
+
# CROSS-PLATFORM: This script must run on Linux, macOS, and Windows.
|
|
5
|
+
# - Use $IsWindows / $IsLinux / $IsMacOS for platform detection.
|
|
6
|
+
# - Use "($IsWindows -or $env:OS -eq 'Windows_NT')" for PS 5.1 compat.
|
|
7
|
+
# - Avoid Windows-only env vars ($env:TEMP) — use $env:TMPDIR fallback.
|
|
8
|
+
# - Guard "chcp" and other Windows-only commands behind platform checks.
|
|
9
|
+
# - Paths: use Join-Path / Split-Path; never bake \ or / as literal.
|
|
10
|
+
# - [System.IO.Path]::IsPathRooted is platform-aware — C:\foo is NOT
|
|
11
|
+
# rooted on Linux; test with platform-appropriate absolute paths.
|
|
12
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
<#
|
|
15
|
+
.SYNOPSIS
|
|
16
|
+
Tests for install-browser4-cli.ps1
|
|
17
|
+
PowerShell 5.1+ only — zero external dependencies.
|
|
18
|
+
Run: powershell -NoProfile -ExecutionPolicy Bypass -File install-browser4-cli.tests.ps1
|
|
19
|
+
#>
|
|
20
|
+
|
|
21
|
+
$ErrorActionPreference = "Stop"
|
|
22
|
+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
23
|
+
$installScript = Join-Path (Split-Path $scriptDir -Parent) "install-browser4-cli.ps1"
|
|
24
|
+
|
|
25
|
+
$pass = 0
|
|
26
|
+
$fail = 0
|
|
27
|
+
|
|
28
|
+
function Test($name, [ScriptBlock]$block) {
|
|
29
|
+
try {
|
|
30
|
+
$null = & $block
|
|
31
|
+
$script:pass++
|
|
32
|
+
Write-Host " PASS $name" -ForegroundColor Green
|
|
33
|
+
} catch {
|
|
34
|
+
$script:fail++
|
|
35
|
+
Write-Host " FAIL $name" -ForegroundColor Red
|
|
36
|
+
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function RunScript([string]$scriptArgs, [ref]$exitCode) {
|
|
41
|
+
# Use -Command to control stderr redirect (2> path must come before script args,
|
|
42
|
+
# which -File would pass as literal arguments to the script).
|
|
43
|
+
$tmpErr = [System.IO.Path]::GetTempFileName()
|
|
44
|
+
$cmd = "& '$installScript' $scriptArgs 2>'$tmpErr'"
|
|
45
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
46
|
+
$psi.FileName = if ($IsWindows -or ($env:OS -eq 'Windows_NT')) { "powershell.exe" } else { "pwsh" }
|
|
47
|
+
$psi.Arguments = "-NoProfile -ExecutionPolicy Bypass -Command `"$cmd`""
|
|
48
|
+
$psi.RedirectStandardOutput = $true
|
|
49
|
+
$psi.UseShellExecute = $false
|
|
50
|
+
$psi.CreateNoWindow = $true
|
|
51
|
+
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
52
|
+
$out = $proc.StandardOutput.ReadToEnd()
|
|
53
|
+
$proc.WaitForExit()
|
|
54
|
+
$exitCode.Value = $proc.ExitCode
|
|
55
|
+
$err = if (Test-Path $tmpErr) { Get-Content $tmpErr -Raw -ErrorAction SilentlyContinue; Remove-Item $tmpErr -Force -ErrorAction SilentlyContinue } else { "" }
|
|
56
|
+
return [PSCustomObject]@{ Output = $out; Error = $err; ExitCode = $proc.ExitCode }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Write-Host "============================================" -ForegroundColor Cyan
|
|
60
|
+
Write-Host " install-browser4-cli.ps1 Test Suite" -ForegroundColor Cyan
|
|
61
|
+
Write-Host "============================================" -ForegroundColor Cyan
|
|
62
|
+
Write-Host ""
|
|
63
|
+
|
|
64
|
+
# ── Pre-flight ──
|
|
65
|
+
Write-Host "--- Pre-flight ---" -ForegroundColor Cyan
|
|
66
|
+
|
|
67
|
+
Test "file exists" {
|
|
68
|
+
if (-not (Test-Path $installScript)) { throw "Not found: $installScript" }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Test "file is readable" {
|
|
72
|
+
$null = Get-Content $installScript -Raw -ErrorAction Stop
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Test "no non-ASCII bytes" {
|
|
76
|
+
$bytes = [System.IO.File]::ReadAllBytes($installScript)
|
|
77
|
+
$nonAscii = @($bytes | Where-Object { $_ -gt 127 })
|
|
78
|
+
if ($nonAscii.Count -gt 0) {
|
|
79
|
+
throw "Found $($nonAscii.Count) non-ASCII bytes"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Test "AST parses without errors" {
|
|
84
|
+
$parseErrors = $null
|
|
85
|
+
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
|
|
86
|
+
$installScript, [ref]$null, [ref]$parseErrors
|
|
87
|
+
)
|
|
88
|
+
if ($parseErrors.Count -gt 0) {
|
|
89
|
+
$msg = ($parseErrors | ForEach-Object { "L$($_.Extent.StartLineNumber): $($_.Message)" }) -join "; "
|
|
90
|
+
throw $msg
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Write-Host ""
|
|
95
|
+
|
|
96
|
+
# ── Param block (via AST) ──
|
|
97
|
+
Write-Host "--- Param block ---" -ForegroundColor Cyan
|
|
98
|
+
|
|
99
|
+
$content = Get-Content $installScript -Raw
|
|
100
|
+
$parseErrors = $null
|
|
101
|
+
$ast = [System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$null, [ref]$parseErrors)
|
|
102
|
+
|
|
103
|
+
Test "param block defines all expected parameters" {
|
|
104
|
+
$expected = @('Version', 'InstallDir', 'Source', 'AddToPath',
|
|
105
|
+
'Silent', 'DryRun', 'SkipIfInstalled', 'SkipLocal', 'Locate')
|
|
106
|
+
# Extract param block text via AST extents
|
|
107
|
+
$paramAst = $ast.ParamBlock
|
|
108
|
+
if (-not $paramAst) { throw "Could not find param block AST" }
|
|
109
|
+
$txt = $content.Substring($paramAst.Extent.StartOffset, $paramAst.Extent.EndOffset - $paramAst.Extent.StartOffset)
|
|
110
|
+
foreach ($p in $expected) {
|
|
111
|
+
if ($txt -notmatch ('\$' + $p + '\b')) {
|
|
112
|
+
throw "Missing parameter: $p"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Test "Source has ValidateSet with empty string" {
|
|
118
|
+
if ($content -notmatch 'ValidateSet\("",\s*"github",\s*"oss"\)') {
|
|
119
|
+
throw "ValidateSet must include empty string for iex compatibility"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Write-Host ""
|
|
124
|
+
|
|
125
|
+
# ── Locate mode ──
|
|
126
|
+
Write-Host "--- Locate mode ---" -ForegroundColor Cyan
|
|
127
|
+
|
|
128
|
+
$ec = 0
|
|
129
|
+
$r = RunScript -scriptArgs "-Locate" -exitCode ([ref]$ec)
|
|
130
|
+
|
|
131
|
+
Test "-Locate exits with code 0" {
|
|
132
|
+
if ($ec -ne 0) { throw "Exit code: $ec" }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Test "-Locate shows platform key" {
|
|
136
|
+
if ($r.Output -notmatch 'Platform key') {
|
|
137
|
+
throw "Missing 'Platform key' in: $($r.Output.Substring(0, [Math]::Min(500, $r.Output.Length)))"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Test "-Locate shows binary name" {
|
|
142
|
+
if ($r.Output -notmatch 'Binary name') {
|
|
143
|
+
throw "Missing 'Binary name' in output (len=$($r.Output.Length))"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
Test "-Locate shows download order" {
|
|
148
|
+
if ($r.Output -notmatch 'Download order') {
|
|
149
|
+
throw "Missing 'Download order' in output"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Write-Host ""
|
|
154
|
+
|
|
155
|
+
# ── Download URLs (via -Locate output) ──
|
|
156
|
+
Write-Host "--- Download URLs ---" -ForegroundColor Cyan
|
|
157
|
+
|
|
158
|
+
Test "locate shows correct GitHub latest/download URL" {
|
|
159
|
+
if ($r.Output -notmatch 'github\.com/platonai/Browser4/releases/latest/download/') {
|
|
160
|
+
$lines = ($r.Output -split '\n' | Where-Object { $_ -match 'ownload' }) -join '; '
|
|
161
|
+
throw "GitHub latest URL not found. Download lines: $lines"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Test "locate shows correct OSS download/latest URL" {
|
|
166
|
+
if ($r.Output -notmatch 'oss-cn-beijing.*?releases/download/latest/') {
|
|
167
|
+
$lines = ($r.Output -split '\n' | Where-Object { $_ -match 'ownload' }) -join '; '
|
|
168
|
+
throw "OSS latest URL not found. Download lines: $lines"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Test versioned URLs
|
|
173
|
+
$ec2 = 0
|
|
174
|
+
$r2 = RunScript -scriptArgs "-Version v4.11.0 -Locate" -exitCode ([ref]$ec2)
|
|
175
|
+
|
|
176
|
+
Test "-Version shows tag-based URLs" {
|
|
177
|
+
if ($r2.Output -notmatch 'releases/download/v4\.11\.0/') {
|
|
178
|
+
throw "Versioned URL not in output"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Write-Host ""
|
|
183
|
+
|
|
184
|
+
# ── Parameter acceptance ──
|
|
185
|
+
Write-Host "--- Parameter acceptance ---" -ForegroundColor Cyan
|
|
186
|
+
|
|
187
|
+
Test "-SkipIfInstalled flag accepted" {
|
|
188
|
+
$ec3 = 0; $r3 = RunScript -scriptArgs "-SkipIfInstalled -DryRun" -exitCode ([ref]$ec3)
|
|
189
|
+
if ($ec3 -ne 0) { throw "Exit code: $ec3, output: $($r3.Output)" }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Test "-SkipLocal flag accepted" {
|
|
193
|
+
$ec4 = 0; $r4 = RunScript -scriptArgs "-SkipLocal -DryRun" -exitCode ([ref]$ec4)
|
|
194
|
+
if ($ec4 -ne 0) { throw "Exit code: $ec4, output: $($r4.Output)" }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Test "-Force rejected (replaced by -SkipIfInstalled)" {
|
|
198
|
+
$ec5 = 0; $r5 = RunScript -scriptArgs "-Force -DryRun" -exitCode ([ref]$ec5)
|
|
199
|
+
if ($r5.Error -notmatch 'Force') {
|
|
200
|
+
throw "-Force should be rejected, got: $($r5.Error)"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
Test "-Source oss accepted" {
|
|
205
|
+
$ec6 = 0; $r6 = RunScript -scriptArgs "-Source oss -DryRun" -exitCode ([ref]$ec6)
|
|
206
|
+
if ($ec6 -ne 0) { throw "Exit code: $ec6, output: $($r6.Output)" }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Test "-Source github accepted" {
|
|
210
|
+
$ec7 = 0; $r7 = RunScript -scriptArgs "-Source github -DryRun" -exitCode ([ref]$ec7)
|
|
211
|
+
if ($ec7 -ne 0) { throw "Exit code: $ec7, output: $($r7.Output)" }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Test "-Source invalid rejected" {
|
|
215
|
+
$ec8 = 0; $r8 = RunScript -scriptArgs "-Source invalid -DryRun" -exitCode ([ref]$ec8)
|
|
216
|
+
if ($r8.Error -notmatch 'Source|invalid|parameter') {
|
|
217
|
+
throw "-Source invalid should be rejected, got: $($r8.Error)"
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
Test "-Silent flag accepted" {
|
|
222
|
+
$ec9 = 0; $r9 = RunScript -scriptArgs "-Silent -DryRun" -exitCode ([ref]$ec9)
|
|
223
|
+
if ($ec9 -ne 0) { throw "Exit code: $ec9, output: $($r9.Output)" }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Test "-Version flag accepted" {
|
|
227
|
+
$ec10 = 0; $r10 = RunScript -scriptArgs "-Version v4.11.0 -DryRun" -exitCode ([ref]$ec10)
|
|
228
|
+
if ($ec10 -ne 0) { throw "Exit code: $ec10, output: $($r10.Output)" }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Write-Host ""
|
|
232
|
+
|
|
233
|
+
# ── Functions via dot-source ──
|
|
234
|
+
Write-Host "--- Functions ---" -ForegroundColor Cyan
|
|
235
|
+
|
|
236
|
+
# Strip trailing Main call and dot-source for function-level tests
|
|
237
|
+
$scriptContent = Get-Content $installScript -Raw
|
|
238
|
+
$scriptContent = $scriptContent -replace '\r?\nMain\s*$', ''
|
|
239
|
+
$scriptContent = $scriptContent -replace '\$ErrorActionPreference\s*=\s*"Stop"', ''
|
|
240
|
+
$sb = [ScriptBlock]::Create($scriptContent)
|
|
241
|
+
|
|
242
|
+
& {
|
|
243
|
+
# Suppress output
|
|
244
|
+
$Silent = $true
|
|
245
|
+
$DryRun = $false
|
|
246
|
+
$SkipLocal = $false
|
|
247
|
+
$Locate = $false
|
|
248
|
+
$Source = ""
|
|
249
|
+
$Version = ""
|
|
250
|
+
$InstallDir = ""
|
|
251
|
+
$AddToPath = $true
|
|
252
|
+
|
|
253
|
+
. $sb
|
|
254
|
+
|
|
255
|
+
Test "Get-PlatformKey returns valid format" {
|
|
256
|
+
$key = Get-PlatformKey
|
|
257
|
+
if ($key -notmatch '^(win32|linux|darwin)-(x64|arm64)$' -and
|
|
258
|
+
$key -notmatch '^linux-musl-(x64|arm64)$') {
|
|
259
|
+
throw "Unexpected platform key: $key"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
Test "Get-BinaryName includes .exe on win32" {
|
|
264
|
+
$name = Get-BinaryName -PlatformKey "win32-x64"
|
|
265
|
+
if ($name -ne "browser4-cli-win32-x64.exe") { throw "Got: $name" }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Test "Get-BinaryName excludes .exe on linux" {
|
|
269
|
+
$name = Get-BinaryName -PlatformKey "linux-x64"
|
|
270
|
+
if ($name -ne "browser4-cli-linux-x64") { throw "Got: $name" }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
Test "Get-BinaryName excludes .exe on darwin" {
|
|
274
|
+
$name = Get-BinaryName -PlatformKey "darwin-arm64"
|
|
275
|
+
if ($name -ne "browser4-cli-darwin-arm64") { throw "Got: $name" }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
Test "Get-DefaultInstallDir returns non-empty" {
|
|
279
|
+
$dir = Get-DefaultInstallDir
|
|
280
|
+
if ([string]::IsNullOrEmpty($dir)) { throw "Empty install dir" }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
Test "Test-ChinaLocale returns [bool]" {
|
|
284
|
+
$result = Test-ChinaLocale
|
|
285
|
+
if ($result -isnot [bool]) { throw "Expected [bool], got $($result.GetType())" }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Test "Find-LocalBinary returns null for non-existent" {
|
|
289
|
+
$result = Find-LocalBinary -BinaryName "nonexistent-file-xyz.exe"
|
|
290
|
+
if ($result -ne $null) { throw "Expected null, got: $result" }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Test "Test-LocalBinary returns false for empty string" {
|
|
294
|
+
if (Test-LocalBinary -Path "") { throw "Should be false" }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
Test "Test-LocalBinary returns false for null" {
|
|
298
|
+
if (Test-LocalBinary -Path $null) { throw "Should be false" }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
Write-Host ""
|
|
303
|
+
|
|
304
|
+
# ── New-Symlinks (b4 link logic) ──
|
|
305
|
+
Write-Host "--- New-Symlinks ---" -ForegroundColor Cyan
|
|
306
|
+
|
|
307
|
+
& {
|
|
308
|
+
$Silent = $true
|
|
309
|
+
$DryRun = $false
|
|
310
|
+
$SkipLocal = $false
|
|
311
|
+
$Locate = $false
|
|
312
|
+
$Source = ""
|
|
313
|
+
$Version = ""
|
|
314
|
+
$InstallDir = ""
|
|
315
|
+
$AddToPath = $true
|
|
316
|
+
|
|
317
|
+
. $sb
|
|
318
|
+
|
|
319
|
+
# NOTE: $env:PATH (all-caps) is the real env var on Linux; $env:Path
|
|
320
|
+
# (mixed-case) is a different, unrelated variable.
|
|
321
|
+
$testPlatformKey = if ($script:OSWin) { "win32-x64" } else { "linux-x64" }
|
|
322
|
+
$testExt = if ($testPlatformKey.StartsWith("win32")) { ".exe" } else { "" }
|
|
323
|
+
$testBinaryName = "browser4-cli-$testPlatformKey$testExt"
|
|
324
|
+
$testShortName = "b4$testExt"
|
|
325
|
+
|
|
326
|
+
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "b4-install-test-$(Get-Random)"
|
|
327
|
+
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
# Create the dummy platform binary that New-PlatformLink needs
|
|
331
|
+
$dummyBinary = Join-Path $tempDir $testBinaryName
|
|
332
|
+
"dummy" | Out-File $dummyBinary
|
|
333
|
+
|
|
334
|
+
$testShortPath = Join-Path $tempDir $testShortName
|
|
335
|
+
$testCmdPath = Join-Path $tempDir "b4.cmd"
|
|
336
|
+
|
|
337
|
+
function Clear-B4Links {
|
|
338
|
+
if (Test-Path $testShortPath) { Remove-Item $testShortPath -Force -ErrorAction SilentlyContinue }
|
|
339
|
+
if (Test-Path $testCmdPath) { Remove-Item $testCmdPath -Force -ErrorAction SilentlyContinue }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Helper: temporarily override $env:PATH so the New-Symlinks PATH
|
|
343
|
+
# scan only sees directories the test scenario controls. On Linux
|
|
344
|
+
# the kernel's b4 patch tool lives in /usr/bin (and /bin on some
|
|
345
|
+
# distros); without this override Get-Command/Get-Command b4 would
|
|
346
|
+
# always find the system b4 and skip creating our link.
|
|
347
|
+
function Invoke-WithPath([string]$Path, [ScriptBlock]$Action) {
|
|
348
|
+
$prevPath = $env:PATH
|
|
349
|
+
try {
|
|
350
|
+
$env:PATH = $Path
|
|
351
|
+
return & $Action
|
|
352
|
+
} finally {
|
|
353
|
+
$env:PATH = $prevPath
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# ── Scenario 1: b4 does not exist → should create ──
|
|
358
|
+
Clear-B4Links
|
|
359
|
+
|
|
360
|
+
Invoke-WithPath -Path $tempDir -Action {
|
|
361
|
+
New-Symlinks -BinaryName $testBinaryName -InstallDir $tempDir -PlatformKey $testPlatformKey
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
Test "New-Symlinks creates b4 when it does not exist" {
|
|
365
|
+
if (-not ((Test-Path $testShortPath) -or (Test-Path $testCmdPath))) {
|
|
366
|
+
throw "b4 link was not created (neither symlink nor .cmd wrapper found in $tempDir)"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
# ── Scenario 2: b4 exists in install dir → should update ──
|
|
371
|
+
Clear-B4Links
|
|
372
|
+
|
|
373
|
+
Invoke-WithPath -Path $tempDir -Action {
|
|
374
|
+
New-Symlinks -BinaryName $testBinaryName -InstallDir $tempDir -PlatformKey $testPlatformKey
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
$existingPath = if (Test-Path $testShortPath) { $testShortPath }
|
|
378
|
+
elseif (Test-Path $testCmdPath) { $testCmdPath }
|
|
379
|
+
else { $null }
|
|
380
|
+
|
|
381
|
+
if ($existingPath) {
|
|
382
|
+
$beforeTime = (Get-Item $existingPath).LastWriteTime
|
|
383
|
+
Start-Sleep -Milliseconds 200
|
|
384
|
+
|
|
385
|
+
Invoke-WithPath -Path $tempDir -Action {
|
|
386
|
+
New-Symlinks -BinaryName $testBinaryName -InstallDir $tempDir -PlatformKey $testPlatformKey
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
$afterPath = if (Test-Path $testShortPath) { $testShortPath }
|
|
390
|
+
elseif (Test-Path $testCmdPath) { $testCmdPath }
|
|
391
|
+
else { $null }
|
|
392
|
+
|
|
393
|
+
Test "New-Symlinks updates b4 when it already exists in install dir" {
|
|
394
|
+
if (-not $afterPath) {
|
|
395
|
+
throw "b4 link disappeared after update"
|
|
396
|
+
}
|
|
397
|
+
if ((Get-Item $afterPath).LastWriteTime -le $beforeTime) {
|
|
398
|
+
throw "b4 link was not updated (timestamp unchanged)"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
Write-Host " SKIP New-Symlinks updates b4 (precondition: initial link creation failed)" -ForegroundColor Yellow
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ── Scenario 3: foreign b4 on PATH → should NOT create b4 in install dir ──
|
|
406
|
+
Clear-B4Links
|
|
407
|
+
|
|
408
|
+
$foreignDir = Join-Path ([System.IO.Path]::GetTempPath()) "b4-foreign-test-$(Get-Random)"
|
|
409
|
+
New-Item -ItemType Directory -Path $foreignDir -Force | Out-Null
|
|
410
|
+
try {
|
|
411
|
+
# Create fake b4 that is NOT browser4-cli
|
|
412
|
+
$foreignB4 = Join-Path $foreignDir $testShortName
|
|
413
|
+
if ($script:OSWin) {
|
|
414
|
+
# Use .cmd so Get-Command finds it via PATHEXT
|
|
415
|
+
$foreignCmd = Join-Path $foreignDir "b4.cmd"
|
|
416
|
+
"@echo off`r`necho NotBrowser4" | Out-File $foreignCmd -Encoding ASCII
|
|
417
|
+
} else {
|
|
418
|
+
"#!/bin/sh`necho NotBrowser4" | Out-File $foreignB4 -Encoding ASCII
|
|
419
|
+
& { chmod +x $foreignB4 2>$null } | Out-Null
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
Invoke-WithPath -Path "$foreignDir$([System.IO.Path]::PathSeparator)$tempDir" -Action {
|
|
423
|
+
New-Symlinks -BinaryName $testBinaryName -InstallDir $tempDir -PlatformKey $testPlatformKey
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
Test "New-Symlinks skips b4 when foreign b4 is on PATH" {
|
|
427
|
+
if ((Test-Path $testShortPath) -or (Test-Path $testCmdPath)) {
|
|
428
|
+
throw "b4 link was created even though a non-browser4-cli b4 is on PATH"
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} finally {
|
|
432
|
+
Remove-Item $foreignDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# ── Scenario 4: b4 on PATH is browser4-cli → should create b4 link ──
|
|
436
|
+
Clear-B4Links
|
|
437
|
+
|
|
438
|
+
$oursDir = Join-Path ([System.IO.Path]::GetTempPath()) "b4-ours-test-$(Get-Random)"
|
|
439
|
+
New-Item -ItemType Directory -Path $oursDir -Force | Out-Null
|
|
440
|
+
try {
|
|
441
|
+
$oursB4 = Join-Path $oursDir $testShortName
|
|
442
|
+
if ($script:OSWin) {
|
|
443
|
+
$oursCmd = Join-Path $oursDir "b4.cmd"
|
|
444
|
+
"@echo off`r`necho browser4-cli v4.12.0" | Out-File $oursCmd -Encoding ASCII
|
|
445
|
+
} else {
|
|
446
|
+
"#!/bin/sh`necho browser4-cli v4.12.0" | Out-File $oursB4 -Encoding ASCII
|
|
447
|
+
& { chmod +x $oursB4 2>$null } | Out-Null
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
Invoke-WithPath -Path "$oursDir$([System.IO.Path]::PathSeparator)$tempDir" -Action {
|
|
451
|
+
New-Symlinks -BinaryName $testBinaryName -InstallDir $tempDir -PlatformKey $testPlatformKey
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
Test "New-Symlinks creates b4 when b4 on PATH is browser4-cli" {
|
|
455
|
+
if (-not ((Test-Path $testShortPath) -or (Test-Path $testCmdPath))) {
|
|
456
|
+
throw "b4 link was not created even though PATH b4 is browser4-cli"
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
} finally {
|
|
460
|
+
Remove-Item $oursDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
461
|
+
}
|
|
462
|
+
} finally {
|
|
463
|
+
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
Write-Host ""
|
|
468
|
+
|
|
469
|
+
# ── Summary ──
|
|
470
|
+
Write-Host "============================================" -ForegroundColor Cyan
|
|
471
|
+
$total = $pass + $fail
|
|
472
|
+
Write-Host " Results: $pass / $total passed" -ForegroundColor $(if ($fail -eq 0) { "Green" } else { "Red" })
|
|
473
|
+
Write-Host "============================================" -ForegroundColor Cyan
|
|
474
|
+
|
|
475
|
+
if ($fail -gt 0) { exit 1 }
|
|
476
|
+
exit 0
|