Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
afe4c12
Initial CC implementation
spetersenms Jan 22, 2026
0fd2190
Load order fix
spetersenms Jan 22, 2026
90ad23d
Installing wfc dependencies
spetersenms Jan 22, 2026
c557dd4
error handling
spetersenms Jan 22, 2026
8985267
Additional dependency stuff
spetersenms Jan 22, 2026
6fe02a9
trying again
spetersenms Jan 22, 2026
fbf101d
Write log temp implementation
spetersenms Jan 23, 2026
c76d7ee
Disable SSL
spetersenms Jan 23, 2026
b4fec54
Building url correctly
spetersenms Jan 23, 2026
904153f
Fixes
spetersenms Jan 23, 2026
4bcee77
Supporting Covertura output format.
spetersenms Jan 23, 2026
9d5d8a3
Syntax fix
spetersenms Jan 23, 2026
bb351c3
Fixed parsing issue
spetersenms Jan 23, 2026
6bc1026
Getting all line data
spetersenms Jan 23, 2026
7cc0235
CC visualizer
spetersenms Jan 23, 2026
d1bcd1c
Path fix
spetersenms Jan 23, 2026
72aa358
FIxed path and import issue
spetersenms Jan 23, 2026
babdf32
Fixed path
spetersenms Jan 26, 2026
c22a048
supporting other exporters
spetersenms Jan 26, 2026
3d0a40d
Fixed display bug in visualizer
spetersenms Jan 27, 2026
cc692bd
Use correct data for total lines
spetersenms Jan 27, 2026
f98686d
Importing new CC module
spetersenms Jan 29, 2026
fc12fd2
Fix
spetersenms Jan 30, 2026
c7dc6fc
Build artifact folder as global var.
spetersenms Jan 30, 2026
4dd313c
Improved source and report generation
spetersenms Jan 30, 2026
3ea7df4
Better error handling
spetersenms Feb 2, 2026
840ab9a
Better handling of xml method tags
spetersenms Feb 2, 2026
c3eb8c0
Correctly handle empty xml tags
spetersenms Feb 2, 2026
9406cfa
Restructure output markdown to be much more compact.
spetersenms Feb 2, 2026
e6b602f
Use workspace root to find source files
spetersenms Feb 5, 2026
bc89d87
Putting details in collapsable container.
spetersenms Feb 5, 2026
3827e30
Function moved to other file
spetersenms Feb 11, 2026
b229aaf
Better parsing
spetersenms Feb 11, 2026
a515576
Improvements to coverage calculations
spetersenms Feb 11, 2026
8dfa10e
Use collection type
spetersenms Feb 11, 2026
5218035
Re-structured test runner.
spetersenms Feb 23, 2026
93594fa
Using global to access PSVersionTable in class
spetersenms Feb 23, 2026
a6b22c9
Count correct total lines
spetersenms Feb 25, 2026
d2c7a15
Use correct list of app roots per project for CC
spetersenms Feb 25, 2026
f0064ef
Action for CC summary for all projects
spetersenms Feb 25, 2026
a1ad6dc
Pass dependencies json to RunALPipeline to use with CC
spetersenms Feb 25, 2026
189afb3
CC merge summary job
spetersenms Feb 25, 2026
70e6fa1
Correctly run all test apps
spetersenms Feb 26, 2026
a217bd5
Made CC conditional
spetersenms Feb 26, 2026
39d5024
Use file indexes
spetersenms Feb 27, 2026
b6cf566
Documentation and release notes.
spetersenms Feb 27, 2026
01ea3e5
Run CC merge with incomplete data
spetersenms Feb 27, 2026
77694c3
Added more cases for non executable lines
spetersenms Mar 2, 2026
ca950af
Use correct unicode dashes
spetersenms Mar 2, 2026
dd1db07
New settings for Code Coverage
spetersenms Mar 5, 2026
a799705
Handling new settings object correctly
spetersenms Mar 5, 2026
94eefa7
Initializing list to work with strict mode
spetersenms Mar 5, 2026
30d32b9
Rename .Modules/CodeCoverage to .Modules/TestRunner
spetersenms Mar 11, 2026
79043b3
Add enableCodeCoverage and codeCoverageSetup settings definitions
spetersenms Mar 11, 2026
170fa30
Fix missing TestPage parameter in Run-NextTest function
spetersenms Mar 11, 2026
3275c96
Fix undefined variable and broken string in Print-TestResults
spetersenms Mar 11, 2026
b2182d7
Use OutputWarning instead of raw Write-Host for warnings
spetersenms Mar 11, 2026
af5fedc
Fix line number sorting in CoberturaMerger
spetersenms Mar 11, 2026
e1574c6
Add null check for projectDeps after JSON parsing
spetersenms Mar 11, 2026
8ac7615
Add README for MergeCoverageSummaries action
spetersenms Mar 11, 2026
39ff9bd
Add error handling for XML file reads in CoberturaMerger
spetersenms Mar 11, 2026
6540604
Remove unused mergeStats variable
spetersenms Mar 11, 2026
d8d703c
Fix typo: oututFile -> outputFile
spetersenms Mar 11, 2026
eddceb0
Add test infrastructure and BCCoverageParser tests (WIP)
spetersenms Mar 11, 2026
c553f1d
Remove temporary debug script
spetersenms Mar 11, 2026
1e3d87f
Additional test data and tests.
spetersenms Mar 11, 2026
da5f0ab
Removed how it works section
spetersenms Mar 11, 2026
deeb6d2
Cleanup
spetersenms Mar 11, 2026
1d8a4a8
Merge branch 'main' into CodeCoverage
spetersenms Mar 11, 2026
81603f3
Fixing CalculateArtifactsName test.
spetersenms Mar 12, 2026
9a82e24
Merge branch 'CodeCoverage' of github.com:spetersenms/AL-Go into Code…
spetersenms Mar 12, 2026
57dfa36
Add MergeCoverage job to PostProcess needs in ModifyBuildWorkflows
spetersenms Mar 12, 2026
fceb252
Added coverage processor tests
spetersenms Mar 12, 2026
223f6d4
Consistent decimal handling
spetersenms Mar 12, 2026
8e1a71b
Cobertura formatter and merger tests
spetersenms Mar 12, 2026
d5aa70d
Covererage report action tests
spetersenms Mar 12, 2026
a6fc0d5
Pre-commit
spetersenms Mar 12, 2026
fab2ba2
exclude intentional invalid test xml file
spetersenms Mar 12, 2026
69113fd
Run CC tests in CI
spetersenms Mar 20, 2026
cf2ced8
Tests for summary logic
spetersenms Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ jobs:
run: |
. (Join-Path "." "Tests/runtests.ps1") -Path "Tests"

