Skip to content

Commit 64741f2

Browse files
authored
Merge branch 'main' into copilot/fix-431868db-717c-46b0-9850-6caf1fb09dd8
2 parents 140973a + 4344231 commit 64741f2

34 files changed

+2387
-227
lines changed

.build/README.md

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1-
# DSC Resource Integration Test Optimization
1+
# .build scripts
22

3-
This document describes the script used to dynamically determine whether DSC
4-
resource integration tests should run in Azure Pipelines.
3+
Documentation for the SqlServerDsc module build and pipeline scripts.
54

6-
## What the Script Does
5+
## `Test-ShouldRunDscResourceIntegrationTests.ps1`
76

8-
The `Test-ShouldRunDscResourceIntegrationTests.ps1` script analyzes git
7+
This script dynamically determines whether DSC resource integration tests
8+
should run in Azure Pipelines.
9+
10+
### What the Script Does
11+
<!-- markdownlint-disable-next-line MD013 -->
12+
The [`Test-ShouldRunDscResourceIntegrationTests.ps1`](./Test-ShouldRunDscResourceIntegrationTests.ps1) script analyzes git
913
changes between two references and determines if DSC resource integration tests
1014
need to run. It automatically discovers which public commands are used by DSC
1115
resources and classes, then checks if any relevant files have been modified.
1216

13-
## How It Works
17+
### How It Works
1418

15-
The script checks for changes to:
19+
The script performs an optimized analysis by checking for changes in this order:
1620

21+
1. **Early Source Check**: First checks if any files under the configured
22+
source path (`-SourcePath`, default `source/`) have changed
23+
- If no source changes, skips integration tests immediately
1724
1. **DSC Resources**: Files under `source/DSCResources/`
1825
1. **Classes**: Files under `source/Classes/`
1926
1. **Public Commands**: Commands that are actually used by DSC resources or
@@ -23,26 +30,94 @@ The script checks for changes to:
2330
1. **Integration Tests**: DSC resource integration test files under
2431
`tests/Integration/Resources/`
2532

26-
## Usage
33+
### Flow Diagram
34+
35+
The following diagram illustrates the decision flow of the script:
36+
37+
<!-- markdownlint-disable MD013 - Mermaid diagram has long lines -->
38+
```mermaid
39+
flowchart TD
40+
Start([Start Script]) --> Init[Initialize Parameters<br/>BaseBranch, CurrentBranch, UseMergeBase]
41+
Init --> GetChanges[Get Changed Files<br/>git diff between branches]
42+
43+
GetChanges --> HasChanges{Any Changed<br/>Files?}
44+
HasChanges -->|No| RunTrue[Return TRUE<br/>Run integration tests]
45+
46+
HasChanges -->|Yes| CheckSource{Any Changes<br/>Under SourcePath?}
47+
CheckSource -->|No| Skip[Return FALSE<br/>Skip integration tests]
48+
49+
CheckSource -->|Yes| Discover[Discover Public Commands<br/>Used by DSC Resources]
50+
Discover --> CheckDscRes{DSC Resources or<br/>Classes Changed?<br/>source/DSCResources/<br/>source/Classes/}
51+
CheckDscRes -->|Yes| RunTrue
52+
53+
CheckDscRes -->|No| CheckPublic{Public Commands<br/>Used by DSC<br/>Resources Changed?<br/>source/Public/}
54+
CheckPublic -->|Yes| RunTrue
55+
56+
CheckPublic -->|No| CheckPrivate{Private Functions<br/>Used by DSC-related<br/>Commands Changed?<br/>source/Private/}
57+
CheckPrivate -->|Yes| RunTrue
58+
59+
CheckPrivate -->|No| CheckTests{Integration Test<br/>Files Changed?<br/>tests/Integration/Resources/}
60+
CheckTests -->|Yes| RunTrue
61+
62+
CheckTests -->|No| Skip
63+
64+
RunTrue --> End([End])
65+
Skip --> End
2766
28-
### Azure Pipelines
67+
style Start fill:#e1f5fe
68+
style End fill:#e8f5e8
69+
style RunTrue fill:#fff3e0
70+
style Skip fill:#f3e5f5
71+
```
72+
<!-- markdownlint-enable MD013 -->
73+
74+
### Parameters
75+
76+
| Parameter | Type | Default | Purpose |
77+
|-----------|------|---------|---------|
78+
| `BaseBranch` | String | `'origin/main'` | Base branch to compare against |
79+
| `CurrentBranch` | String | `'HEAD'` | Current branch or commit to compare |
80+
| `UseMergeBase` | Switch | `$false` | Use merge-base to compute diff base |
81+
82+
### Outputs
83+
84+
<!-- markdownlint-disable MD013 - Table with long descriptions -->
85+
| Output | Type | Description |
86+
|--------|------|-------------|
87+
| Return value | Boolean | `$true` when the monitored categories have relevant changes between the specified refs, `$false` when no such changes are detected |
88+
<!-- markdownlint-enable MD013 -->
89+
90+
### Usage
91+
92+
#### Azure Pipelines
2993

