# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. Describe "Verify Markdown Links" { BeforeAll { if(!(Get-Command -Name 'markdown-link-check' -ErrorAction SilentlyContinue)) { Write-Verbose "installing markdown-link-check ..." -Verbose start-nativeExecution { sudo yarn global add markdown-link-check@3.7.2 } } if(!(Get-Module -Name 'ThreadJob' -ListAvailable -ErrorAction SilentlyContinue)) { Install-Module -Name ThreadJob -Scope CurrentUser } # Cleanup jobs for reliability Get-Job | Remove-Job -Force } AfterAll { # Cleanup jobs to leave the process the same Get-Job | Remove-Job -Force } $groups = Get-ChildItem -Path "$PSScriptRoot\..\..\..\*.md" -Recurse | Where-Object {$_.DirectoryName -notlike '*node_modules*'} | Group-Object -Property directory $jobs = @{} # start all link verification in parallel Foreach($group in $groups) { Write-Verbose -Verbose "starting jobs for $($group.Name) ..." $job = Start-ThreadJob { param([object] $group) foreach($file in $group.Group) { $results = markdown-link-check $file 2>&1 Write-Output ([PSCustomObject]@{ file = $file results = $results }) } } -ArgumentList @($group) $jobs.add($group.name,$job) } Write-Verbose -Verbose "Getting results ..." # Get the results and verify foreach($key in $jobs.keys) { $job = $jobs.$key $results = Receive-Job -Job $job -Wait Remove-Job -Job $Job foreach($jobResult in $results) { $file = $jobResult.file $result = $jobResult.results Context "Verify links in $file" { # failures look like `[✖] https://someurl` (perhaps without the https://) # passes look like `[✓] https://someurl` (perhaps without the https://) $failures = $result -like '*[✖]*' | ForEach-Object { $_.Substring(4).Trim() } $passes = $result -like '*[✓]*' | ForEach-Object { @{url=$_.Substring(4).Trim() } } $trueFailures = @() $verifyFailures = @() foreach ($failure in $failures) { if($failure -like 'https://www.amazon.com*') { # In testing amazon links often failed when they are valid # Verify manually $verifyFailures += @{url = $failure} } else { $trueFailures += @{url = $failure} } } # must have some code in the test for it to pass function noop { } if($passes) { It " should work" -TestCases $passes { noop } } if($trueFailures) { It " should work" -TestCases $trueFailures { param($url) # there could be multiple reasons why a failure is ok # check against the allowed failures $allowedFailures = [System.Net.HttpStatusCode[]]( 503, # Service Unavailable 504 # Gateway Timeout ) $prefix = $url.Substring(0,7) # Logging for diagnosability. Azure DevOps sometimes redacts the full url. Write-Verbose "prefix: '$prefix'" if($url -match '^http(s)?:') { # If invoke-WebRequest can handle the URL, re-verify, with 6 retries try { $null = Invoke-WebRequest -Uri $url -RetryIntervalSec 10 -MaximumRetryCount 6 } catch [Microsoft.PowerShell.Commands.HttpResponseException] { if ( $allowedFailures -notcontains $_.Exception.Response.StatusCode ) { throw "Failed to complete request to `"$url`". $($_.Exception.Message)" } } } else { throw "Tool reported URL as unreachable." } } } if($verifyFailures) { It " should work" -TestCases $verifyFailures -Pending { } } if(!$passes -and !$trueFailures -and !$verifyFailures) { It "has no links" { noop } } } } } }