- name: Test Code Coverage Modules (${{ matrix.psVersion }})
run: |
. (Join-Path "." "Tests/runtests.ps1") -Path "Tests/CodeCoverage"

- name: Test AL-Go Workflows (${{ matrix.psVersion }})
if: github.repository_owner == 'microsoft'
run: |
Expand Down
59 changes: 30 additions & 29 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks

repos:
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.21
hooks:
- id: mdformat
args: [--end-of-line=keep]

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: check-merge-conflict
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: sort-simple-yaml

- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
hooks:
- id: gitleaks
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks

repos:
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.21
hooks:
- id: mdformat
args: [--end-of-line=keep]

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-json
- id: check-xml
exclude: 'Tests/CodeCoverage/TestData/CoberturaFiles/cobertura-malformed\.xml$'
- id: check-yaml
- id: check-merge-conflict
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: sort-simple-yaml

- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
hooks:
- id: gitleaks
6 changes: 6 additions & 0 deletions Actions/.Modules/ReadSettings.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ function GetDefaultSettings
"doNotRunTests" = $false
"doNotRunBcptTests" = $false
"doNotRunPageScriptingTests" = $false
"enableCodeCoverage" = $false
"codeCoverageSetup" = [ordered]@{
"trackingType" = "PerRun"
"produceCodeCoverageMap" = "PerCodeunit"
"excludeFilesPattern" = @()
}
"doNotPublishApps" = $false
"doNotSignApps" = $false
"configPackages" = @()
Expand Down
266 changes: 266 additions & 0 deletions Actions/.Modules/TestRunner/ALTestRunner.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
function Run-AlTests
(
[string] $TestSuite = $script:DefaultTestSuite,
[string] $TestCodeunitsRange = "",
[string] $TestProcedureRange = "",
[string] $ExtensionId = "",
[ValidateSet('None','Disabled','Codeunit','Function')]
[string] $RequiredTestIsolation = "None",
[ValidateSet('','None','UnitTest','IntegrationTest','Uncategorized','AITest')]
[string] $TestType = "",
[ValidateSet("Disabled", "Codeunit")]
[string] $TestIsolation = "Codeunit",
[ValidateSet('Windows','NavUserPassword','AAD')]
[string] $AutorizationType = $script:DefaultAuthorizationType,
[string] $TestPage = $global:DefaultTestPage,
[switch] $DisableSSLVerification,
[Parameter(Mandatory=$true)]
[string] $ServiceUrl,
[Parameter(Mandatory=$false)]
[pscredential] $Credential,
[array] $DisabledTests = @(),
[bool] $Detailed = $true,
[ValidateSet('no','error','warning')]
[string] $AzureDevOps = 'no',
[bool] $SaveResultFile = $true,
[string] $ResultsFilePath = "$PSScriptRoot\TestResults.xml",
[ValidateSet('XUnit','JUnit')]
[string] $ResultsFormat = 'JUnit',
[string] $AppName = '',
[ValidateSet('Disabled', 'PerRun', 'PerCodeunit', 'PerTest')]
[string] $CodeCoverageTrackingType = 'Disabled',
[ValidateSet('Disabled','PerCodeunit','PerTest')]
[string] $ProduceCodeCoverageMap = 'Disabled',
[string] $CodeCoverageOutputPath = "$PSScriptRoot\CodeCoverage",
[string] $CodeCoverageExporterId = $script:DefaultCodeCoverageExporter,
[switch] $CodeCoverageTrackAllSessions,
[string] $CodeCoverageFilePrefix = ("TestCoverageMap_" + (get-date -Format 'yyyyMMdd')),
[bool] $StabilityRun
)
{
$testRunArguments = @{
TestSuite = $TestSuite
TestCodeunitsRange = $TestCodeunitsRange
TestProcedureRange = $TestProcedureRange
ExtensionId = $ExtensionId
RequiredTestIsolation = $RequiredTestIsolation
TestType = $TestType
TestRunnerId = (Get-TestRunnerId -TestIsolation $TestIsolation)
CodeCoverageTrackingType = $CodeCoverageTrackingType
ProduceCodeCoverageMap = $ProduceCodeCoverageMap
CodeCoverageOutputPath = $CodeCoverageOutputPath
CodeCoverageFilePrefix = $CodeCoverageFilePrefix
CodeCoverageExporterId = $CodeCoverageExporterId
AutorizationType = $AutorizationType
TestPage = $TestPage
DisableSSLVerification = $DisableSSLVerification
ServiceUrl = $ServiceUrl
Credential = $Credential
DisabledTests = $DisabledTests
Detailed = $Detailed
StabilityRun = $StabilityRun
}

[array]$testRunResult = Run-AlTestsInternal @testRunArguments

if($SaveResultFile -and $testRunResult)
{
# Import the formatter module
$formatterPath = Join-Path $PSScriptRoot "TestResultFormatter.psm1"
Import-Module $formatterPath -Force

Save-TestResults -TestRunResultObject $testRunResult -ResultsFilePath $ResultsFilePath -Format $ResultsFormat -ExtensionId $ExtensionId -AppName $AppName
}
elseif ($SaveResultFile -and -not $testRunResult) {
Write-Host "Warning: No test results to save - tests may not have run"
}

if($AzureDevOps -ne 'no' -and $testRunResult)
{
Report-ErrorsInAzureDevOps -AzureDevOps $AzureDevOps -TestRunResultObject $testRunResult
}
}

