Skip to content

Commit

Permalink
Tests - use new Test-Config instead of constants.ps1 (#9524)
Browse files Browse the repository at this point in the history
  • Loading branch information
potatoqualitee authored Oct 21, 2024
1 parent d615e36 commit c0feb7a
Show file tree
Hide file tree
Showing 716 changed files with 5,272 additions and 5,133 deletions.
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

0 comments on commit c0feb7a

Please sign in to comment.