Public/Test-RePSExercise.ps1
|
function Test-RePSExercise { <# .SYNOPSIS Checks your workspace solution against the exercise's Pester tests. .DESCRIPTION Runs the tests in a separate pwsh process (so your code cannot affect this session) and records the result in your progress file. Passing after revealing the solution is recorded as 'Assisted'. .PARAMETER Id Exercise to test. Defaults to the current exercise. .PARAMETER Path Test a specific file instead of the workspace file. .PARAMETER Quiet Only show the summary line, not each test. .PARAMETER TimeoutSeconds Stop the test run (and record a failure) if it takes longer than this, for example because of an infinite loop. Default 60. .PARAMETER PassThru Output a result object. .EXAMPLE rept Tests the current exercise. #> [CmdletBinding()] param( [Parameter(Position = 0)] [string]$Id, [string]$Path, [switch]$Quiet, [ValidateRange(1, 3600)] [int]$TimeoutSeconds = 60, [switch]$PassThru ) $exercise = Resolve-RePSExercise -Id $Id Assert-RePSPlatform -Exercise $exercise if (-not $Path) { $Path = Get-RePSWorkspacePath -Id $exercise.Id } if (-not (Test-Path -LiteralPath $Path)) { throw "Solution file '$Path' not found. Run Start-RePSExercise -Id $($exercise.Id) to create it." } $Path = (Resolve-Path -LiteralPath $Path).ProviderPath $resultFile = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath "reps-$([guid]::NewGuid()).json" $pwsh = (Get-Process -Id $PID).Path $runnerArgs = @( '-NoProfile', '-NonInteractive', '-File', $script:RunnerPath '-TestPath', $exercise.TestPath '-SolutionPath', $Path '-ResultPath', $resultFile '-Verbosity', $(if ($Quiet) { 'None' } else { 'Detailed' }) ) if ($Host.UI.SupportsVirtualTerminal -and -not [Console]::IsOutputRedirected) { $runnerArgs += '-Ansi' } $timedOut = $false try { # Inherit the console so Pester output streams live; ArgumentList quotes each argument. $startInfo = [System.Diagnostics.ProcessStartInfo]::new($pwsh) $startInfo.UseShellExecute = $false foreach ($argument in $runnerArgs) { $startInfo.ArgumentList.Add([string]$argument) } $process = [System.Diagnostics.Process]::Start($startInfo) try { if (-not $process.WaitForExit($TimeoutSeconds * 1000)) { $timedOut = $true $process.Kill($true) $process.WaitForExit() } } finally { $process.Dispose() } $summary = if ($timedOut) { [pscustomobject]@{ Result = 'TimedOut'; TotalCount = 0; PassedCount = 0; FailedCount = 1; Failed = @("Timed out after $TimeoutSeconds seconds") } } elseif (Test-Path -LiteralPath $resultFile) { Get-Content -LiteralPath $resultFile -Raw | ConvertFrom-Json } else { throw 'Test runner did not produce a result.' } } finally { Remove-Item -LiteralPath $resultFile -Force -ErrorAction SilentlyContinue } $passed = $summary.Result -eq 'Passed' -and $summary.FailedCount -eq 0 -and $summary.PassedCount -gt 0 # Reload after the (possibly long) run so changes from other sessions are not overwritten. $progress = Read-RePSProgress $entry = Get-RePSProgressEntry -Progress $progress -Id $exercise.Id $entry.Attempts = [int]$entry.Attempts + 1 $entry.LastAttempt = (Get-Date).ToString('o') if ($passed) { $entry.Status = if ($entry.SolutionShown) { 'Assisted' } else { 'Passed' } if (-not $entry.PassedAt) { $entry.PassedAt = $entry.LastAttempt } } elseif ($entry.Status -ne 'Passed') { $entry.Status = 'Failed' } Save-RePSProgress -Progress $progress Write-Host '' if ($passed) { $note = if ($entry.Status -eq 'Assisted') { ' (assisted)' } else { '' } Write-Host " PASSED$note - $($summary.PassedCount)/$($summary.TotalCount) tests. Next rep: reps" -ForegroundColor Green } elseif ($timedOut) { Write-Host " TIMED OUT after $TimeoutSeconds s - infinite loop? Raise the limit with -TimeoutSeconds." -ForegroundColor Red } else { Write-Host " NOT YET - $($summary.FailedCount) failing, $($summary.PassedCount) passing. Stuck? reph" -ForegroundColor Red } Write-Host '' if ($PassThru) { [pscustomobject]@{ PSTypeName = 'RePS.TestResult' Id = $exercise.Id Passed = $passed Status = $entry.Status PassedCount = [int]$summary.PassedCount FailedCount = [int]$summary.FailedCount TotalCount = [int]$summary.TotalCount Failed = @($summary.Failed) Path = $Path } } } |