function Invoke-ALTestResultVerification
(
[string] $TestResultsFolder = $(throw "Missing argument TestResultsFolder"),
[switch] $IgnoreErrorIfNoTestsExecuted
)
{
$failedTestList = Get-FailedTestsFromXMLFiles -TestResultsFolder $TestResultsFolder

if($failedTestList.Count -gt 0)
{
$testsExecuted = $true;
Write-Log "Failed tests:"
$testsFailed = ""
foreach($failedTest in $failedTestList)
{
$testsFailed += "Name: " + $failedTest.name + [environment]::NewLine
$testsFailed += "Method: " + $failedTest.method + [environment]::NewLine
$testsFailed += "Time: " + $failedTest.time + [environment]::NewLine
$testsFailed += "Message: " + [environment]::NewLine + $failedTest.message + [environment]::NewLine
$testsFailed += "StackTrace: "+ [environment]::NewLine + $failedTest.stackTrace + [environment]::NewLine + [environment]::NewLine
}

Write-Log $testsFailed
throw "Test execution failed due to the failing tests, see the list of the failed tests above."
}

if(-not $testsExecuted)
{
[array]$testResultFiles = Get-ChildItem -Path $TestResultsFolder -Filter "*.xml" | Foreach { "$($_.FullName)" }

foreach($resultFile in $testResultFiles)
{
[xml]$xmlDoc = Get-Content "$resultFile"
[array]$otherTests = $xmlDoc.assemblies.assembly.collection.ChildNodes | Where-Object {$_.result -ne 'Fail'}
if($otherTests.Length -gt 0)
{
return;
}

}

if (-not $IgnoreErrorIfNoTestsExecuted) {
throw "No test codeunits were executed"
}
}
}