3094
The Azure Pipelines task sets an output variable that downstream stages can
3195
use to conditionally run DSC resource integration tests. The script returns
3296
a boolean value that the pipeline captures, e.g.:
3397

98+
<!-- markdownlint-disable MD013 -->
3499
```yaml
35100
- powershell: |
36-
$shouldRun = ./.build/Test-ShouldRunDscResourceIntegrationTests.ps1 -BaseBranch $targetBranch -CurrentBranch HEAD
101+
$shouldRun = & ./.build/Test-ShouldRunDscResourceIntegrationTests.ps1 -BaseBranch $targetBranch -CurrentBranch HEAD -UseMergeBase
37102
Write-Host "##vso[task.setvariable variable=ShouldRunDscResourceIntegrationTests;isOutput=true]$shouldRun"
38103
displayName: 'Determine if DSC resource tests should run'
104+
name: determineShouldRun
39105
```
106+
<!-- markdownlint-enable MD013 -->
40107
41-
Downstream stages reference this output variable using the pattern:
42-
`dependencies.JobName.outputs['StepName.VariableName']` to gate their
43-
execution based on whether DSC resource tests should run.
108+
Downstream stages reference this output variable in a stage `condition:`
109+
using the pattern:
110+
<!-- markdownlint-disable MD013 -->
111+
```yaml
112+
condition: |
113+
and(
114+
succeeded(),
115+
eq(lower(dependencies.stageName.outputs['jobName.taskName.ShouldRunDscResourceIntegrationTests']), 'true')
116+
)
117+
```
118+
<!-- markdownlint-enable MD013 -->
44119

45-
### Command Line
120+
#### Command Line
46121

47122
```powershell
48123
# Basic usage (compares current HEAD with origin/main)
@@ -52,9 +127,3 @@ execution based on whether DSC resource tests should run.
52127
.build/Test-ShouldRunDscResourceIntegrationTests.ps1 -BaseBranch 'origin/dev' \
53128
-CurrentBranch 'feature-branch'
54129
```
55-
56-
## Dynamic Discovery
57-
58-
The script automatically discovers public commands used by DSC resources by
59-
scanning source files, eliminating the need to maintain hardcoded lists.
60-
This ensures accuracy and reduces maintenance overhead.

.build/Test-ShouldRunDscResourceIntegrationTests.ps1

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,20 @@
1717
.PARAMETER CurrentBranch
1818
The current branch or commit to compare. Default is 'HEAD'.
1919
20+
.PARAMETER UseMergeBase
21+
When specified, compares the current branch against the merge-base with the base branch
22+
instead of directly comparing against the base branch. This is useful for comparing
23+
only the changes introduced by the current branch.
24+
2025
.EXAMPLE
2126
Test-ShouldRunDscResourceIntegrationTests
2227
2328
.EXAMPLE
2429
Test-ShouldRunDscResourceIntegrationTests -BaseBranch 'origin/main' -CurrentBranch 'HEAD'
2530
31+
.EXAMPLE
32+
Test-ShouldRunDscResourceIntegrationTests -BaseBranch 'origin/main' -CurrentBranch 'HEAD' -UseMergeBase
33+
2634
.OUTPUTS
2735
System.Boolean. Returns $true if DSC resource integration tests should run, $false otherwise.
2836
#>
@@ -35,7 +43,11 @@ param
3543

