tests/cases/30-ps12exe-core.ps1

# ps12exe 端到端:编译、宿主行为、打包、目标框架、CLI 输出。
$deps = $script:CoreCompileDeps

Add-Test @{
    Name  = 'ps12exe.compile.error-clean'
    Group = 'ps12exe'
    Deps  = $deps
    Run   = {
        param($ctx)
        $src = Join-Path $ctx.WorkDir 'error-clean.ps1'
        $out = Join-Path $ctx.WorkDir 'error-clean.exe'
        [System.IO.File]::WriteAllText($src, "Write-Error 'a'; Write-Output 'error-clean'", [System.Text.UTF8Encoding]::new($true))
        $error.Clear()
        ps12exe -inputFile $src -outputFile $out -NoUpdateCheck | Out-Null
        Assert-FileExists $out 'error-clean 编译失败'
        Assert-True ($error.Count -eq 0) "成功编译却污染了 `$error:$($error | Out-String)"
    }
}

Add-Test @{
    Name  = 'ps12exe.self.compile'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{ Name = 'self'; InputFile = (Join-Path (Get-TestRepoRoot) 'ps12exe.ps1'); Output = 'ps12exe.exe' }
    Run   = {
        param($ctx)
        $self = Copy-BuildAs -BuildPath $ctx.Builds['self'] -WorkDir $ctx.WorkDir -Name 'ps12exe-self.exe'
        $src = Join-Path $ctx.WorkDir 'inner.ps1'
        [System.IO.File]::WriteAllText($src, "'self-built-ok'", [System.Text.UTF8Encoding]::new($true))
        $out = Join-Path $ctx.WorkDir 'self-built-inner.exe'
        & $self $src $out -NoUpdateCheck | Out-Null
        Assert-FileExists $out '用自编译的 ps12exe.exe 编译失败'
        $r = Invoke-ExeCaptureMergedOutput -ExePath $out
        Assert-Equal 0 $r.ExitCode 'self-built inner 退出码'
        Assert-Match $r.Output 'self-built-ok' 'self-built inner 输出'
    }
}

Add-Test @{
    Name  = 'ps12exe.host.hello-and-paths'
    Group = 'ps12exe'
    Deps  = $deps
    Builds = @(
        @{ Name = 'hello'; InputText = "Get-Date | Out-Null; Write-Output 'Hello world!'"; Output = 'hello.exe' }
        @{ Name = 'paths'; InputText = "`$PSCommandPath`n`$PSScriptRoot"; Output = 'paths.exe' }
    )
    Run   = {
        param($ctx)
        $hello = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['hello']
        Assert-Match $hello.Output 'Hello world!' 'hello 输出'

        $pathsExe = Copy-BuildAs -BuildPath $ctx.Builds['paths'] -WorkDir $ctx.WorkDir -Name 'paths.exe'
        $result = @(& $pathsExe)
        $expected = @($pathsExe, $ctx.WorkDir)
        Assert-Equal $expected.Count $result.Count 'paths 行数'
        for ($i = 0; $i -lt $expected.Count; $i++) {
            Assert-Equal ([System.IO.Path]::GetFullPath($expected[$i])) ([System.IO.Path]::GetFullPath($result[$i])) "paths 第 $i 行"
        }
    }
}

Add-Test @{
    Name  = 'ps12exe.pack.size'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{ Name = 'packed'; InputText = "Get-Date | Out-Null; Write-Output 'packed-embed'"; Output = 'packed.exe' }
    Run   = {
        param($ctx)
        $packedSize = (Get-Item -LiteralPath $ctx.Builds['packed']).Length
        Assert-True ($packedSize -lt 25088) "非常量 exe 未按默认压缩负载打包(size=$packedSize)"
        $r = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['packed']
        Assert-Match $r.Output 'packed-embed' 'packed 运行输出'
    }
}

Add-Test @{
    Name  = 'ps12exe.host.native-stdout'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{ Name = 'echo'; InputText = "cmd /c echo native-hello"; Output = 'native-echo.exe' }
    Run   = {
        param($ctx)
        $out = & $ctx.Builds['echo']
        Assert-Match "$out" 'native-hello' "宿主 stdout 被管道接走时 native 输出丢失:$out"
    }
}

Add-Test @{
    Name  = 'ps12exe.host.error-order'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{ Name = 'erro'; InputText = "Write-Error 'err-a'; Write-Output 'out-b'"; Output = 'err-order.exe' }
    Run   = {
        param($ctx)
        $r = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['erro']
        Assert-True (-not [string]::IsNullOrWhiteSpace($r.Output)) 'err-order 无捕获输出'
        Assert-Match $r.Output '(?s)err-a.*out-b' 'Write-Error 应出现在成功输出之前'
    }
}

