Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests - use new Test-Config instead of constants.ps1 #9524

Merged
merged 26 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- [ ] Bug fix (non-breaking change, fixes #<!--issue number--> )
- [ ] New feature (non-breaking change, adds functionality, fixes #<!--issue number--> )
- [ ] Breaking change (affects multiple commands or functionality, fixes #<!--issue number--> )
- [ ] Ran manual Pester test and has passed (`.\tests\manual.pester.ps1`)
- [ ] Ran manual Pester test and has passed (`Invoke-ManualPester`)
- [ ] Adding code coverage to existing functionality
- [ ] Pester test is included
- [ ] If new file reference added for test, has is been added to github.com/dataplat/appveyor-lab ?
Expand Down
5 changes: 5 additions & 0 deletions dbatools.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ if (-not (Test-Path -Path "$script:PSModuleRoot\dbatools.dat") -or $script:seria
. $file.FullName
}

# All internal functions privately available within the toolset
foreach ($file in (Get-ChildItem -Path "$script:PSModuleRoot/private/testing/" -Recurse -Filter *.ps1)) {
. $file.FullName
}

Write-ImportTime -Text "Loading internal commands via dotsource"

# All exported functions
Expand Down
62 changes: 62 additions & 0 deletions private/testing/Get-TestConfig.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
function Get-TestConfig {
param(
[string]$LocalConfigPath = "$script:PSModuleRoot/tests/constants.local.ps1"
)
$config = [ordered]@{}

if (Test-Path $LocalConfigPath) {
Write-Host "Tests will use local constants file: tests\constants.local.ps1." -ForegroundColor Cyan
. $LocalConfigPath
# Note: Local constants are sourced but not explicitly added to $config
} elseif ($env:CODESPACES -and ($env:TERM_PROGRAM -eq 'vscode' -and $env:REMOTE_CONTAINERS)) {
$config['Instance1'] = "dbatools1"
$config['Instance2'] = "dbatools2"
$config['Instance3'] = "dbatools3"
$config['Instances'] = @($config['Instance1'], $config['Instance2'])

$config['SqlCred'] = [PSCredential]::new('sa', (ConvertTo-SecureString $env:SA_PASSWORD -AsPlainText -Force))
$config['PSDefaultParameterValues'] = @{
"*:SqlCredential" = $config['SqlCred']
}
} elseif ($env:GITHUB_WORKSPACE) {
$config['DbaToolsCi_Computer'] = "localhost"
$config['Instance1'] = "localhost"
$config['Instance2'] = "localhost:14333"
$config['Instance2SQLUserName'] = $null # placeholders for -SqlCredential testing
$config['Instance2SQLPassword'] = $null
$config['Instance3'] = "localhost"
$config['Instance2_Detailed'] = "localhost,14333" # Just to make sure things parse a port properly
$config['AppveyorLabRepo'] = "/tmp/appveyor-lab"
$config['Instances'] = @($config['Instance1'], $config['Instance2'])
$config['SsisServer'] = "localhost\sql2016"
$config['AzureBlob'] = "https://dbatools.blob.core.windows.net/sql"
$config['AzureBlobAccount'] = "dbatools"
$config['AzureServer'] = 'psdbatools.database.windows.net'
$config['AzureSqlDbLogin'] = "[email protected]"
} else {
$config['DbaToolsCi_Computer'] = "localhost"
$config['Instance1'] = "localhost\sql2008r2sp2"
$config['Instance2'] = "localhost\sql2016"
$config['Instance2SQLUserName'] = $null # placeholders for -SqlCredential testing
$config['Instance2SQLPassword'] = $null
$config['Instance3'] = "localhost\sql2017"
$config['Instance2_Detailed'] = "localhost,14333\sql2016" # Just to make sure things parse a port properly
$config['AppveyorLabRepo'] = "C:\github\appveyor-lab"
$config['Instances'] = @($config['Instance1'], $config['Instance2'])
$config['SsisServer'] = "localhost\sql2016"
$config['AzureBlob'] = "https://dbatools.blob.core.windows.net/sql"
$config['AzureBlobAccount'] = "dbatools"
$config['AzureServer'] = 'psdbatools.database.windows.net'
$config['AzureSqlDbLogin'] = "[email protected]"
$config['BigDatabaseBackup'] = 'C:\github\StackOverflowMini.bak'
$config['BigDatabaseBackupSourceUrl'] = 'https://github.com/BrentOzarULTD/Stack-Overflow-Database/releases/download/20230114/StackOverflowMini.bak'
}

if ($env:appveyor) {
$config['PSDefaultParameterValues'] = @{
'*:WarningAction' = 'SilentlyContinue'
}
}

[pscustomobject]$config
}
252 changes: 252 additions & 0 deletions private/testing/Invoke-ManualPester.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
function Invoke-ManualPester {
<#
.SYNOPSIS
Runs dbatools tests.

.DESCRIPTION
This is an helper to automate running tests locally

.PARAMETER Path
The Path to the test files to run. It accepts multiple test file paths passed in (e.g. .\Find-DbaOrphanedFile.Tests.ps1) as well
as simple strings (e.g. "orphaned" will run all files matching .\*orphaned*.Tests.ps1)

.PARAMETER Show
Gets passed down to Pester's -Show parameter (useful if you want to reduce verbosity)

.PARAMETER PassThru
Gets passed down to Pester's -PassThru parameter (useful if you want to return an object to analyze)

.PARAMETER TestIntegration
dbatools's suite has unittests and integrationtests. This switch enables IntegrationTests, which need live instances
see constants.ps1 for customizations

.PARAMETER Coverage
Enables measuring code coverage on the tested function

.PARAMETER DependencyCoverage
Enables measuring code coverage also of "lower level" (i.e. called) functions

.PARAMETER ScriptAnalyzer
Enables checking the called function's code with Invoke-ScriptAnalyzer, with dbatools's profile

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration -Coverage -DependencyCoverage -ScriptAnalyzer

The most complete number of checks:
- Runs both unittests and integrationtests
- Gathers and shows code coverage measurement for Find-DbaOrphanedFile and all its dependencies
- Checks Find-DbaOrphanedFile with Invoke-ScriptAnalyzer

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1

Runs unittests stored in Find-DbaOrphanedFile.Tests.ps1

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -PassThru

Runs unittests stored in Find-DbaOrphanedFile.Tests.ps1 and returns an object that can be analyzed

.EXAMPLE
Invoke-ManualPester -Path orphan

Runs unittests for all tests matching in `*orphan*.Tests.ps1

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -Show Default

Runs unittests stored in Find-DbaOrphanedFile.Tests.ps1, with reduced verbosity

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration

Runs both unittests and integrationtests stored in Find-DbaOrphanedFile.Tests.ps1

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration -Coverage

Gathers and shows code coverage measurement for Find-DbaOrphanedFile

.EXAMPLE
Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration -Coverage -DependencyCoverage

Gathers and shows code coverage measurement for Find-DbaOrphanedFile and all its dependencies

#>

[CmdletBinding()]
param (
[Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[string[]]$Path,
[ValidateSet('None', 'Default', 'Passed', 'Failed', 'Pending', 'Skipped', 'Inconclusive', 'Describe', 'Context', 'Summary', 'Header', 'All', 'Fails')]
[string]$Show = "All",
[switch]$PassThru,
[switch]$TestIntegration,
[switch]$Coverage,
[switch]$DependencyCoverage,
[switch]$ScriptAnalyzer
)

<#
Remove-Module -Name Pester
Import-Module -name Pester -MaximumVersion 4.*
#>

$invokeFormatterVersion = (Get-Command Invoke-Formatter -ErrorAction SilentlyContinue).Version
$HasScriptAnalyzer = $null -ne $invokeFormatterVersion
$MinimumPesterVersion = [Version] '3.4.5.0' # Because this is when -Show was introduced
$MaximumPesterVersion = [Version] '5.0.0.0' # Because our tests (and runners) are only compatible with 4.*
$PesterVersion = (Get-Command Invoke-Pester -ErrorAction SilentlyContinue).Version
$HasPester = $null -ne $PesterVersion
$ScriptAnalyzerCorrectVersion = '1.18.2'

if (!($HasScriptAnalyzer)) {
Write-Warning "Please install PSScriptAnalyzer"
Write-Warning " Install-Module -Name PSScriptAnalyzer -RequiredVersion '$ScriptAnalyzerCorrectVersion'"
Write-Warning " or go to https://github.com/PowerShell/PSScriptAnalyzer"
} else {
if ($invokeFormatterVersion -ne $ScriptAnalyzerCorrectVersion) {
Remove-Module PSScriptAnalyzer
try {
Import-Module PSScriptAnalyzer -RequiredVersion $ScriptAnalyzerCorrectVersion -ErrorAction Stop
} catch {
Write-Warning "Please install PSScriptAnalyzer $ScriptAnalyzerCorrectVersion"
Write-Warning " Install-Module -Name PSScriptAnalyzer -RequiredVersion '$ScriptAnalyzerCorrectVersion'"
}
}
}
if (!($HasPester)) {
Write-Warning "Please install Pester"
Write-Warning " Install-Module -Name Pester -Force -SkipPublisherCheck"
Write-Warning " or go to https://github.com/pester/Pester"
}
if ($PesterVersion -lt $MinimumPesterVersion) {
Write-Warning "Please update Pester to at least 3.4.5"
Write-Warning " Install-Module -Name Pester -MaximumVersion '4.10' -Force -SkipPublisherCheck"
Write-Warning " or go to https://github.com/pester/Pester"
}
if ($PesterVersion -gt $MaximumPesterVersion) {
Write-Warning "Please get Pester to the 4.* release"
Write-Warning " Install-Module -Name Pester -MaximumVersion '4.10' -Force -SkipPublisherCheck"
Write-Warning " or go to https://github.com/pester/Pester"
}

if (($HasPester -and $HasScriptAnalyzer -and ($PesterVersion -ge $MinimumPesterVersion) -and ($PesterVersion -lt $MaximumPesterVersion) -and ($invokeFormatterVersion -eq $ScriptAnalyzerCorrectVersion)) -eq $false) {
Write-Warning "Exiting..."
return
}

$ModuleBase = Split-Path -Path $PSScriptRoot -Parent

if (-not(Test-Path "$ModuleBase\.git" -Type Container)) {
New-Item -Type Container -Path "$ModuleBase\.git" -Force
}

#removes previously imported dbatools, if any
# No need the force will do it
# Remove-Module dbatools -ErrorAction Ignore
#imports the module making sure DLL is loaded ok
Import-Module "$ModuleBase\dbatools.psd1" -DisableNameChecking -Force
#imports the psm1 to be able to use internal functions in tests
Import-Module "$ModuleBase\dbatools.psm1" -DisableNameChecking -Force

$ScriptAnalyzerRulesExclude = @('PSUseOutputTypeCorrectly', 'PSAvoidUsingPlainTextForPassword', 'PSUseBOMForUnicodeEncodedFile')

$testInt = $false
if ($config_TestIntegration) {
$testInt = $true
}
if ($TestIntegration) {
$testInt = $true
}

function Get-CoverageIndications($Path, $ModuleBase) {
# takes a test file path and figures out what to analyze for coverage (i.e. dependencies)
$CBHRex = [regex]'(?smi)<#(.*)#>'
$everything = (Get-Module dbatools).ExportedCommands.Values
$everyfunction = $everything.Name
$funcs = @()
$leaf = Split-Path $path -Leaf
# assuming Get-DbaFoo.Tests.ps1 wants coverage for "Get-DbaFoo"
# but allowing also Get-DbaFoo.one.Tests.ps1 and Get-DbaFoo.two.Tests.ps1
$func_name += ($leaf -replace '^([^.]+)(.+)?.Tests.ps1', '$1')
if ($func_name -in $everyfunction) {
$funcs += $func_name
$f = $everything | Where-Object Name -eq $func_name
$source = $f.Definition
$CBH = $CBHRex.match($source).Value
$cmdonly = $source.Replace($CBH, '')
foreach ($e in $everyfunction) {
# hacky, I know, but every occurrence of any function plus a space kinda denotes usage !?
$searchme = "$e "
if ($cmdonly.contains($searchme)) {
$funcs += $e
}
}
}
$testpaths = @()
$allfiles = Get-ChildItem -File -Path "$ModuleBase\private\functions", "$ModuleBase\public" -Filter '*.ps1'
foreach ($f in $funcs) {
# exclude always used functions ?!
if ($f -in ('Connect-DbaInstance', 'Select-DefaultView', 'Stop-Function', 'Write-Message')) { continue }
# can I find a correspondence to a physical file (again, on the convenience of having Get-DbaFoo.ps1 actually defining Get-DbaFoo)?
$res = $allfiles | Where-Object { $_.Name.Replace('.ps1', '') -eq $f }
if ($res.count -gt 0) {
$testpaths += $res.FullName
}
}
return @() + ($testpaths | Select-Object -Unique)
}

$files = @()

if ($Path) {
foreach ($item in $path) {
if (Test-Path $item) {
$files += Get-ChildItem -Path $item
} else {
$files += Get-ChildItem -Path "$ModuleBase\tests\*$item*.Tests.ps1"
}
}
}

if ($files.Length -eq 0) {
Write-Warning "No tests to be run"
}

$AllTestsWithinScenario = $files

foreach ($f in $AllTestsWithinScenario) {
$PesterSplat = @{
'Script' = $f.FullName
'Show' = $show
'PassThru' = $passThru
}
#opt-in
$HeadFunctionPath = $f.FullName

if ($Coverage -or $ScriptAnalyzer) {
$CoverFiles = Get-CoverageIndications -Path $f -ModuleBase $ModuleBase
$HeadFunctionPath = $CoverFiles | Select-Object -First 1
}
if ($Coverage) {
if ($DependencyCoverage) {
$CoverFilesPester = $CoverFiles
} else {
$CoverFilesPester = $HeadFunctionPath
}
$PesterSplat['CodeCoverage'] = $CoverFilesPester
}
if (!($testInt)) {
$PesterSplat['ExcludeTag'] = "IntegrationTests"
}
Invoke-Pester @PesterSplat
if ($ScriptAnalyzer) {
if ($Show -ne "None") {
Write-Host -ForegroundColor green -Object "ScriptAnalyzer check for $HeadFunctionPath"
}
Invoke-ScriptAnalyzer -Path $HeadFunctionPath -ExcludeRule $ScriptAnalyzerRulesExclude
}
}
}
16 changes: 8 additions & 8 deletions tests/Add-DbaAgDatabase.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "")
Write-Host -Object "Running $PSCommandpath" -ForegroundColor Cyan
. "$PSScriptRoot\constants.ps1"
$global:TestConfig = Get-TestConfig

Describe "$CommandName Unit Tests" -Tag 'UnitTests' {
Context "Validate parameters" {
Expand All @@ -15,14 +15,14 @@ Describe "$CommandName Unit Tests" -Tag 'UnitTests' {

Describe "$commandname Integration Tests" -Tag "IntegrationTests" {
BeforeAll {
$null = Get-DbaProcess -SqlInstance $script:instance3 -Program 'dbatools PowerShell module - dbatools.io' | Stop-DbaProcess -WarningAction SilentlyContinue
$server = Connect-DbaInstance -SqlInstance $script:instance3
$null = Get-DbaProcess -SqlInstance $TestConfig.instance3 -Program 'dbatools PowerShell module - dbatools.io' | Stop-DbaProcess -WarningAction SilentlyContinue
$server = Connect-DbaInstance -SqlInstance $TestConfig.instance3
$agname = "dbatoolsci_addagdb_agroup"
$dbname = "dbatoolsci_addagdb_agroupdb"
$newdbname = "dbatoolsci_addag_agroupdb_2"
$server.Query("create database $dbname")
$backup = Get-DbaDatabase -SqlInstance $script:instance3 -Database $dbname | Backup-DbaDatabase
$ag = New-DbaAvailabilityGroup -Primary $script:instance3 -Name $agname -ClusterType None -FailoverMode Manual -Database $dbname -Confirm:$false -Certificate dbatoolsci_AGCert
$backup = Get-DbaDatabase -SqlInstance $TestConfig.instance3 -Database $dbname | Backup-DbaDatabase
$ag = New-DbaAvailabilityGroup -Primary $TestConfig.instance3 -Name $agname -ClusterType None -FailoverMode Manual -Database $dbname -Confirm:$false -Certificate dbatoolsci_AGCert
}
AfterAll {
$null = Remove-DbaAvailabilityGroup -SqlInstance $server -AvailabilityGroup $agname -Confirm:$false
Expand All @@ -31,11 +31,11 @@ Describe "$commandname Integration Tests" -Tag "IntegrationTests" {
Context "adds ag db" {
It "returns proper results" {
$server.Query("create database $newdbname")
$backup = Get-DbaDatabase -SqlInstance $script:instance3 -Database $newdbname | Backup-DbaDatabase
$results = Add-DbaAgDatabase -SqlInstance $script:instance3 -AvailabilityGroup $agname -Database $newdbname -Confirm:$false
$backup = Get-DbaDatabase -SqlInstance $TestConfig.instance3 -Database $newdbname | Backup-DbaDatabase
$results = Add-DbaAgDatabase -SqlInstance $TestConfig.instance3 -AvailabilityGroup $agname -Database $newdbname -Confirm:$false
$results.AvailabilityGroup | Should -Be $agname
$results.Name | Should -Be $newdbname
$results.IsJoined | Should -Be $true
}
}
} #$script:instance2 for appveyor
} #$TestConfig.instance2 for appveyor
Loading
Loading