3644
[Parameter()]
3745
[System.String]
38-
$CurrentBranch = 'HEAD'
46+
$CurrentBranch = 'HEAD',
47+
48+
[Parameter()]
49+
[System.Management.Automation.SwitchParameter]
50+
$UseMergeBase
3951
)
4052

4153
<#
@@ -137,17 +149,25 @@ function Get-PublicCommandsUsedByDscResources
137149
.DESCRIPTION
138150
This function retrieves the list of files that have been modified between
139151
two git references using git diff. It handles various scenarios including
140-
different diff syntax and untracked files.
152+
different diff syntax and untracked files. Optionally can compare against
153+
the merge-base of the two references.
141154
142155
.PARAMETER From
143156
The source git reference (branch, commit, tag).
144157
145158
.PARAMETER To
146159
The target git reference (branch, commit, tag).
147160
161+
.PARAMETER UseMergeBase
162+
When specified, finds the merge-base between From and To references and
163+
compares To against that merge-base instead of directly against From.
164+
148165
.EXAMPLE
149166
Get-ChangedFiles -From 'origin/main' -To 'HEAD'
150167
168+
.EXAMPLE
169+
Get-ChangedFiles -From 'origin/main' -To 'HEAD' -UseMergeBase
170+
151171
.OUTPUTS
152172
System.String[]. Array of file paths that have been changed.
153173
#>
@@ -162,23 +182,46 @@ function Get-ChangedFiles
162182

163183
[Parameter(Mandatory = $true)]
164184
[System.String]
165-
$To
185+
$To,
186+
187+
[Parameter()]
188+
[System.Management.Automation.SwitchParameter]
189+
$UseMergeBase
166190
)
167191

168192
try
169193
{
194+
$compareFrom = $From
195+
196+
# If UseMergeBase is specified, find the merge-base between From and To
197+
if ($UseMergeBase)
198+
{
199+
Write-Verbose "Finding merge-base between $From and $To"
200+
$mergeBase = & git merge-base $From $To 2>&1
201+
if ($LASTEXITCODE -eq 0 -and $mergeBase)
202+
{
203+
$compareFrom = $mergeBase.Trim()
204+
Write-Verbose "Using merge-base: $compareFrom"
205+
}
206+
else
207+
{
208+
Write-Warning "Failed to find merge-base between $From and $To. Falling back to direct comparison. Exit code: $LASTEXITCODE. Output: $mergeBase"
209+
$compareFrom = $From
210+
}
211+
}
212+
170213
# Try different git diff approaches
171214
$gitDiffOutput = $null
172215

173216
# First, try the standard diff
174-
$gitDiffOutput = & git diff --name-only "$From..$To" 2>&1
217+
$gitDiffOutput = & git diff --name-only "$compareFrom..$To" 2>&1
175218
if ($LASTEXITCODE -eq 0 -and $gitDiffOutput)
176219
{
177220
return $gitDiffOutput | Where-Object -FilterScript { $_ -and $_.Trim() }
178221
}
179222

180223
# If that fails, try without the range syntax
181-
$gitDiffOutput = & git diff --name-only $From $To 2>&1
224+
$gitDiffOutput = & git diff --name-only $compareFrom $To 2>&1
182225
if ($LASTEXITCODE -eq 0 -and $gitDiffOutput)
183226
{
184227
return $gitDiffOutput | Where-Object -FilterScript { $_ -and $_.Trim() }
@@ -194,7 +237,7 @@ function Get-ChangedFiles
194237
}
195238
}
196239