Add-Test @{
    Name  = 'ps12exe.host.tty'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{
        Name      = 'probe'
        Output    = 'tty-probe.exe'
        InputText = @'
@{
    hostName = $Host.Name
    inRedirected = [Console]::IsInputRedirected
    outRedirected = [Console]::IsOutputRedirected
} | ConvertTo-Json -Compress | Set-Content -LiteralPath $env:PS12EXE_TTY_HOST -Encoding UTF8
Remove-Item -LiteralPath $env:PS12EXE_TTY_CHILD -ErrorAction Ignore
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
& $powershell -NoProfile -NonInteractive -File $env:PS12EXE_TTY_CHILD_PS1
if ($LASTEXITCODE) { exit $LASTEXITCODE }
'@

    }
    Run   = {
        param($ctx)
        $child = Join-Path $ctx.WorkDir 'tty-child.ps1'
        $childFlag = Join-Path $ctx.WorkDir 'child-redirected.txt'
        $hostJson = Join-Path $ctx.WorkDir 'host.json'
        [System.IO.File]::WriteAllText($child, '[System.IO.File]::WriteAllText($env:PS12EXE_TTY_CHILD, ([Console]::IsOutputRedirected).ToString())', [System.Text.UTF8Encoding]::new($true))
        $probe = Copy-BuildAs -BuildPath $ctx.Builds['probe'] -WorkDir $ctx.WorkDir -Name 'tty-probe.exe'
        $env:PS12EXE_TTY_CHILD = $childFlag
        $env:PS12EXE_TTY_HOST = $hostJson
        $env:PS12EXE_TTY_CHILD_PS1 = $child
        $exit = Invoke-ExeWithPrivateConsole -ExePath $probe -WorkingDirectory $ctx.WorkDir
        Assert-Equal 0 $exit "tty probe 退出码"
        $hostInfo = Get-Content -LiteralPath $hostJson -Raw | ConvertFrom-Json
        Assert-False ([bool]$hostInfo.outRedirected) 'tty probe 宿主 stdout 应未重定向(独立 console)'
        $childRedirected = (Get-Content -LiteralPath $childFlag -Raw).Trim()
        Assert-Equal 'False' $childRedirected '独立 console 下 native 子进程 stdout 不应被重定向(issue 59)'
    }
}

Add-Test @{
    Name  = 'ps12exe.host.stdin'
    Group = 'ps12exe'
    Deps  = $deps
    Builds = @(
        @{ Name = 'noinput'; InputText = "`$null = [System.IO.File]::Exists('')`n'no-input-ok'"; Output = 'no-input.exe' }
        @{ Name = 'withinput'; InputText = "'input=[' + ((@(`$input) -join ',')) + ']'"; Output = 'with-input.exe' }
    )
    Run   = {
        param($ctx)
        $noInputExe = Copy-BuildAs -BuildPath $ctx.Builds['noinput'] -WorkDir $ctx.WorkDir -Name 'no-input.exe'
        $psi = [System.Diagnostics.ProcessStartInfo]::new($noInputExe)
        $psi.RedirectStandardInput = $true
        $psi.RedirectStandardOutput = $true
        $psi.UseShellExecute = $false
        $psi.WorkingDirectory = $ctx.WorkDir
        $p = [System.Diagnostics.Process]::Start($psi)
        try {
            Assert-True ($p.WaitForExit(10000)) '顶层不用 $input 时,重定向 stdin 不应阻塞启动(issue 62)'
            Assert-Match ($p.StandardOutput.ReadToEnd()) 'no-input-ok' 'no-input 输出'
        }
        finally {
            if (-not $p.HasExited) { Stop-ProcessTree -ProcessId $p.Id }
            $p.Dispose()
        }

        $withInputExe = Copy-BuildAs -BuildPath $ctx.Builds['withinput'] -WorkDir $ctx.WorkDir -Name 'with-input.exe'
        $withInputOut = 'a', 'b' | & $withInputExe
        Assert-Match "$withInputOut" 'input=\[a,b\]' "编译后的 exe 未把重定向 stdin 作为管道输入(`$input):$withInputOut"
    }
}

