Skip to content

Commit 655dc39

Browse files
Updated logic to build and run the unit test projects. (#297)
* Fix for unit test project run. - Build all the cs projects under the Repo. - Install the nuget packages. - Run the unit test projects with code coverage. * Implemeneted logic to restore nuget packages at projects root level. * Fixed issue with cdsproj with plugin name containing . symbol. * Path not found issue fix * Fix for file names with spaces. * Removed unused function to determin test projects. * Added comments * Added comments * Added validation to handle invalid sdk step id retrieval. * Modified the logic to build cs projects only if they are under Solution folder (i.e., Repo/Solution). * Adding ability to store snk in variable group * Removing snk * Adding back snk creation from variable * Fix for checking if snk exists * Cleaning Up Comments and Logging * Fix check for empty variable * Added verbose and validations while reading snk file. * Prinitng snk content to troubleshoot * Corrected typos. --------- Co-authored-by: Mike! <mike@mikefactorial.onmicrosoft.com>
1 parent 6deaea9 commit 655dc39

5 files changed

+221
-87
lines changed

Pipelines/Templates/build-Solution.yml

+12-18
Original file line numberDiff line numberDiff line change
@@ -187,44 +187,38 @@ steps:
187187
displayName: 'Validate Flows'
188188
condition: and(succeeded(), ne(variables['TriggerFlowValidation'],''), eq(variables['TriggerFlowValidation'],'true'))
189189

190+
# Add code first projects(csproj,pcfproj) to cdsproj
190191
- pwsh: |
191192
. "$env:POWERSHELLPATH/code-first-functions.ps1"
192193
Invoke-Check-Code-First-Components '$(Build.SourcesDirectory)' '$(RepoName)' '${{parameters.solutionName}}'
193194
displayName: 'Check codefirst components existence'
194195

195196
- pwsh: |
196197
. "$env:POWERSHELLPATH/code-first-functions.ps1"
197-
Add-Codefirst-Projects-To-Cdsproj '$(Build.SourcesDirectory)' '$(RepoName)' '${{parameters.solutionName}}' '$(pacPath)'
198+
Add-Codefirst-Projects-To-Cdsproj '$(Build.SourcesDirectory)' '$(RepoName)' '${{parameters.solutionName}}' '$(pacPath)' '$(PluginSNK)'
198199
displayName: 'Add project references to cdsproject'
199200

201+
# Runs NPM Install against all PCF projects
200202
- pwsh: |
201203
. "$env:POWERSHELLPATH/code-first-functions.ps1"
202204
Invoke-Pcf-Projects-Install-Npm '$(Build.SourcesDirectory)' '$(RepoName)'
203205
displayName: 'Install npm - pcf projects'
204206

207+
# Build Cdsproj file which generates the solution zip files
205208
- task: VSBuild@1
206209
inputs:
207-
#solution: '$(Build.SourcesDirectory)\$(RepoName)\${{parameters.solutionName}}\SolutionPackage\${{parameters.solutionName}}\${{parameters.solutionName}}.cdsproj'
208210
solution: '$(Build.SourcesDirectory)\$(RepoName)\${{parameters.solutionName}}\SolutionPackage\${{parameters.solutionName}}.cdsproj'
209-
msbuildArgs: '/t:build /restore'
210-
#platform: '$(buildPlatform)'
211-
#configuration: '$(buildConfiguration)'
211+
msbuildArgs: '/t:build /restore'
212212
displayName: 'Build cdsproject'
213213

214-
- task: VSTest@2
215-
inputs:
216-
platform: 'Any CPU'
217-
configuration: 'Release'
218-
codeCoverageEnabled: True
219-
resultsFolder: '$(build.ArtifactStagingDirectory)\Test\Results'
220-
searchFolder: '$(Build.SourcesDirectory)\$(RepoName)\${{parameters.solutionName}}'
221-
testAssemblyVer2: |
222-
**\*test*.dll
223-
!**\*TestAdapter.dll
224-
!**\obj\**
225-
condition: and(succeeded(), eq(variables.pluginstestexists, 'true'))
226-
displayName: 'Run unit test case projects'
214+
# Run the unit test cases
215+
- template: build-run-unit-test-projects.yml
216+
parameters:
217+
buildSourceDirectory: '$(Build.SourcesDirectory)'
218+
repo: '$(RepoName)'
219+
solutionName: '${{parameters.solutionName}}'
227220

221+
# Verbose purpose to make sure cdsproj buiild generated required files
228222
- pwsh: |
229223
$sourceDirectory = "$(Build.SourcesDirectory)\$(RepoName)\${{parameters.solutionName}}\SolutionPackage\bin\Debug\*"
230224
Get-ChildItem "$sourceDirectory" -Recurse
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
parameters:
2+
- name: buildSourceDirectory
3+
type: string
4+
- name: repo
5+
type: string
6+
- name: solutionName
7+
type: string
8+
9+
steps:
10+
- task: NuGetToolInstaller@1
11+
displayName: 'Install NuGet'
12+
13+
- task: PowerShell@2
14+
inputs:
15+
targetType: 'inline'
16+
script: |
17+
$repoPath = "${{parameters.buildSourceDirectory}}\${{parameters.repo}}\${{parameters.solutionName}}"
18+
$csharpProjects = Get-ChildItem -Path "$repoPath" -Filter "*.csproj" -Recurse
19+
if ($csharpProjects) {
20+
Write-Host "##vso[task.setvariable variable=HasCSharpProjects]true"
21+
Write-Host "C# projects found in the Repo."
22+
} else {
23+
Write-Host "##vso[task.setvariable variable=HasCSharpProjects]false"
24+
Write-Host "No C# projects found in the Repo."
25+
}
26+
displayName: 'Check the presence of CS projects'
27+
28+
# CS Project might be referring NuGet packages. If a NuGet package referred in a CS Project a 'HintPath' node will be added.
29+
# Example <HintPath>..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll</HintPath>
30+
# 'HintPath' pattern will be different for each project template (i.e., Class Library vs Unit test project)
31+
# This function removes '..\' references so that NuGet packages will always be restored at project root folder level
32+
- powershell: |
33+
. "$env:POWERSHELLPATH/flow-xml-validation.ps1"
34+
Remove-Relative-References-from-HintPath '${{parameters.buildSourceDirectory}}' '${{parameters.repo}}' '${{parameters.solutionName}}'
35+
displayName: 'Remove Relative References from Packages HintPath'
36+
condition: and(succeeded(),eq(variables['HasCSharpProjects'], 'true'))
37+
38+
# Nuget packages must be restored at csproj\packages folder and csproj's parent folder
39+
# Structure of Csharp Projects must be Repo/parentfolder/csprojects
40+
- powershell: |
41+
$repoPath = "${{parameters.buildSourceDirectory}}\${{parameters.repo}}\${{parameters.solutionName}}"
42+
$projects = Get-ChildItem -Path "$repoPath" -Filter '*.csproj' -Recurse
43+
foreach ($project in $projects) {
44+
$projectPath = $project.FullName
45+
$projectDirectory = Split-Path -Path $projectPath -Parent
46+
$restoreDirectory = Join-Path -Path $projectDirectory -ChildPath 'packages'
47+
$configFile = Join-Path -Path $projectDirectory -ChildPath 'packages.config'
48+
49+
Write-Host "Restoring NuGet packages for project - $projectPath"
50+
51+
& nuget.exe restore $configFile -PackagesDirectory $restoreDirectory
52+
}
53+
displayName: 'Restore NuGet packages to run unit test cases'
54+
condition: and(succeeded(),eq(variables['HasCSharpProjects'], 'true'))
55+
56+
- task: VSBuild@1
57+
displayName: 'Build cs projects'
58+
inputs:
59+
solution: '**/*.csproj'
60+
msbuildArgs: '/t:build /restore'
61+
condition: and(succeeded(),eq(variables['HasCSharpProjects'], 'true'))
62+
63+
# Checks if 'Run Settings' file available in the Repo.
64+
- task: PowerShell@2
65+
displayName: 'Check for RunSettings file in the Repo'
66+
inputs:
67+
targetType: 'inline'
68+
script: |
69+
$repoPath = "${{parameters.buildSourceDirectory}}\${{parameters.repo}}\${{parameters.solutionName}}"
70+
$runSettingsFiles = Get-ChildItem -Path "$repoPath" -Recurse -Filter "*.runsettings"
71+
72+
if ($runSettingsFiles.Count -gt 0) {
73+
$runSettingsFilePath = $runSettingsFiles[0].FullName
74+
Write-Host "Run settings file found at $runSettingsFilePath"
75+
Write-Host "##vso[task.setvariable variable=RunSettingsFilePath]$runSettingsFilePath"
76+
} else {
77+
Write-Host "No run settings file found in the repository."
78+
}
79+
condition: and(succeeded(),eq(variables['HasCSharpProjects'], 'true'))
80+
81+
# Run unit test with no 'Run Settings' file
82+
- task: VSTest@2
83+
displayName: 'VSTest without Run Settings file'
84+
inputs:
85+
testSelector: 'testAssemblies'
86+
testAssemblyVer2: |
87+
**/*Tests.dll
88+
!**\*TestAdapter.dll
89+
!**\obj\**
90+
searchFolder: '${{parameters.buildSourceDirectory}}\${{parameters.repo}}\${{parameters.solutionName}}'
91+
platform: 'Any CPU'
92+
codeCoverageEnabled: true
93+
testRunTitle: 'Unit Test Execution with no Run Settings file'
94+
condition: and(succeeded(),eq(variables['HasCSharpProjects'], 'true'), eq(variables['RunSettingsFilePath'], ''))
95+
96+
# Run unit test with 'Run Settings' file
97+
- task: VSTest@2
98+
displayName: 'VSTest with Run Settings file'
99+
inputs:
100+
testSelector: 'testAssemblies'
101+
testAssemblyVer2: |
102+
**/*Tests.dll
103+
!**\*TestAdapter.dll
104+
!**\obj\**
105+
searchFolder: '${{parameters.buildSourceDirectory}}\${{parameters.repo}}\${{parameters.solutionName}}'
106+
platform: 'Any CPU'
107+
codeCoverageEnabled: true
108+
testRunTitle: 'Unit Test Execution with runsettings file'
109+
runSettingsFile: '$(RunSettingsFilePath)'
110+
condition: and(succeeded(),eq(variables['HasCSharpProjects'], 'true'), ne(variables['RunSettingsFilePath'], ''))

PowerShell/code-first-functions.ps1

+37-58
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ function Add-Codefirst-Projects-To-Cdsproj{
9292
[Parameter(Mandatory)] [String]$buildSourceDirectory,
9393
[Parameter(Mandatory)] [String]$repo,
9494
[Parameter(Mandatory)] [String]$solutionName,
95-
[Parameter(Mandatory)] [String]$pacPath
95+
[Parameter(Mandatory)] [String]$pacPath,
96+
[Parameter()] [String]$base64Snk
9697
)
9798
if (-not ([string]::IsNullOrEmpty($pacPath)) -and (Test-Path "$pacPath\pac.exe"))
9899
{
@@ -118,8 +119,7 @@ function Add-Codefirst-Projects-To-Cdsproj{
118119
foreach($pcfProj in $pcfProjectFiles)
119120
{
120121
Write-Host "Adding Reference of Pcf Project - " $pcfProj.FullName
121-
$pcfProjectPath = "`"$($pcfProj.FullName)`""
122-
122+
$pcfProjectPath = '"' + $($pcfProj.FullName) + '"'
123123
$addReferenceCommand = "solution add-reference --path $pcfProjectPath"
124124
Write-Host "Add Reference Command - $addReferenceCommand"
125125
Invoke-Expression -Command "$pacexepath $addReferenceCommand"
@@ -131,30 +131,47 @@ function Add-Codefirst-Projects-To-Cdsproj{
131131
if(Test-Path "$unpackedPluginAssemblyPath"){
132132
# Get all .csproj files under Repo/Commited Solution folder
133133
$csProjectFiles = Get-ChildItem -Path "$buildSourceDirectory\$repo\$solutionName" -Filter *.csproj -Recurse
134-
foreach($csProject in $csProjectFiles)
134+
Write-Host "$($csProjectFiles.Count) cs project files found"
135+
# Filter out projects name ending with "Tests.csproj" (i.e.,Unit test projects)
136+
$filteredProjects = $csProjectFiles | Where-Object { $_.Name -notlike "*Tests.csproj" }
137+
Write-Host "$($filteredProjects.Count) plugin project files found after filtering out unit test projects"
138+
foreach($csProject in $filteredProjects)
135139
{
136140
Write-Host "Adding Reference of Plugin Project - " $csProject.FullName
137-
# Add only Plugin type csproj; Skip others
138-
$csProjectPath = "`"$($csProject.FullName)`""
139-
140-
# Read csproj xml to determin project type
141-
[xml]$xmlDoc = Get-Content -Path $csProjectPath
142-
$tagPowerAppsTargetsPath = $xmlDoc.Project.PropertyGroup.PowerAppsTargetsPath
143-
144-
# 'PowerAppsTargetsPath' tag is only availble in plugin project generate via 'pac plugin init'
145-
if(-not [string]::IsNullOrWhiteSpace($tagPowerAppsTargetsPath)){
146-
$addReferenceCommand = "solution add-reference --path $csProjectPath"
147-
Write-Host "Add Reference Command - $addReferenceCommand"
148-
Invoke-Expression -Command "$pacexepath $addReferenceCommand"
149-
}
150-
else{
151-
Write-Host "Not a plug-in project; Skipping add reference to cdsproj; Path - $csProjectPath"
141+
$csProjectPath = '"' + $($csProject.FullName) + '"'
142+
143+
# Read csproj file's AssemblyOriginatorKeyFile and SignAssembly properties.
144+
# We need to read these properties to determine whether the C# project is signed.
145+
[xml]$xmlDoc = Get-Content -Path $($csProject.FullName)
146+
$snkFileName = $xmlDoc.Project.PropertyGroup.AssemblyOriginatorKeyFile
147+
$signAssembly = $xmlDoc.Project.PropertyGroup.SignAssembly
148+
Write-Host "SNKFileName - $snkFileName"
149+
Write-Host "SignAssembly - $signAssembly"
150+
# Check for existing snk file or pull from global variables
151+
if($signAssembly -eq "true") {
152+
$projectDirectory = [System.IO.Path]::GetDirectoryName("$csProject.FullName")
153+
Write-Host "SNK Path: $projectDirectory\$snkFileName"
154+
if(!(Test-Path "$projectDirectory\$snkFileName")) {
155+
if(!($base64Snk.Contains('$('))) {
156+
Write-Host "Writing plugin snk file to disk"
157+
$bytes = [Convert]::FromBase64String($base64Snk)
158+
[IO.File]::WriteAllBytes("$projectDirectory\$snkFileName", $bytes)
159+
}else{
160+
Write-Host "No snk found at repo and no snk content defined in variables"
161+
}
162+
}else{
163+
Write-Host ".snk file - $snkFileName already presents in the repo. No need to read from variable"
164+
}
152165
}
166+
167+
$addReferenceCommand = "solution add-reference --path $csProjectPath"
168+
Write-Host "Add Reference Command - $addReferenceCommand"
169+
Invoke-Expression -Command "$pacexepath $addReferenceCommand"
153170
}
154171
}
155172
else
156173
{
157-
Write-Host "PluginAssemblies folder unavailble in unpacked solution"
174+
Write-Host "PluginAssemblies folder unavailable in unpacked solution"
158175
}
159176
}
160177
else
@@ -501,42 +518,4 @@ function Invoke-Append-Version-To-Solutions{
501518
{
502519
Write-Host "Unmanaged solution is unavailble at unmanagedSolutionPath"
503520
}
504-
}
505-
506-
<#
507-
This function checks the existence of unit test projects under the repository.
508-
This function is needed to run the plugin test projects.
509-
#>
510-
function Invoke-Check-Test-Projects{
511-
param (
512-
[Parameter(Mandatory)] [String]$buildSourceDirectory,
513-
[Parameter(Mandatory)] [String]$repo,
514-
[Parameter(Mandatory)] [String]$solutionName
515-
)
516-
517-
$testProjectsPath = "$buildSourceDirectory\$repo\$solutionName\Test"
518-
If(Test-Path "$testProjectsPath")
519-
{
520-
csProjectFiles = Get-ChildItem -Path "$testProjectsPath" -Filter *.csproj -Recurse
521-
foreach($csProject in $csProjectFiles)
522-
{
523-
# Add only Plugin type csproj; Skip others
524-
$csProjectPath = $csProject.FullName
525-
526-
# Read csproj xml to determin project type
527-
[xml]$xmlDoc = Get-Content -Path $csProjectPath
528-
$testProjectType = $xmlDoc.Project.PropertyGroup.TestProjectType
529-
530-
# 'TestProjectType' tag is available only to Test projects
531-
if(-not [string]::IsNullOrWhiteSpace($testProjectType)){
532-
Write-Host "Test projects exist in the Repo - $csProjectPath"
533-
Write-Host "##vso[task.setvariable variable=pluginstestexists;]$true"
534-
break
535-
}
536-
}
537-
}
538-
else
539-
{
540-
Write-Host "Test projects not exist under $testProjectsPath"
541-
}
542521
}

PowerShell/flow-xml-validation.ps1

+46
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,50 @@ function Parse-Validate-Flow-Json-File($jsonObject) {
8181
}
8282

8383
return $false
84+
}
85+
86+
# CS Project might be referring NuGet packages. If a NuGet package referred in a CS Project a 'HintPath' node will be added.
87+
# Example <HintPath>..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll</HintPath>
88+
# 'HintPath' pattern will be different for each project template (i.e., Class Library vs Unit test project)
89+
# This function removes '..\' references so that NuGet packages will always be restored at project root folder level
90+
function Remove-Relative-References-from-HintPath{
91+
param (
92+
[Parameter(Mandatory)] [String]$buildSourceDirectory,
93+
[Parameter(Mandatory)] [String]$repo,
94+
[Parameter(Mandatory)] [String]$solutionName
95+
)
96+
97+
$repoPath = "$buildSourceDirectory\$repo\$solutionName"
98+
$projects = Get-ChildItem -Path "$repoPath" -Filter '*.csproj' -Recurse
99+
foreach ($project in $projects) {
100+
$csProjectPath = $project.FullName
101+
Write-Host "Processing $($project.Name)"
102+
# Load the XML file content
103+
$xmlContent = Get-Content -Path $csProjectPath -Raw
104+
#Write-Host "Content before - "$xmlContent
105+
106+
# Load the XML content
107+
$xml = [xml]$xmlContent
108+
109+
# Call the function with the root node
110+
RemoveRelativeReferences $xml.DocumentElement
111+
112+
# Save the modified XML content back to the file
113+
$xml.Save($csProjectPath)
114+
#$xmlContent = Get-Content -Path $csProjectPath -Raw
115+
#Write-Host "Content after - "$xmlContent
116+
}
117+
}
118+
119+
# This is a subfunction of Remove-Relative-References-from-HintPath
120+
# Fetches all the occurances of HintPath node
121+
function RemoveRelativeReferences($node) {
122+
if ($node -is [System.Xml.XmlElement]) {
123+
if ($node.Name -eq "HintPath") {
124+
$node.InnerText = $node.InnerText -replace "\.\.\\", ""
125+
}
126+
foreach ($childNode in $node.ChildNodes) {
127+
RemoveRelativeReferences $childNode
128+
}
129+
}
84130
}

PowerShell/update-deployment-settings.ps1

+16-11
Original file line numberDiff line numberDiff line change
@@ -147,18 +147,23 @@ function Set-DeploymentSettingsConfiguration
147147
elseif($configurationVariableName.StartsWith("sdkstep.", "CurrentCultureIgnoreCase")) {
148148
if(-not [string]::IsNullOrWhiteSpace($configurationVariableValue))
149149
{
150-
# SDK step configuration format will be "sdkstep.{unsec/sec}.{sdkmessageprocessingstep}"
151-
$sdkmessageprocessingstepid = $configurationVariableName -replace "sdkstep.unsec.", "" -replace "sdkstep.sec.", ""
152-
Write-Host "Sdkmessageprocessingstepid - $sdkmessageprocessingstepid"
153-
$configKey = $configurationVariableName -replace "sdkstep.", ""
154-
$sdkmessageprocessingstepRecord = Get-CrmRecord -conn $conn -EntityLogicalName "sdkmessageprocessingstep" -Id "$sdkmessageprocessingstepid" -Fields sdkmessageprocessingstepid
155-
if($null -ne $sdkmessageprocessingstepRecord){
156-
$sdkConfig = [PSCustomObject]@{"Config"="$configKey"; "Value"="#{$configurationVariableName}#"}
157-
if($usePlaceholders.ToLower() -eq 'false' -or $isDevEnvironment) {
158-
$sdkConfig = [PSCustomObject]@{"Config"="$configKey"; "Value"="$configurationVariableValue"}
159-
}
160-
$sdkMessages.Add($sdkConfig)
150+
try{
151+
# SDK step configuration format will be "sdkstep.{unsec/sec}.{sdkmessageprocessingstep}"
152+
$sdkmessageprocessingstepid = $configurationVariableName -replace "sdkstep.unsec.", "" -replace "sdkstep.sec.", ""
153+
Write-Host "Sdkmessageprocessingstepid - $sdkmessageprocessingstepid"
154+
$configKey = $configurationVariableName -replace "sdkstep.", ""
155+
$sdkmessageprocessingstepRecord = Get-CrmRecord -conn $conn -EntityLogicalName "sdkmessageprocessingstep" -Id "$sdkmessageprocessingstepid" -Fields sdkmessageprocessingstepid
156+
if($null -ne $sdkmessageprocessingstepRecord){
157+
$sdkConfig = [PSCustomObject]@{"Config"="$configKey"; "Value"="#{$configurationVariableName}#"}
158+
if($usePlaceholders.ToLower() -eq 'false' -or $isDevEnvironment) {
159+
$sdkConfig = [PSCustomObject]@{"Config"="$configKey"; "Value"="$configurationVariableValue"}
160+
}
161+
$sdkMessages.Add($sdkConfig)
162+
}
161163
}
164+
catch {
165+
Write-Host "Error occurred while retrieving the SDK step - $($_.Exception.Message)"
166+
}
162167
}
163168
else{
164169
Write-Host "SDK Message Variable $configurationVariableName value is either Null or Empty for $environmentName"

0 commit comments

Comments
 (0)