197-
Write-Warning "Failed to get git diff between $From and $To. Exit code: $LASTEXITCODE. Output: $gitDiffOutput"
240+
Write-Warning "Failed to get git diff between $compareFrom and $To. Exit code: $LASTEXITCODE. Output: $gitDiffOutput"
198241
return @()
199242
}
200243
catch
@@ -337,16 +380,23 @@ function Get-PrivateFunctionsUsedByClassResources
337380
.DESCRIPTION
338381
This function analyzes the changes between two git references and determines
339382
if DSC resource integration tests should run based on the files that have
340-
been modified. It checks for changes to DSC resources, classes, public
341-
commands used by DSC resources, private functions used by those public commands,
342-
private functions used by class-based DSC resources, and integration
343-
tests.
383+
been modified. It performs an optimized analysis by first checking if any
384+
changes exist under the source/ folder. If no source changes are detected,
385+
it skips the expensive analysis and returns false. Otherwise, it checks for
386+
changes to DSC resources, classes, public commands used by DSC resources,
387+
private functions used by those public commands, private functions used by
388+
class-based DSC resources, and integration tests.
344389
.PARAMETER BaseBranch
345390
The base branch to compare against. Default is 'origin/main'.
346391
347392
.PARAMETER CurrentBranch
348393
The current branch or commit to compare. Default is 'HEAD'.
349394
395+
.PARAMETER UseMergeBase
396+
When specified, compares the current branch against the merge-base with the base branch
397+
instead of directly comparing against the base branch. This is useful for comparing
398+
only the changes introduced by the current branch.
399+
350400
.PARAMETER SourcePath
351401
The source path containing the source code directories. Default is 'source'.
352402
@@ -356,6 +406,9 @@ function Get-PrivateFunctionsUsedByClassResources
356406
.EXAMPLE
357407
Test-ShouldRunDscResourceIntegrationTests -BaseBranch 'origin/main' -CurrentBranch 'feature-branch'
358408
409+
.EXAMPLE
410+
Test-ShouldRunDscResourceIntegrationTests -BaseBranch 'origin/main' -CurrentBranch 'feature-branch' -UseMergeBase
411+
359412
.OUTPUTS
360413
System.Boolean. Returns $true if DSC resource integration tests should run, $false otherwise.
361414
#>
@@ -372,21 +425,27 @@ function Test-ShouldRunDscResourceIntegrationTests
372425
[System.String]
373426
$CurrentBranch = 'HEAD',
374427

428+
[Parameter()]
429+
[System.Management.Automation.SwitchParameter]
430+
$UseMergeBase,
431+
375432
[Parameter()]
376433
[System.String]
377434
$SourcePath = 'source'
378435
)
379436

380437
Write-Host "##[section]Analyzing DSC Resource Integration Test Requirements"
381-
Write-Host "Analyzing changes between $BaseBranch and $CurrentBranch..."
382-
Write-Host ""
383-
384-
# Get list of public commands used by DSC resources dynamically
385-
$PublicCommandsUsedByDscResources = Get-PublicCommandsUsedByDscResources -SourcePath $SourcePath
386-
Write-Host "Discovered $($PublicCommandsUsedByDscResources.Count) public commands used by DSC resources and classes."
438+
if ($UseMergeBase)
439+
{
440+
Write-Host "Analyzing changes introduced by $CurrentBranch since merge-base with $BaseBranch..."
441+
}
442+
else
443+
{
444+
Write-Host "Analyzing changes between $BaseBranch and $CurrentBranch..."
445+
}
387446
Write-Host ""
388447

389-
$changedFiles = Get-ChangedFiles -From $BaseBranch -To $CurrentBranch
448+
$changedFiles = Get-ChangedFiles -From $BaseBranch -To $CurrentBranch -UseMergeBase:$UseMergeBase
390449