Add-Test @{
    Name  = 'ps12exe.host.nested'
    Group = 'ps12exe'
    Deps  = $deps
    Timeout = 900
    Builds = @(
        @{
            Name = 'okhost'; Output = 'nested-ok-host.exe'
            InputText = @"
Import-Module '$(Get-TestRepoRoot)' -Force
`$Error.Clear()
ps12exe -inputFile (Join-Path `$PSScriptRoot 'inner.ps1') -outputFile (Join-Path `$PSScriptRoot 'inner-ok.exe') -NoUpdateCheck
Write-Host "NESTED_LASTEXITCODE=`$LastExitCode"
Write-Host "NESTED_ERROR_COUNT=`$(`$Error.Count)"
"@

        }
        @{
            Name = 'failhost'; Output = 'nested-fail-host.exe'
            InputText = @"
Import-Module '$(Get-TestRepoRoot)' -Force
`$Error.Clear()
ps12exe -inputFile (Join-Path `$PSScriptRoot 'inner.ps1') -outputFile `$PSCommandPath -NoUpdateCheck
Write-Host "NESTED_LASTEXITCODE=`$LastExitCode"
Write-Host "NESTED_ERROR_COUNT=`$(`$Error.Count)"
`$Error | ForEach-Object { Write-Host "NESTED_ERROR_TEXT: `$_" }
"@

        }
    )
    Run   = {
        param($ctx)
        $inner = Join-Path $ctx.WorkDir 'inner.ps1'
        [System.IO.File]::WriteAllText($inner, "Write-Output 'inner-ok'", [System.Text.UTF8Encoding]::new($true))

        $okHost = Copy-BuildAs -BuildPath $ctx.Builds['okhost'] -WorkDir $ctx.WorkDir -Name 'nested-ok-host.exe'
        $ok = Invoke-ExeCaptureMergedOutput -ExePath $okHost -TimeoutSeconds 600
        Assert-FileExists (Join-Path $ctx.WorkDir 'inner-ok.exe') "嵌套 ps12exe(控制组)未产出 inner exe:$($ok.Output)"
        Assert-Match $ok.Output 'NESTED_LASTEXITCODE=0' "嵌套 ps12exe(控制组)未报告成功:$($ok.Output)"

        $failHost = Copy-BuildAs -BuildPath $ctx.Builds['failhost'] -WorkDir $ctx.WorkDir -Name 'nested-fail-host.exe'
        $fail = Invoke-ExeCaptureMergedOutput -ExePath $failHost -TimeoutSeconds 600
        Assert-Match $fail.Output 'NESTED_LASTEXITCODE=[1-3]' "嵌套自覆盖应以文档化 LastExitCode 失败:$($fail.Output)"
        Assert-Match $fail.Output '(?i)CS0016|being used by another process' "嵌套 ps12exe 吞掉了真实编译失败原因(issue 60):$($fail.Output)"
    }
}

Add-Test @{
    Name  = 'ps12exe.target.framework20'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{ Name = 'fw20'; InputText = "Write-Error 'a'; Write-Output '123'"; Params = @{ Build = @{ Target = 'Framework2.0' } }; Output = 'fw20.exe' }
    Run   = {
        param($ctx)
        Assert-FileExists $ctx.Builds['fw20'] 'Framework2.0 编译未产出'
        $r = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['fw20']
        Assert-Match $r.Output '(?s)a.*123' "Framework2.0 输出不符:$($r.Output)"
    }
}

Add-Test @{
    Name  = 'ps12exe.target.core'
    Group = 'ps12exe'
    Deps  = $deps
    Build = @{ Name = 'core'; InputText = "Get-Date | Out-Null; Write-Output 'core-run-ok'"; Params = @{ Build = @{ Target = 'Core' } }; Output = 'core-run.exe' }
    Run   = {
        param($ctx)
        if (-not (Get-Command dotnet -ErrorAction Ignore)) { throw 'Core 目标需要 .NET SDK(dotnet)' }
        Assert-FileExists $ctx.Builds['core'] 'Core 编译未产出'
        $r = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['core']
        Assert-Match $r.Output 'core-run-ok' "Core 输出不符:$($r.Output)"
    }
}

Add-Test @{
    Name  = 'ps12exe.cli.redirect'
    Group = 'ps12exe'
    Deps  = $deps
    Run   = {
        param($ctx)
        $exe = Join-Path $ctx.WorkDir 'redirect_test.exe'
        $redirectPs1 = Join-Path $ctx.WorkDir 'redirect_test.ps1'
        [System.IO.File]::WriteAllText($redirectPs1, "Write-Output 'redirect-test'", [System.Text.UTF8Encoding]::new($true))
        $expected = [System.IO.Path]::GetFullPath($exe)
        $captured = & (Join-Path $ctx.RepoRoot 'ps12exe.ps1') -inputFile $redirectPs1 -outputFile $exe 2>$null
        Assert-True (-not [string]::IsNullOrWhiteSpace($captured)) "重定向时 stdout 为空(期望路径 $expected)"
        Assert-Equal $expected ([System.IO.Path]::GetFullPath("$captured")) '重定向时 stdout 应只输出 exe 路径'
    }
}