Public/Invoke-QAFrameworkTestDiscovery.ps1
|
function Invoke-QAFrameworkTestDiscovery { <# .SYNOPSIS Harvests QAFramework tests by invoking the standalone orchestrator. .DESCRIPTION Installs or updates the package-local QAFramework tool and runs its discover command. Attribute parsing, filtering, metadata generation, and dependency harvesting are owned by Skyline.DataMiner.QAOps.QAFrameworkOrchestrator. .PARAMETER TestPackageContentPath Test package content root, or its TestHarvesting directory. .PARAMETER RegressionTestsRoot RegressionTests source directory. A relative path is resolved from TestHarvesting. .PARAMETER FolderTagPrefix Override for the generated automation script folder tag prefix. .PARAMETER OnlyTests Pin these test names in the harvest selection. .PARAMETER ForceOnlyTests Select only -OnlyTests, bypassing the package's other source filters. .PARAMETER BaselineGate Override the baseline selection gate. .PARAMETER Keywords Override the keyword selection. A value prefixed with ! is treated as an exclusion. .PARAMETER ExcludeKeywords Override keyword exclusions. .PARAMETER Squads Override the squad selection. A value prefixed with ! is treated as an exclusion. .PARAMETER ExcludeSquads Override squad exclusions. .PARAMETER IncludeDisabled Include disabled tests that otherwise match the active source selection. .PARAMETER ConfigPath Explicit unified or supported legacy QAFramework configuration file. .PARAMETER SupplementaryFilesPath Validated supplementary-files directory used for configuration overrides. .PARAMETER SkipDependencies Unsupported. The orchestrator always writes its complete dependency snapshot. .EXAMPLE Invoke-QAFrameworkTestDiscovery -TestPackageContentPath (Resolve-Path "$PSScriptRoot\..") .OUTPUTS The JSON discovery report returned by the orchestrator. #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true)] [string]$TestPackageContentPath, [Parameter()] [string]$RegressionTestsRoot, [Parameter()] [string]$FolderTagPrefix, [Parameter()] [string[]]$OnlyTests, [Parameter()] [switch]$ForceOnlyTests, [Parameter()] [ValidateSet('Required', 'Additive', 'Disabled')] [string]$BaselineGate, [Parameter()] [string[]]$Keywords, [Parameter()] [string[]]$ExcludeKeywords, [Parameter()] [string[]]$Squads, [Parameter()] [string[]]$ExcludeSquads, [Parameter()] [switch]$IncludeDisabled, [Parameter()] [string]$ConfigPath, [Parameter()] [string]$SupplementaryFilesPath, [Parameter()] [switch]$SkipDependencies ) if ($SkipDependencies) { throw '-SkipDependencies is no longer supported. QAFramework discovery writes the dependency snapshot required by the orchestrator.' } if ($ForceOnlyTests -and (-not $PSBoundParameters.ContainsKey('OnlyTests') -or @($OnlyTests).Count -eq 0)) { throw '-ForceOnlyTests requires at least one -OnlyTests name.' } $paths = Resolve-QAFrameworkContentPath -Path $TestPackageContentPath if (-not $PSCmdlet.ShouldProcess($paths.ContentPath, 'Discover QAFramework tests')) { return } $configPathResolved = $null $legacyDiscoveryConfig = $null $packageConfigInfo = Get-QAFrameworkPackageConfigInfo -ContentPath $paths.ContentPath if ($PSBoundParameters.ContainsKey('ConfigPath')) { $resolvedConfig = Resolve-Path -LiteralPath $ConfigPath -ErrorAction Stop if ($resolvedConfig.Provider.Name -ne 'FileSystem' -or -not (Test-Path -LiteralPath $resolvedConfig.ProviderPath -PathType Leaf)) { throw "ConfigPath must be an existing file: $ConfigPath" } $configPathResolved = $resolvedConfig.ProviderPath if ((Split-Path -Leaf $configPathResolved) -ieq 'qaframework.discovery.json') { try { $explicitConfig = Get-Content -LiteralPath $configPathResolved -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { throw "Could not read legacy QAFramework discovery configuration '$configPathResolved': $($_.Exception.Message)" } if ($explicitConfig -is [pscustomobject] -and $explicitConfig.PSObject.Properties.Name -notcontains 'schemaVersion') { $legacyDiscoveryConfig = ConvertFrom-QAFrameworkLegacyDiscoveryConfig -Document $explicitConfig $configPathResolved = $null } } } else { $legacyDiscoveryPath = Join-Path $paths.HarvestPath 'qaframework.discovery.json' $allowLegacyDiscoveryConfig = $null -eq $packageConfigInfo -or $packageConfigInfo.Policy -eq 'LegacyPipeline' if ($allowLegacyDiscoveryConfig -and (Test-Path -LiteralPath $legacyDiscoveryPath -PathType Leaf)) { try { $legacyDiscoveryDocument = Get-Content -LiteralPath $legacyDiscoveryPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { throw "Could not read legacy QAFramework discovery configuration '$legacyDiscoveryPath': $($_.Exception.Message)" } $legacyDiscoveryConfig = ConvertFrom-QAFrameworkLegacyDiscoveryConfig -Document $legacyDiscoveryDocument } } $supplementaryPathResolved = $null if ($PSBoundParameters.ContainsKey('SupplementaryFilesPath')) { $resolvedSupplementaryPath = Resolve-Path -LiteralPath $SupplementaryFilesPath -ErrorAction Stop if ($resolvedSupplementaryPath.Provider.Name -ne 'FileSystem' -or -not (Test-Path -LiteralPath $resolvedSupplementaryPath.ProviderPath -PathType Container)) { throw "SupplementaryFilesPath must be an existing directory: $SupplementaryFilesPath" } $supplementaryPathResolved = $resolvedSupplementaryPath.ProviderPath } $regressionTestsRoot = if ($PSBoundParameters.ContainsKey('RegressionTestsRoot')) { $RegressionTestsRoot } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('regressionTestsRoot')) { [string]$legacyDiscoveryConfig['regressionTestsRoot'] } else { $null } $sourcePath = $null if (-not [string]::IsNullOrWhiteSpace($regressionTestsRoot)) { $sourcePath = if ([System.IO.Path]::IsPathRooted($regressionTestsRoot)) { [System.IO.Path]::GetFullPath($regressionTestsRoot) } else { [System.IO.Path]::GetFullPath((Join-Path $paths.HarvestPath $regressionTestsRoot)) } if (-not (Test-Path -LiteralPath $sourcePath -PathType Container)) { throw "RegressionTestsRoot does not exist: $sourcePath" } } elseif ($PSBoundParameters.ContainsKey('RegressionTestsRoot')) { throw 'RegressionTestsRoot cannot be empty.' } $overrides = @{} $filter = @{} if ($legacyDiscoveryConfig -or (-not $PSBoundParameters.ContainsKey('ConfigPath') -and $null -eq $packageConfigInfo)) { $overrides['selectionPolicy'] = 'LegacyPipeline' } $hasOnlyTests = $PSBoundParameters.ContainsKey('OnlyTests') ` -or ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('onlyTests')) $onlyTests = if ($PSBoundParameters.ContainsKey('OnlyTests')) { [string[]]@($OnlyTests) } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('onlyTests')) { [string[]]@($legacyDiscoveryConfig['onlyTests']) } else { [string[]]@() } if ($hasOnlyTests) { if (@($onlyTests | Where-Object { [string]::IsNullOrWhiteSpace($_) }).Count -gt 0) { throw 'OnlyTests cannot contain empty names.' } $filter['includedTests'] = $onlyTests } $forceOnlyTests = if ($PSBoundParameters.ContainsKey('ForceOnlyTests')) { [bool]$ForceOnlyTests } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('forceOnlyTests')) { [bool]$legacyDiscoveryConfig['forceOnlyTests'] } else { $false } if ($forceOnlyTests -and (-not $hasOnlyTests -or $onlyTests.Count -eq 0)) { throw '-ForceOnlyTests requires at least one -OnlyTests name.' } $effectiveBaselineGate = if ($PSBoundParameters.ContainsKey('BaselineGate')) { $BaselineGate } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('baselineGate')) { [string]$legacyDiscoveryConfig['baselineGate'] } else { $null } if ($null -ne $effectiveBaselineGate) { $filter['baselineGate'] = $effectiveBaselineGate } foreach ($selection in @( @{ Parameter = 'Keywords'; LegacyKey = 'keywords'; IncludeKey = 'keywords'; ExcludeKey = 'excludeKeywords'; ExcludeParameter = 'ExcludeKeywords' }, @{ Parameter = 'Squads'; LegacyKey = 'squads'; IncludeKey = 'squads'; ExcludeKey = 'excludeSquads'; ExcludeParameter = 'ExcludeSquads' } )) { $includeSpecified = $PSBoundParameters.ContainsKey($selection.Parameter) ` -or ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains($selection.LegacyKey)) $rawValues = if ($PSBoundParameters.ContainsKey($selection.Parameter)) { [string[]]@($PSBoundParameters[$selection.Parameter]) } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains($selection.LegacyKey)) { [string[]]@($legacyDiscoveryConfig[$selection.LegacyKey]) } else { [string[]]@() } $includes = [System.Collections.Generic.List[string]]::new() $bangExcludes = [System.Collections.Generic.List[string]]::new() foreach ($value in $rawValues) { if ([string]::IsNullOrWhiteSpace($value)) { throw "$($selection.Parameter) cannot contain empty values." } if ($value.StartsWith('!') -and $value.Length -gt 1) { $bangExcludes.Add($value.Substring(1)) } else { $includes.Add($value) } } if ($includeSpecified) { $filter[$selection.IncludeKey] = [string[]]$includes } $excludeSpecified = $PSBoundParameters.ContainsKey($selection.ExcludeParameter) ` -or ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains($selection.ExcludeKey)) $excludes = [System.Collections.Generic.List[string]]::new() if ($PSBoundParameters.ContainsKey($selection.ExcludeParameter)) { $excludes.AddRange([string[]]@($PSBoundParameters[$selection.ExcludeParameter])) } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains($selection.ExcludeKey)) { $excludes.AddRange([string[]]@($legacyDiscoveryConfig[$selection.ExcludeKey])) } $excludes.AddRange([string[]]$bangExcludes) if ($excludeSpecified -or $bangExcludes.Count -gt 0) { if (@($excludes | Where-Object { [string]::IsNullOrWhiteSpace($_) }).Count -gt 0) { throw "$($selection.ExcludeParameter) cannot contain empty values." } $filter[$selection.ExcludeKey] = [string[]]$excludes } } $effectiveFolderTagPrefix = if ($PSBoundParameters.ContainsKey('FolderTagPrefix')) { $FolderTagPrefix } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('folderTagPrefix')) { [string]$legacyDiscoveryConfig['folderTagPrefix'] } else { $null } if ($null -ne $effectiveFolderTagPrefix) { if ([string]::IsNullOrWhiteSpace($effectiveFolderTagPrefix)) { throw 'FolderTagPrefix cannot be empty.' } $overrides['discovery'] = @{ folderTagPrefix = $effectiveFolderTagPrefix } } if ($forceOnlyTests) { $filter['mode'] = 'AttributeOnly' $filter['baselineGate'] = 'Disabled' $filter['keywords'] = [string[]]@() $filter['squads'] = [string[]]@() $filter['where'] = $null $filter['excludeKeywords'] = [string[]]@() $filter['excludeSquads'] = [string[]]@() $filter['excludedTests'] = [string[]]@() $filter['customers'] = [string[]]@() } if ($filter.Count -gt 0) { $overrides['filter'] = $filter } $includeDisabled = if ($PSBoundParameters.ContainsKey('IncludeDisabled')) { [bool]$IncludeDisabled } elseif ($legacyDiscoveryConfig -and $legacyDiscoveryConfig.Contains('includeDisabled')) { [bool]$legacyDiscoveryConfig['includeDisabled'] } else { $false } if (-not $includeDisabled -and $PSBoundParameters.ContainsKey('IncludeDisabled')) { $filter['includedDisabledTests'] = [string[]]@() $overrides['filter'] = $filter } $requestFiles = [System.Collections.Generic.List[string]]::new() try { $null = Install-QAFrameworkTool -PipelineDirectory $paths.PipelinePath $requestPath = New-QAFrameworkRequestFile -Overrides $overrides if ($requestPath) { $requestFiles.Add($requestPath) } if ($includeDisabled) { $previewArguments = @('preview', '--content', $paths.ContentPath, '--include-disabled') if ($configPathResolved) { $previewArguments += @('--config', $configPathResolved) } if ($sourcePath) { $previewArguments += @('--source', $sourcePath) } if ($supplementaryPathResolved) { $previewArguments += @('--supplementary-path', $supplementaryPathResolved) } if ($requestPath) { $previewArguments += @('--request-file', $requestPath) } $preview = Invoke-QAFrameworkToolJson -PipelineDirectory $paths.PipelinePath -Operation 'preview' -Arguments $previewArguments if ($null -eq $preview.disabledCandidates) { throw 'QAFramework preview did not return disabledCandidates; disabled tests were not harvested.' } $disabledNames = [string[]]@( $preview.disabledCandidates | ForEach-Object { [string]$_.name } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } ) $filter['includedDisabledTests'] = $disabledNames $overrides['filter'] = $filter $requestPath = New-QAFrameworkRequestFile -Overrides $overrides if ($requestPath) { $requestFiles.Add($requestPath) } } $discoverArguments = @('discover', '--content', $paths.ContentPath) if ($configPathResolved) { $discoverArguments += @('--config', $configPathResolved) } if ($sourcePath) { $discoverArguments += @('--source', $sourcePath) } if ($supplementaryPathResolved) { $discoverArguments += @('--supplementary-path', $supplementaryPathResolved) } if ($requestPath) { $discoverArguments += @('--request-file', $requestPath) } return Invoke-QAFrameworkToolJson -PipelineDirectory $paths.PipelinePath -Operation 'discover' -Arguments $discoverArguments } finally { foreach ($requestFile in $requestFiles) { if (Test-Path -LiteralPath $requestFile -PathType Leaf) { Remove-Item -LiteralPath $requestFile -Force } } } } |