391450
if (-not $changedFiles)
392451
{
@@ -400,6 +459,21 @@ function Test-ShouldRunDscResourceIntegrationTests
400459
Write-Host "##[endgroup]"
401460
Write-Host ""
402461

462+
# Early optimization: Check if any changes are under the source folder
463+
$sourcePrefix = '^' + [regex]::Escape((($SourcePath -replace '\\','/') -replace '/+$','')) + '/'
464+
$changedSourceFiles = $changedFiles | Where-Object -FilterScript { $_ -match $sourcePrefix }
465+
if (-not $changedSourceFiles)
466+
{
467+
Write-Host "No changes detected under the source folder. DSC resource integration tests can be skipped."
468+
Write-Host ""
469+
return $false
470+
}
471+
472+
# Get list of public commands used by DSC resources dynamically (only when needed)
473+
$PublicCommandsUsedByDscResources = Get-PublicCommandsUsedByDscResources -SourcePath $SourcePath
474+
Write-Host "Discovered $($PublicCommandsUsedByDscResources.Count) public commands used by DSC resources and classes."
475+
Write-Host ""
476+
403477
# Check if any DSC resources are directly changed
404478
$changedDscResources = $changedFiles | Where-Object -FilterScript { $_ -match '^source/DSCResources/' -or $_ -match '^source/Classes/' }
405479
if ($changedDscResources)
@@ -413,7 +487,7 @@ function Test-ShouldRunDscResourceIntegrationTests
413487
}
414488

415489
# Check if any public commands used by DSC resources are changed
416-
$changedPublicCommands = $changedFiles | Where-Object -FilterScript { $_ -match '^source/Public/(.+)\.ps1$' } |
490+
$changedPublicCommands = $changedFiles | Where-Object -FilterScript { $_ -match '^source/Public/(.+)\.ps1$' } |
417491
ForEach-Object -Process { [System.IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $_ -Leaf)) }
418492

419493
$affectedCommands = $changedPublicCommands | Where-Object -FilterScript { $_ -in $PublicCommandsUsedByDscResources }
@@ -428,7 +502,7 @@ function Test-ShouldRunDscResourceIntegrationTests
428502
}
429503

430504
# Check if any private functions used by the affected public commands or class-based DSC resources are changed
431-
$changedPrivateFunctions = $changedFiles | Where-Object -FilterScript { $_ -match '^source/Private/(.+)\.ps1$' } |
505+
$changedPrivateFunctions = $changedFiles | Where-Object -FilterScript { $_ -match '^source/Private/(.+)\.ps1$' } |
432506
ForEach-Object -Process { [System.IO.Path]::GetFileNameWithoutExtension((Split-Path -Path $_ -Leaf)) }
433507

434508
$affectedPrivateFunctions = @()
@@ -477,7 +551,7 @@ function Test-ShouldRunDscResourceIntegrationTests
477551
# If script is run directly (not imported), execute the main function
478552
if ($MyInvocation.InvocationName -ne '.')
479553
{
480-
$shouldRun = Test-ShouldRunDscResourceIntegrationTests -BaseBranch $BaseBranch -CurrentBranch $CurrentBranch
554+
$shouldRun = Test-ShouldRunDscResourceIntegrationTests -BaseBranch $BaseBranch -CurrentBranch $CurrentBranch -UseMergeBase:$UseMergeBase
481555

482556
# Provide clear final result with appropriate color coding
483557
Write-Host "##[section]Test Requirements Decision"
@@ -490,10 +564,6 @@ if ($MyInvocation.InvocationName -ne '.')
490564
Write-Host "RESULT: DSC resource integration tests will be SKIPPED"
491565
}
492566

493-
# Output the result for the calling script to capture
494-
Write-Output -InputObject ""
495-
Write-Output -InputObject "ShouldRunDscResourceIntegrationTests: $shouldRun"
496-
497567
# Return the boolean value for pipeline script to use
498568
return $shouldRun
499569
}

0 commit comments

Comments
 (0)