function Get-FailedTestsFromXMLFiles
(
[string] $TestResultsFolder = $(throw "Missing argument TestResultsFolder")
)
{
$failedTestList = New-Object System.Collections.ArrayList
$testsExecuted = $false
[array]$testResultFiles = Get-ChildItem -Path $TestResultsFolder -Filter "*.xml" | Foreach { "$($_.FullName)" }

if($testResultFiles.Length -eq 0)
{
throw "No test results were found"
}

foreach($resultFile in $testResultFiles)
{
[xml]$xmlDoc = Get-Content "$resultFile"
[array]$failedTests = $xmlDoc.assemblies.assembly.collection.ChildNodes | Where-Object {$_.result -eq 'Fail'}
if($failedTests)
{
$testsExecuted = $true
foreach($failedTest in $failedTests)
{
$failedTestObject = @{
codeunitID = [int]($failedTest.ParentNode.ParentNode.'x-code-unit');
codeunitName = $failedTest.name;
method = $failedTest.method;
time = $failedTest.time;
message = $failedTest.failure.message;
stackTrace = $failedTest.failure.'stack-trace';
}

$failedTestList.Add($failedTestObject) > $null
}
}
}

return $failedTestList
}

function Write-DisabledTestsJson
(
$FailedTests,
[string] $OutputFolder = $(throw "Missing argument OutputFolder"),
[string] $FileName = 'DisabledTests.json'
)
{
$testsToDisable = New-Object -TypeName "System.Collections.ArrayList"
foreach($failedTest in $failedTests)
{
$test = @{
codeunitID = $failedTest.codeunitID;
codeunitName = $failedTest.name;
method = $failedTest.method;
}

$testsToDisable.Add($test)
}

$outputFile = Join-Path $OutputFolder $FileName
if(-not (Test-Path $outputFolder))
{
New-Item -Path $outputFolder -ItemType Directory
}

Add-Content -Value (ConvertTo-Json $testsToDisable) -Path $outputFile
}

function Report-ErrorsInAzureDevOps
(
[ValidateSet('no','error','warning')]
[string] $AzureDevOps = 'no',
$TestRunResultObject
)
{
if ($AzureDevOps -eq 'no')
{
return
}

$failedCodeunits = $TestRunResultObject | Where-Object { $_.result -eq $script:FailureTestResultType }
$failedTests = $failedCodeunits.testResults | Where-Object { $_.result -eq $script:FailureTestResultType }

foreach($failedTest in $failedTests)
{
$methodName = $failedTest.method;
$errorMessage = $failedTests.message
Write-Host "##vso[task.logissue type=$AzureDevOps;sourcepath=$methodName;]$errorMessage"
}
}

function Get-DisabledAlTests
(
[string] $DisabledTestsPath
)
{
$DisabledTests = @()
if(Test-Path $DisabledTestsPath)
{
$DisabledTests = Get-Content $DisabledTestsPath | ConvertFrom-Json
}

return $DisabledTests
}

function Get-TestRunnerId
(
[ValidateSet("Disabled", "Codeunit")]
[string] $TestIsolation = "Codeunit"
)
{
switch($TestIsolation)
{
"Codeunit"
{
return Get-CodeunitTestIsolationTestRunnerId
}
"Disabled"
{
return Get-DisabledTestIsolationTestRunnerId
}
}
}

function Get-DisabledTestIsolationTestRunnerId()
{
return $global:TestRunnerIsolationDisabled
}

function Get-CodeunitTestIsolationTestRunnerId()
{
return $global:TestRunnerIsolationCodeunit
}

. "$PSScriptRoot\Internal\Constants.ps1"
Import-Module "$PSScriptRoot\Internal\ALTestRunnerInternal.psm1"
Loading