Modules/businessdev.ALbuild.Apps/Public/Invoke-BcContainerTest.ps1
|
function Invoke-BcContainerTest { <# .SYNOPSIS Runs AL tests in a Business Central container and collects/parses the results. .DESCRIPTION ALbuild's built-in AL test runner. It drives the AL Test Tool page inside the container (no BcContainerHelper) and produces a JUnit/XUnit result file that is copied to the host, parsed into a summary, and optionally used to fail the build. Test apps are taken from -ProjectFolder (every AL test app found is run) or from an explicit -ExtensionId list. For folder-based discovery the optional ALbuild 'pipeline.config' beside each app.json is honoured: its 'alTestRunnerId' selects the test-runner codeunit. The runner codeunit otherwise defaults to 130450 (test isolation) or, with -DisableIsolation, 130451 (isolation disabled); an explicit -TestRunnerCodeunitId overrides everything. For advanced scenarios a custom in-container invocation can still be supplied via -TestExecution (a script block that writes the result file to $ResultPathInContainer); when given, ALbuild only collects and parses the result and does not use the built-in runner. .PARAMETER Name Container name. .PARAMETER ProjectFolder Folder searched recursively for AL test apps to run. Mutually exclusive with -ExtensionId. .PARAMETER ExtensionId One or more app ids to run tests for, instead of discovering them from a project folder. .PARAMETER Credential Credentials of a SUPER user (NavUserPassword). If omitted, the containerUsername / containerPassword environment variables are used when present. .PARAMETER TestSuite Test suite name. Default 'DEFAULT'. .PARAMETER TestRunnerCodeunitId Explicit test-runner codeunit id, overriding pipeline.config and the isolation default. .PARAMETER DisableIsolation Use the isolation-disabled test runner (130451) as the default instead of 130450. .PARAMETER Tenant Tenant to use. Default 'default'. .PARAMETER CompanyName Company to run tests in. Default: the server's default company. .PARAMETER Culture Culture for the test run. Default 'en-US' (Microsoft tests target en-US). .PARAMETER Auth Client services credential type: NavUserPassword (default), Windows or AAD. .PARAMETER AzureDevOps Emit Azure DevOps warning log issues for failing tests. .PARAMETER ResultPath Host path to write the JUnit result file to. Default: ./TestResults.xml. .PARAMETER ResultPathInContainer Container path the run writes results to. Default: C:\bcptest\TestResults.xml. .PARAMETER TestExecution Optional custom in-container script block (advanced). When supplied, the built-in runner is not used; the block must write the result file to $ResultPathInContainer. .PARAMETER FailOnTestFailure Throw if any test failed. .PARAMETER DockerExecutable The Docker executable to use (default 'docker'). .EXAMPLE Invoke-BcContainerTest -Name bld -ProjectFolder $env:BUILD_REPOSITORY_LOCALPATH -FailOnTestFailure .EXAMPLE Invoke-BcContainerTest -Name bld -ExtensionId '5e8c2f...' -Credential $cred -AzureDevOps .OUTPUTS PSCustomObject with Passed, Failed, Skipped, Total, Failures, Success, ResultPath. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Rebuilds the container credential from the containerPassword variable to authenticate the in-container test client; never persisted.')] [CmdletBinding(DefaultParameterSetName = 'ProjectFolder')] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [string] $Name, [Parameter(ParameterSetName = 'ProjectFolder')] [string] $ProjectFolder, [Parameter(Mandatory, ParameterSetName = 'ExtensionId')] [ValidateNotNullOrEmpty()] [string[]] $ExtensionId, [Parameter(Mandatory, ParameterSetName = 'Custom')] [ValidateNotNull()] [scriptblock] $TestExecution, [pscredential] $Credential, [string] $TestSuite = 'DEFAULT', [int] $TestRunnerCodeunitId, [switch] $DisableIsolation, [string] $Tenant = 'default', [string] $CompanyName = '', [string] $Culture = 'en-US', [ValidateSet('NavUserPassword', 'Windows', 'AAD')] [string] $Auth = 'NavUserPassword', [switch] $AzureDevOps, [string] $ResultPath = (Join-Path (Get-Location) 'TestResults.xml'), # Default into the shared mount (C:\run\my) so the result can be read on the host without # 'docker cp', which is unsupported against a running hyperv-isolated container. [string] $ResultPathInContainer = 'C:\run\my\TestResults.xml', [switch] $FailOnTestFailure, [string] $DockerExecutable = 'docker' ) # Fall back to the containerUsername/containerPassword variables (set by the Create BC Container # task) when no credential was passed - the in-container test client must authenticate, otherwise # it fails with 'InvalidCredentialsError' and the session never initialises. if (-not $Credential -and $env:containerUsername -and $env:containerPassword) { $Credential = [System.Management.Automation.PSCredential]::new( $env:containerUsername, (ConvertTo-SecureString -String $env:containerPassword -AsPlainText -Force)) } if ($PSCmdlet.ParameterSetName -eq 'Custom') { Invoke-BcContainerCommand -ContainerName $Name -DockerExecutable $DockerExecutable ` -Variables @{ ResultPathInContainer = $ResultPathInContainer } -ScriptBlock $TestExecution | Out-Null } else { $nativeArgs = @{ Name = $Name ResultPathInContainer = $ResultPathInContainer TestSuite = $TestSuite DisableIsolation = $DisableIsolation Tenant = $Tenant CompanyName = $CompanyName Culture = $Culture Auth = $Auth AzureDevOps = $AzureDevOps DockerExecutable = $DockerExecutable } if ($PSBoundParameters.ContainsKey('ProjectFolder')) { $nativeArgs['ProjectFolder'] = $ProjectFolder } if ($PSBoundParameters.ContainsKey('ExtensionId')) { $nativeArgs['ExtensionId'] = $ExtensionId } if ($Credential) { $nativeArgs['Credential'] = $Credential } if ($PSBoundParameters.ContainsKey('TestRunnerCodeunitId')) { $nativeArgs['TestRunnerCodeunitId'] = $TestRunnerCodeunitId } Invoke-BcNativeContainerTest @nativeArgs } # Prefer reading the result from the host side of the shared folder (the container wrote it # there); fall back to 'docker cp' for a process-isolated container or a custom path. $hostResult = $null if ($ResultPathInContainer -like 'C:\run\my\*') { $hostResult = Join-Path (Get-BcContainerHostShare -Name $Name) (Split-Path -Path $ResultPathInContainer -Leaf) } if ($hostResult -and (Test-Path -LiteralPath $hostResult)) { Copy-Item -LiteralPath $hostResult -Destination $ResultPath -Force } else { Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -Arguments @('cp', "$($Name):$ResultPathInContainer", $ResultPath) | Out-Null } if (-not (Test-Path -LiteralPath $ResultPath)) { throw "Test result file '$ResultPath' was not produced by the test run." } [xml] $doc = Get-Content -LiteralPath $ResultPath -Raw -Encoding UTF8 $summary = Get-BcTestResultSummary -Document $doc Write-ALbuildLog ("Tests: $($summary.Passed) passed, $($summary.Failed) failed, $($summary.Skipped) skipped.") foreach ($failure in $summary.Failures) { Write-ALbuildLog -Level Error $failure } $result = [PSCustomObject]@{ Passed = $summary.Passed Failed = $summary.Failed Skipped = $summary.Skipped Total = $summary.Total Failures = $summary.Failures Success = $summary.Success ResultPath = $ResultPath } if ($FailOnTestFailure -and -not $summary.Success) { throw "$($summary.Failed) test(s) failed." } return $result } |