mirror of
https://github.com/actions/runner-images-sangeeth.git
synced 2025-12-11 03:57:29 +00:00
Implement tests for software-report-module (#6815)
* Minor improvements * fix typos * fix brew rendering * add temp test * Implement tests * Add arguments validation * ToMarkdown() * Use before-All and helpers * Get rid of arrays * Add validation, no new nodes after header * Fix naming * add workflow * Revisit comments + tiny improvements * Fix tables * Fix html table indent * remove comment * attemp to break test - testing CI * revert breaking test * fix nitpicks
This commit is contained in:
25
.github/workflows/powershell-tests.yml
vendored
Normal file
25
.github/workflows/powershell-tests.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# CI Validation
|
||||
|
||||
name: PowerShell Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'helpers/software-report-base/**'
|
||||
|
||||
jobs:
|
||||
powershell-tests:
|
||||
name: PowerShell tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Software Report module tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
Invoke-Pester -Output Detailed "helpers/software-report-base/tests"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.Comparer.psm1
|
||||
using module ./SoftwareReport.DifferenceCalculator.psm1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
@@ -10,6 +10,10 @@ using module ./SoftwareReport.Comparer.psm1
|
||||
Path to the current software report.
|
||||
.PARAMETER OutputFile
|
||||
Path to the file where the difference will be saved.
|
||||
.PARAMETER ReleaseBranchName
|
||||
Name of the release branch to build image docs URL.
|
||||
.PARAMETER ReadmePath
|
||||
Path to the README file in repository to build image docs URL.
|
||||
#>
|
||||
|
||||
Param (
|
||||
@@ -20,7 +24,9 @@ Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $OutputFile,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string] $ImageDocsUrl
|
||||
[string] $ReleaseBranchName,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string] $ReadmePath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -44,12 +50,14 @@ function Read-SoftwareReport {
|
||||
$previousReport = Read-SoftwareReport -JsonReportPath $PreviousJsonReportPath
|
||||
$currentReport = Read-SoftwareReport -JsonReportPath $CurrentJsonReportPath
|
||||
|
||||
$comparer = [SoftwareReportComparer]::new($previousReport, $currentReport)
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($previousReport, $currentReport)
|
||||
$comparer.CompareReports()
|
||||
$diff = $comparer.GetMarkdownReport()
|
||||
|
||||
if ($ImageDocsUrl) {
|
||||
$diff += "`n`n`n For comprehensive list of software installed on this image please click [here]($ImageDocsUrl)."
|
||||
if ($ReleaseBranch -and $ReadmePath) {
|
||||
# https://github.com/actions/runner-images/blob/releases/macOS-12/20221215/images/macos/macos-12-Readme.md
|
||||
$ImageDocsUrl = "https://github.com/actions/runner-images/blob/${ReleaseBranchName}/${ReadmePath}"
|
||||
$diff += "`n`n`nFor comprehensive list of software installed on this image please click [here]($ImageDocsUrl)."
|
||||
}
|
||||
|
||||
$diff | Out-File -Path $OutputFile -Encoding utf8NoBOM
|
||||
@@ -8,6 +8,14 @@ class BaseNode {
|
||||
return $false
|
||||
}
|
||||
|
||||
[String] ToMarkdown() {
|
||||
return $this.ToMarkdown(1)
|
||||
}
|
||||
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
throw "Abtract method 'ToMarkdown(level)' is not implemented for '$($this.GetType().Name)'"
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
throw "Abtract method 'IsSimilarTo' is not implemented for '$($this.GetType().Name)'"
|
||||
}
|
||||
@@ -19,6 +27,7 @@ class BaseNode {
|
||||
|
||||
# Abstract base class for all nodes that describe a tool and should be rendered inside diff table
|
||||
class BaseToolNode: BaseNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $ToolName
|
||||
|
||||
BaseToolNode([String] $ToolName) {
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
|
||||
|
||||
# SoftwareReportComparer is used to calculate differences between two SoftwareReport objects
|
||||
class SoftwareReportComparer {
|
||||
hidden [SoftwareReport] $PreviousReport
|
||||
hidden [SoftwareReport] $CurrentReport
|
||||
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $AddedItems
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $ChangedItems
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $DeletedItems
|
||||
|
||||
SoftwareReportComparer([SoftwareReport] $PreviousReport, [SoftwareReport] $CurrentReport) {
|
||||
$this.PreviousReport = $PreviousReport
|
||||
$this.CurrentReport = $CurrentReport
|
||||
}
|
||||
|
||||
[void] CompareReports() {
|
||||
$this.AddedItems = @()
|
||||
$this.ChangedItems = @()
|
||||
$this.DeletedItems = @()
|
||||
|
||||
$this.CompareInternal($this.PreviousReport.Root, $this.CurrentReport.Root, @())
|
||||
}
|
||||
|
||||
hidden [void] CompareInternal([HeaderNode] $previousReportPointer, [HeaderNode] $currentReportPointer, [Array] $Headers) {
|
||||
$currentReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
|
||||
$currentReportNode = $_
|
||||
$sameNodeInPreviousReport = $previousReportPointer ? $previousReportPointer.FindSimilarChildNode($currentReportNode) : $null
|
||||
|
||||
if ($currentReportNode -is [HeaderNode]) {
|
||||
$this.CompareInternal($sameNodeInPreviousReport, $currentReportNode, $Headers + $currentReportNode.Title)
|
||||
} else {
|
||||
if ($sameNodeInPreviousReport -and ($currentReportNode.IsIdenticalTo($sameNodeInPreviousReport))) {
|
||||
# Nodes are identical, nothing changed, just ignore it
|
||||
} elseif ($sameNodeInPreviousReport) {
|
||||
# Nodes are equal but not identical, so something was changed
|
||||
if ($currentReportNode -is [TableNode]) {
|
||||
$this.CompareSimilarTableNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
|
||||
} elseif ($currentReportNode -is [ToolVersionsListNode]) {
|
||||
$this.CompareSimilarToolVersionsListNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
|
||||
} else {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers))
|
||||
}
|
||||
} else {
|
||||
# Node was not found in previous report, new node was added
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $currentReportNode, $Headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Detecting nodes that were removed
|
||||
$previousReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
|
||||
$previousReportNode = $_
|
||||
$sameNodeInCurrentReport = $currentReportPointer ? $currentReportPointer.FindSimilarChildNode($previousReportNode) : $null
|
||||
|
||||
if (-not $sameNodeInCurrentReport) {
|
||||
if ($previousReportNode -is [HeaderNode]) {
|
||||
$this.CompareInternal($previousReportNode, $null, $Headers + $previousReportNode.Title)
|
||||
} else {
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($previousReportNode, $null, $Headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hidden [void] CompareSimilarTableNodes([TableNode] $PreviousReportNode, [TableNode] $CurrentReportNode, [Array] $Headers) {
|
||||
$addedRows = $CurrentReportNode.Rows | Where-Object { $_ -notin $PreviousReportNode.Rows }
|
||||
$deletedRows = $PreviousReportNode.Rows | Where-Object { $_ -notin $CurrentReportNode.Rows }
|
||||
|
||||
if (($addedRows.Count -gt 0) -and ($deletedRows.Count -eq 0)) {
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
} elseif (($deletedRows.Count -gt 0) -and ($addedRows.Count -eq 0)) {
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
} else {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
}
|
||||
}
|
||||
|
||||
hidden [void] CompareSimilarToolVersionsListNodes([ToolVersionsListNode] $PreviousReportNode, [ToolVersionsListNode] $CurrentReportNode, [Array] $Headers) {
|
||||
$previousReportMajorVersions = $PreviousReportNode.Versions | ForEach-Object { $PreviousReportNode.ExtractMajorVersion($_) }
|
||||
$currentReportMajorVersion = $CurrentReportNode.Versions | ForEach-Object { $CurrentReportNode.ExtractMajorVersion($_) }
|
||||
|
||||
$addedVersions = $CurrentReportNode.Versions | Where-Object { $CurrentReportNode.ExtractMajorVersion($_) -notin $previousReportMajorVersions }
|
||||
$deletedVersions = $PreviousReportNode.Versions | Where-Object { $PreviousReportNode.ExtractMajorVersion($_) -notin $currentReportMajorVersion }
|
||||
$changedPreviousVersions = $PreviousReportNode.Versions | Where-Object { ($PreviousReportNode.ExtractMajorVersion($_) -in $currentReportMajorVersion) -and ($_ -notin $CurrentReportNode.Versions) }
|
||||
$changedCurrentVersions = $CurrentReportNode.Versions | Where-Object { ($CurrentReportNode.ExtractMajorVersion($_) -in $previousReportMajorVersions) -and ($_ -notin $PreviousReportNode.Versions) }
|
||||
|
||||
if ($addedVersions.Count -gt 0) {
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $addedVersions, $CurrentReportNode.MajorVersionRegex, $true), $Headers))
|
||||
}
|
||||
|
||||
if ($deletedVersions.Count -gt 0) {
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new([ToolVersionsListNode]::new($PreviousReportNode.ToolName, $deletedVersions, $PreviousReportNode.MajorVersionRegex, $true), $null, $Headers))
|
||||
}
|
||||
|
||||
$previousChangedNode = ($changedPreviousVersions.Count -gt 0) ? [ToolVersionsListNode]::new($PreviousReportNode.ToolName, $changedPreviousVersions, $PreviousReportNode.MajorVersionRegex, $true) : $null
|
||||
$currentChangedNode = ($changedCurrentVersions.Count -gt 0) ? [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $changedCurrentVersions, $CurrentReportNode.MajorVersionRegex, $true) : $null
|
||||
if ($previousChangedNode -and $currentChangedNode) {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($previousChangedNode, $currentChangedNode, $Headers))
|
||||
}
|
||||
}
|
||||
|
||||
[String] GetMarkdownReport() {
|
||||
$reporter = [SoftwareReportComparerReport]::new()
|
||||
$report = $reporter.GenerateMarkdownReport($this.CurrentReport, $this.PreviousReport, $this.AddedItems, $this.ChangedItems, $this.DeletedItems)
|
||||
return $report
|
||||
}
|
||||
|
||||
hidden [Boolean] FilterExcludedNodes([BaseNode] $Node) {
|
||||
# We shouldn't show "Image Version" diff because it is already shown in report header
|
||||
if (($Node -is [ToolVersionNode]) -and ($Node.ToolName -eq "Image Version:")) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
# SoftwareReportComparerReport is used to render results of SoftwareReportComparer in markdown format
|
||||
class SoftwareReportComparerReport {
|
||||
[String] GenerateMarkdownReport([SoftwareReport] $CurrentReport, [SoftwareReport] $PreviousReport, [ReportDifferenceItem[]] $AddedItems, [ReportDifferenceItem[]] $ChangedItems, [ReportDifferenceItem[]] $DeletedItems) {
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
|
||||
$rootNode = $CurrentReport.Root
|
||||
$imageVersion = $this.GetImageVersion($CurrentReport)
|
||||
$previousImageVersion = $this.GetImageVersion($PreviousReport)
|
||||
|
||||
#############################
|
||||
### Render report header ####
|
||||
#############################
|
||||
|
||||
$sb.AppendLine("# :desktop_computer: Actions Runner Image: $($rootNode.Title)")
|
||||
|
||||
# ToolNodes on root level contains main image description so just copy-paste them to final report
|
||||
$rootNode.Children | Where-Object { $_ -is [BaseToolNode] } | ForEach-Object {
|
||||
$sb.AppendLine($_.ToMarkdown(0))
|
||||
}
|
||||
$sb.AppendLine()
|
||||
|
||||
$sb.AppendLine("## :mega: What's changed?")
|
||||
|
||||
###########################
|
||||
### Render added items ####
|
||||
###########################
|
||||
|
||||
[ReportDifferenceItem[]] $addedItemsExcludeTables = $AddedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
if ($addedItemsExcludeTables.Count -gt 0) {
|
||||
$tableItems = $addedItemsExcludeTables | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
|
||||
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
$sb.AppendLine("### Added :heavy_plus_sign:")
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
|
||||
# Render added tables separately
|
||||
$AddedItems | Where-Object { $_.IsTableNode() } | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
|
||||
#############################
|
||||
### Render deleted items ####
|
||||
#############################
|
||||
|
||||
[ReportDifferenceItem[]] $deletedItemsExcludeTables = $DeletedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
if ($deletedItemsExcludeTables.Count -gt 0) {
|
||||
$tableItems = $deletedItemsExcludeTables | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.PreviousReportNode.ToolName);
|
||||
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
$sb.AppendLine("### Deleted :heavy_minus_sign:")
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
|
||||
# Render deleted tables separately
|
||||
$DeletedItems | Where-Object { $_.IsTableNode() } | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
|
||||
#############################
|
||||
### Render updated items ####
|
||||
#############################
|
||||
|
||||
[ReportDifferenceItem[]] $changedItemsExcludeTables = $ChangedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
if ($changedItemsExcludeTables.Count -gt 0) {
|
||||
$tableItems = $changedItemsExcludeTables | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
|
||||
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
|
||||
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
$sb.AppendLine("### Updated")
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
|
||||
# Render updated tables separately
|
||||
$ChangedItems | Where-Object { $_.IsTableNode() } | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[String] RenderHtmlTable([PSCustomObject[]] $Table, $RowSpanColumnName) {
|
||||
$headers = $Table[0].PSObject.Properties.Name
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine("<table>")
|
||||
$sb.AppendLine(" <thead>")
|
||||
$headers | ForEach-Object {
|
||||
$sb.AppendLine(" <th>$_</th>")
|
||||
}
|
||||
$sb.AppendLine(" </thead>")
|
||||
$sb.AppendLine(" <tbody>")
|
||||
|
||||
$tableRowSpans = $this.CalculateHtmlTableRowSpan($Table, $RowSpanColumnName)
|
||||
for ($rowIndex = 0; $rowIndex -lt $Table.Count; $rowIndex++) {
|
||||
$row = $Table[$rowIndex]
|
||||
|
||||
$sb.AppendLine(" <tr>")
|
||||
$headers | ForEach-Object {
|
||||
if ($_ -eq $RowSpanColumnName) {
|
||||
if ($tableRowSpans[$rowIndex] -gt 0) {
|
||||
$sb.AppendLine(" <td rowspan=$($tableRowSpans[$rowIndex])>$($row.$_)</td>")
|
||||
} else {
|
||||
# Skip rendering this cell at all
|
||||
}
|
||||
} else {
|
||||
$sb.AppendLine(" <td>$($row.$_)</td>")
|
||||
}
|
||||
}
|
||||
$sb.AppendLine(" </tr>")
|
||||
}
|
||||
$sb.AppendLine(" </tbody>")
|
||||
$sb.AppendLine("</table>")
|
||||
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[int[]] CalculateHtmlTableRowSpan([PSCustomObject[]] $Table, $keyColumn) {
|
||||
$result = @(0) * $Table.Count
|
||||
|
||||
for ($rowIndex = $Table.Count - 1; $rowIndex -ge 0; $rowIndex--) {
|
||||
if (($rowIndex -lt ($Table.Count - 1)) -and ($Table[$rowIndex].$keyColumn -eq $Table[$rowIndex + 1].$keyColumn)) {
|
||||
# If the current row is the same as the next row
|
||||
# Then rowspan of current row should be equal to rowspan of the next row + 1
|
||||
# And rowspan of the next row should be 0 because it is already included in the rowspan of the current row
|
||||
$result[$rowIndex] = $result[$rowIndex + 1] + 1
|
||||
$result[$rowIndex + 1] = 0
|
||||
} else {
|
||||
$result[$rowIndex] = 1
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
[String] RenderTableNodesDiff([ReportDifferenceItem] $DiffItem) {
|
||||
# Use the simplest approach for now: first, print all removed lines. Then print added lines
|
||||
# It will work well for most cases like changing existing rows, adding new rows and removing rows
|
||||
# But can produce not so pretty results for cases when some rows are changed and some rows are added at the same time
|
||||
# Let's see how it works in practice and improve it later if needed
|
||||
|
||||
[String] $tableHeaders = ($DiffItem.CurrentReportNode ?? $DiffItem.PreviousReportNode).Headers
|
||||
[System.Collections.ArrayList] $tableRows = @()
|
||||
$DiffItem.PreviousReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.CurrentReportNode.Rows } | ForEach-Object {
|
||||
$tableRows.Add($this.StrikeTableRow($_))
|
||||
}
|
||||
$DiffItem.CurrentReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.PreviousReportNode.Rows } | ForEach-Object {
|
||||
$tableRows.Add($_)
|
||||
}
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $false))")
|
||||
$sb.AppendLine([TableNode]::new($tableHeaders, $tableRows).ToMarkdown(0))
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[String] RenderCategory([Array] $Headers, [Boolean] $AddLineSeparator) {
|
||||
# Always skip the first header because it is "Installed Software"
|
||||
[Array] $takeHeaders = $Headers | Select-Object -Skip 1
|
||||
if ($takeHeaders.Count -eq 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
$lineSeparator = $AddLineSeparator ? "<br>": ""
|
||||
return [String]::Join(" >$lineSeparator ", $takeHeaders)
|
||||
}
|
||||
|
||||
[String] RenderToolName([String] $ToolName) {
|
||||
return $ToolName.TrimEnd(":")
|
||||
}
|
||||
|
||||
[String] StrikeTableRow([String] $Row) {
|
||||
# Convert "a|b|c" to "~~a~~|~~b~~|~~c~~
|
||||
$cells = $Row.Split("|")
|
||||
$strikedCells = $cells | ForEach-Object { "~~$($_)~~"}
|
||||
return [String]::Join("|", $strikedCells)
|
||||
}
|
||||
|
||||
[String] GetImageVersion([SoftwareReport] $Report) {
|
||||
$imageVersionNode = $Report.Root.Children ?? @() | Where-Object { ($_ -is [ToolVersionNode]) -and ($_.ToolName -eq "Image Version:") } | Select-Object -First 1
|
||||
return $imageVersionNode.Version ?? "Unknown version"
|
||||
}
|
||||
}
|
||||
|
||||
# Temporary structure to store the single difference between two reports
|
||||
class ReportDifferenceItem {
|
||||
[BaseNode] $PreviousReportNode
|
||||
[BaseNode] $CurrentReportNode
|
||||
[Array] $Headers
|
||||
|
||||
ReportDifferenceItem([BaseNode] $PreviousReportNode, [BaseNode] $CurrentReportNode, [Array] $Headers) {
|
||||
$this.PreviousReportNode = $PreviousReportNode
|
||||
$this.CurrentReportNode = $CurrentReportNode
|
||||
$this.Headers = $Headers
|
||||
}
|
||||
|
||||
[Boolean] IsBaseToolNode() {
|
||||
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
|
||||
return $node -is [BaseToolNode]
|
||||
}
|
||||
|
||||
[Boolean] IsTableNode() {
|
||||
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
|
||||
return $node -is [TableNode]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
using module ./SoftwareReport.DifferenceRender.psm1
|
||||
|
||||
class SoftwareReportDifferenceCalculator {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
hidden [SoftwareReport] $PreviousReport
|
||||
[ValidateNotNullOrEmpty()]
|
||||
hidden [SoftwareReport] $CurrentReport
|
||||
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $AddedItems
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $ChangedItems
|
||||
hidden [Collections.Generic.List[ReportDifferenceItem]] $DeletedItems
|
||||
|
||||
SoftwareReportDifferenceCalculator([SoftwareReport] $PreviousReport, [SoftwareReport] $CurrentReport) {
|
||||
$this.PreviousReport = $PreviousReport
|
||||
$this.CurrentReport = $CurrentReport
|
||||
}
|
||||
|
||||
[void] CompareReports() {
|
||||
$this.AddedItems = @()
|
||||
$this.ChangedItems = @()
|
||||
$this.DeletedItems = @()
|
||||
|
||||
$this.CompareInternal($this.PreviousReport.Root, $this.CurrentReport.Root, @())
|
||||
}
|
||||
|
||||
[String] GetMarkdownReport() {
|
||||
$reporter = [SoftwareReportDifferenceRender]::new()
|
||||
$report = $reporter.GenerateMarkdownReport($this.CurrentReport, $this.PreviousReport, $this.AddedItems, $this.ChangedItems, $this.DeletedItems)
|
||||
return $report
|
||||
}
|
||||
|
||||
hidden [void] CompareInternal([HeaderNode] $previousReportPointer, [HeaderNode] $currentReportPointer, [String[]] $Headers) {
|
||||
$currentReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
|
||||
$currentReportNode = $_
|
||||
$sameNodeInPreviousReport = $previousReportPointer ? $previousReportPointer.FindSimilarChildNode($currentReportNode) : $null
|
||||
|
||||
if ($currentReportNode -is [HeaderNode]) {
|
||||
# Compare HeaderNode recursively
|
||||
$this.CompareInternal($sameNodeInPreviousReport, $currentReportNode, $Headers + $currentReportNode.Title)
|
||||
} else {
|
||||
if ($sameNodeInPreviousReport -and ($currentReportNode.IsIdenticalTo($sameNodeInPreviousReport))) {
|
||||
# Nodes are identical, nothing changed, just ignore it
|
||||
} elseif ($sameNodeInPreviousReport) {
|
||||
# Nodes are equal but not identical, something was changed
|
||||
if ($currentReportNode -is [TableNode]) {
|
||||
$this.CompareSimilarTableNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
|
||||
} elseif ($currentReportNode -is [ToolVersionsListNode]) {
|
||||
$this.CompareSimilarToolVersionsListNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
|
||||
} else {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers))
|
||||
}
|
||||
} else {
|
||||
# Node was not found in previous report, new node was added
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $currentReportNode, $Headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Detecting nodes that were removed
|
||||
$previousReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
|
||||
$previousReportNode = $_
|
||||
$sameNodeInCurrentReport = $currentReportPointer ? $currentReportPointer.FindSimilarChildNode($previousReportNode) : $null
|
||||
|
||||
if (-not $sameNodeInCurrentReport) {
|
||||
if ($previousReportNode -is [HeaderNode]) {
|
||||
# Compare removed HeaderNode recursively
|
||||
$this.CompareInternal($previousReportNode, $null, $Headers + $previousReportNode.Title)
|
||||
} else {
|
||||
# Node was not found in current report, node was removed
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($previousReportNode, $null, $Headers))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hidden [void] CompareSimilarTableNodes([TableNode] $PreviousReportNode, [TableNode] $CurrentReportNode, [String[]] $Headers) {
|
||||
$addedRows = $CurrentReportNode.Rows | Where-Object { $_ -notin $PreviousReportNode.Rows }
|
||||
$deletedRows = $PreviousReportNode.Rows | Where-Object { $_ -notin $CurrentReportNode.Rows }
|
||||
|
||||
if (($addedRows.Count -eq 0) -and ($deletedRows.Count -eq 0)) {
|
||||
# Unexpected state: TableNodes are identical
|
||||
return
|
||||
}
|
||||
|
||||
if ($PreviousReportNode.Headers -ne $CurrentReportNode.Headers) {
|
||||
# If headers are changed and rows are changed at the same time, we should track it as removing table and adding new one
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $null, $Headers))
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $CurrentReportNode, $Headers))
|
||||
} elseif (($addedRows.Count -gt 0) -and ($deletedRows.Count -eq 0)) {
|
||||
# If new rows were added and no rows were deleted, then it is AddedItem
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
} elseif (($deletedRows.Count -gt 0) -and ($addedRows.Count -eq 0)) {
|
||||
# If no rows were added and some rows were deleted, then it is DeletedItem
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
} else {
|
||||
# If some rows were added and some rows were removed, then it is UpdatedItem
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
|
||||
}
|
||||
}
|
||||
|
||||
hidden [void] CompareSimilarToolVersionsListNodes([ToolVersionsListNode] $PreviousReportNode, [ToolVersionsListNode] $CurrentReportNode, [String[]] $Headers) {
|
||||
$previousReportMajorVersions = $PreviousReportNode.Versions | ForEach-Object { $PreviousReportNode.ExtractMajorVersion($_) }
|
||||
$currentReportMajorVersion = $CurrentReportNode.Versions | ForEach-Object { $CurrentReportNode.ExtractMajorVersion($_) }
|
||||
|
||||
$addedVersions = $CurrentReportNode.Versions | Where-Object { $CurrentReportNode.ExtractMajorVersion($_) -notin $previousReportMajorVersions }
|
||||
$deletedVersions = $PreviousReportNode.Versions | Where-Object { $PreviousReportNode.ExtractMajorVersion($_) -notin $currentReportMajorVersion }
|
||||
$changedPreviousVersions = $PreviousReportNode.Versions | Where-Object { ($PreviousReportNode.ExtractMajorVersion($_) -in $currentReportMajorVersion) -and ($_ -notin $CurrentReportNode.Versions) }
|
||||
$changedCurrentVersions = $CurrentReportNode.Versions | Where-Object { ($CurrentReportNode.ExtractMajorVersion($_) -in $previousReportMajorVersions) -and ($_ -notin $PreviousReportNode.Versions) }
|
||||
|
||||
if ($addedVersions.Count -gt 0) {
|
||||
$this.AddedItems.Add([ReportDifferenceItem]::new($null, [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $addedVersions, $CurrentReportNode.MajorVersionRegex, "List"), $Headers))
|
||||
}
|
||||
|
||||
if ($deletedVersions.Count -gt 0) {
|
||||
$this.DeletedItems.Add([ReportDifferenceItem]::new([ToolVersionsListNode]::new($PreviousReportNode.ToolName, $deletedVersions, $PreviousReportNode.MajorVersionRegex, "List"), $null, $Headers))
|
||||
}
|
||||
|
||||
$previousChangedNode = ($changedPreviousVersions.Count -gt 0) ? [ToolVersionsListNode]::new($PreviousReportNode.ToolName, $changedPreviousVersions, $PreviousReportNode.MajorVersionRegex, "List") : $null
|
||||
$currentChangedNode = ($changedCurrentVersions.Count -gt 0) ? [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $changedCurrentVersions, $CurrentReportNode.MajorVersionRegex, "List") : $null
|
||||
if ($previousChangedNode -and $currentChangedNode) {
|
||||
$this.ChangedItems.Add([ReportDifferenceItem]::new($previousChangedNode, $currentChangedNode, $Headers))
|
||||
}
|
||||
}
|
||||
|
||||
hidden [Boolean] FilterExcludedNodes([BaseNode] $Node) {
|
||||
# We shouldn't show "Image Version" diff because it is already shown in report header
|
||||
if (($Node -is [ToolVersionNode]) -and ($Node.ToolName -eq "Image Version:")) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
using module ./SoftwareReport.psm1
|
||||
using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
|
||||
class SoftwareReportDifferenceRender {
|
||||
[String] GenerateMarkdownReport([SoftwareReport] $CurrentReport, [SoftwareReport] $PreviousReport, [ReportDifferenceItem[]] $AddedItems, [ReportDifferenceItem[]] $ChangedItems, [ReportDifferenceItem[]] $DeletedItems) {
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
|
||||
$rootNode = $CurrentReport.Root
|
||||
$imageVersion = $CurrentReport.GetImageVersion()
|
||||
$previousImageVersion = $PreviousReport.GetImageVersion()
|
||||
|
||||
#############################
|
||||
### Render report header ####
|
||||
#############################
|
||||
|
||||
$sb.AppendLine("# :desktop_computer: Actions Runner Image: $($rootNode.Title)")
|
||||
|
||||
# ToolVersionNodes on root level contains main image description so just copy-paste them to final report
|
||||
$rootNode.Children | Where-Object { $_ -is [ToolVersionNode] } | ForEach-Object {
|
||||
$sb.AppendLine($_.ToMarkdown())
|
||||
}
|
||||
$sb.AppendLine()
|
||||
|
||||
$sb.AppendLine("## :mega: What's changed?").AppendLine()
|
||||
|
||||
###########################
|
||||
### Render added items ####
|
||||
###########################
|
||||
|
||||
[ReportDifferenceItem[]] $addedItemsBaseTools = $AddedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
[ReportDifferenceItem[]] $addedItemsTables = $AddedItems | Where-Object { $_.IsTableNode() }
|
||||
if ($addedItemsBaseTools.Count + $addedItemsTables.Count -gt 0) {
|
||||
$sb.AppendLine("### Added :heavy_plus_sign:").AppendLine()
|
||||
}
|
||||
if ($addedItemsBaseTools.Count -gt 0) {
|
||||
$tableItems = $addedItemsBaseTools | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
|
||||
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
if ($addedItemsTables.Count -gt 0) {
|
||||
$addedItemsTables | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
}
|
||||
|
||||
#############################
|
||||
### Render deleted items ####
|
||||
#############################
|
||||
|
||||
[ReportDifferenceItem[]] $deletedItemsBaseTools = $DeletedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
[ReportDifferenceItem[]] $deletedItemsTables = $DeletedItems | Where-Object { $_.IsTableNode() }
|
||||
if ($deletedItemsBaseTools.Count + $deletedItemsTables.Count -gt 0) {
|
||||
$sb.AppendLine("### Deleted :heavy_minus_sign:").AppendLine()
|
||||
}
|
||||
if ($deletedItemsBaseTools.Count -gt 0) {
|
||||
$tableItems = $deletedItemsBaseTools | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.PreviousReportNode.ToolName);
|
||||
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
if ($deletedItemsTables.Count -gt 0) {
|
||||
$deletedItemsTables | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
}
|
||||
|
||||
#############################
|
||||
### Render updated items ####
|
||||
#############################
|
||||
|
||||
[ReportDifferenceItem[]] $changedItemsBaseTools = $ChangedItems | Where-Object { $_.IsBaseToolNode() }
|
||||
[ReportDifferenceItem[]] $changedItemsTables = $ChangedItems | Where-Object { $_.IsTableNode() }
|
||||
if ($changedItemsBaseTools.Count + $changedItemsTables.Count -gt 0) {
|
||||
$sb.AppendLine("### Updated").AppendLine()
|
||||
}
|
||||
if ($changedItemsBaseTools.Count -gt 0) {
|
||||
$tableItems = $changedItemsBaseTools | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
"Category" = $this.RenderCategory($_.Headers, $true);
|
||||
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
|
||||
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
|
||||
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
|
||||
}
|
||||
}
|
||||
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
|
||||
}
|
||||
if ($changedItemsTables.Count -gt 0) {
|
||||
$changedItemsTables | ForEach-Object {
|
||||
$sb.AppendLine($this.RenderTableNodesDiff($_))
|
||||
}
|
||||
}
|
||||
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[String] RenderHtmlTable([PSCustomObject[]] $Table, [String] $RowSpanColumnName) {
|
||||
$headers = $Table[0].PSObject.Properties.Name
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine("<table>")
|
||||
$sb.AppendLine(" <thead>")
|
||||
$headers | ForEach-Object {
|
||||
$sb.AppendLine(" <th>$_</th>")
|
||||
}
|
||||
$sb.AppendLine(" </thead>")
|
||||
$sb.AppendLine(" <tbody>")
|
||||
|
||||
$tableRowSpans = $this.CalculateHtmlTableRowSpan($Table, $RowSpanColumnName)
|
||||
for ($rowIndex = 0; $rowIndex -lt $Table.Count; $rowIndex++) {
|
||||
$row = $Table[$rowIndex]
|
||||
|
||||
$sb.AppendLine(" <tr>")
|
||||
$headers | ForEach-Object {
|
||||
if ($_ -eq $RowSpanColumnName) {
|
||||
if ($tableRowSpans[$rowIndex] -gt 0) {
|
||||
$sb.AppendLine(" <td rowspan=`"$($tableRowSpans[$rowIndex])`">$($row.$_)</td>")
|
||||
} else {
|
||||
# Skip rendering this cell at all
|
||||
}
|
||||
} else {
|
||||
$sb.AppendLine(" <td>$($row.$_)</td>")
|
||||
}
|
||||
}
|
||||
$sb.AppendLine(" </tr>")
|
||||
}
|
||||
$sb.AppendLine(" </tbody>")
|
||||
$sb.AppendLine("</table>")
|
||||
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[int[]] CalculateHtmlTableRowSpan([PSCustomObject[]] $Table, [String] $keyColumn) {
|
||||
$result = @(0) * $Table.Count
|
||||
|
||||
for ($rowIndex = $Table.Count - 1; $rowIndex -ge 0; $rowIndex--) {
|
||||
if (($rowIndex -lt ($Table.Count - 1)) -and ($Table[$rowIndex].$keyColumn -eq $Table[$rowIndex + 1].$keyColumn)) {
|
||||
# If the current row is the same as the next row
|
||||
# Then rowspan of current row should be equal to rowspan of the next row + 1
|
||||
# And rowspan of the next row should be 0 because it is already included in the rowspan of the current row
|
||||
$result[$rowIndex] = $result[$rowIndex + 1] + 1
|
||||
$result[$rowIndex + 1] = 0
|
||||
} else {
|
||||
$result[$rowIndex] = 1
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
[String] RenderTableNodesDiff([ReportDifferenceItem] $DiffItem) {
|
||||
# Use the simplest approach for now: first, print all removed lines. Then print added lines
|
||||
# It will work well for most cases like changing existing rows, adding new rows and removing rows
|
||||
# But can produce not so pretty results for cases when some rows are changed and some rows are added at the same time
|
||||
# Let's see how it works in practice and improve it later if needed
|
||||
|
||||
[String] $tableHeaders = ($DiffItem.CurrentReportNode ?? $DiffItem.PreviousReportNode).Headers
|
||||
[Collections.Generic.List[String]] $tableRows = @()
|
||||
$DiffItem.PreviousReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.CurrentReportNode.Rows } | ForEach-Object {
|
||||
$tableRows.Add($this.StrikeTableRow($_))
|
||||
}
|
||||
$DiffItem.CurrentReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.PreviousReportNode.Rows } | ForEach-Object {
|
||||
$tableRows.Add($_)
|
||||
}
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $false))")
|
||||
$sb.AppendLine([TableNode]::new($tableHeaders, $tableRows).ToMarkdown())
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
[String] RenderCategory([String[]] $Headers, [Boolean] $AddLineSeparator) {
|
||||
# Always skip the first header because it is "Installed Software"
|
||||
[String[]] $takeHeaders = $Headers | Select-Object -Skip 1
|
||||
if ($takeHeaders.Count -eq 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
$lineSeparator = $AddLineSeparator ? "<br>": ""
|
||||
return [String]::Join(" >$lineSeparator ", $takeHeaders)
|
||||
}
|
||||
|
||||
[String] RenderToolName([String] $ToolName) {
|
||||
return $ToolName.TrimEnd(":")
|
||||
}
|
||||
|
||||
[String] StrikeTableRow([String] $Row) {
|
||||
# Convert "a|b|c" to "~~a~~|~~b~~|~~c~~
|
||||
$cells = $Row.Split("|")
|
||||
$strikedCells = $cells | ForEach-Object { "~~$($_)~~"}
|
||||
return [String]::Join("|", $strikedCells)
|
||||
}
|
||||
}
|
||||
|
||||
# Temporary structure to store the single difference between two reports
|
||||
class ReportDifferenceItem {
|
||||
[BaseNode] $PreviousReportNode
|
||||
[BaseNode] $CurrentReportNode
|
||||
[String[]] $Headers
|
||||
|
||||
ReportDifferenceItem([BaseNode] $PreviousReportNode, [BaseNode] $CurrentReportNode, [String[]] $Headers) {
|
||||
$this.PreviousReportNode = $PreviousReportNode
|
||||
$this.CurrentReportNode = $CurrentReportNode
|
||||
$this.Headers = $Headers
|
||||
}
|
||||
|
||||
[Boolean] IsBaseToolNode() {
|
||||
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
|
||||
return $node -is [BaseToolNode]
|
||||
}
|
||||
|
||||
[Boolean] IsTableNode() {
|
||||
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
|
||||
return $node -is [TableNode]
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,27 @@ using module ./SoftwareReport.BaseNodes.psm1
|
||||
# NodesFactory is used to simplify parsing different types of notes
|
||||
# Every node has own logic of parsing and this method just invokes "FromJsonObject" of correct node type
|
||||
class NodesFactory {
|
||||
static [BaseNode] ParseNodeFromObject($jsonObj) {
|
||||
if ($jsonObj.NodeType -eq [HeaderNode].Name) {
|
||||
return [HeaderNode]::FromJsonObject($jsonObj)
|
||||
} elseif ($jsonObj.NodeType -eq [ToolVersionNode].Name) {
|
||||
return [ToolVersionNode]::FromJsonObject($jsonObj)
|
||||
} elseif ($jsonObj.NodeType -eq [ToolVersionsListNode].Name) {
|
||||
return [ToolVersionsListNode]::FromJsonObject($jsonObj)
|
||||
} elseif ($jsonObj.NodeType -eq [TableNode].Name) {
|
||||
return [TableNode]::FromJsonObject($jsonObj)
|
||||
} elseif ($jsonObj.NodeType -eq [NoteNode].Name) {
|
||||
return [NoteNode]::FromJsonObject($jsonObj)
|
||||
static [BaseNode] ParseNodeFromObject([object] $JsonObj) {
|
||||
if ($JsonObj.NodeType -eq [HeaderNode].Name) {
|
||||
return [HeaderNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [ToolVersionNode].Name) {
|
||||
return [ToolVersionNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [ToolVersionsListNode].Name) {
|
||||
return [ToolVersionsListNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [TableNode].Name) {
|
||||
return [TableNode]::FromJsonObject($JsonObj)
|
||||
} elseif ($JsonObj.NodeType -eq [NoteNode].Name) {
|
||||
return [NoteNode]::FromJsonObject($JsonObj)
|
||||
}
|
||||
|
||||
throw "Unknown node type in ParseNodeFromObject '$($jsonObj.NodeType)'"
|
||||
throw "Unknown node type in ParseNodeFromObject '$($JsonObj.NodeType)'"
|
||||
}
|
||||
}
|
||||
|
||||
# Node type to describe headers: "## Installed software"
|
||||
class HeaderNode: BaseNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Title
|
||||
[System.Collections.ArrayList] $Children
|
||||
[Collections.Generic.List[BaseNode]] $Children
|
||||
|
||||
HeaderNode([String] $Title) {
|
||||
$this.Title = $Title
|
||||
@@ -44,10 +44,15 @@ class HeaderNode: BaseNode {
|
||||
throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.`nFound node: $($similarNode.ToJsonObject() | ConvertTo-Json)`nNew node: $($node.ToJsonObject() | ConvertTo-Json)"
|
||||
}
|
||||
|
||||
[Array] $existingHeaderNodes = $this.Children | Where-Object { $_ -is [HeaderNode] }
|
||||
if (($existingHeaderNodes.Count -gt 0) -and ($node -isnot [HeaderNode])) {
|
||||
throw "It is not allowed to add the node of type '$($node.GetType().Name)' to the HeaderNode that already contains the HeaderNode children."
|
||||
}
|
||||
|
||||
$this.Children.Add($node)
|
||||
}
|
||||
|
||||
[void] AddNodes([Array] $nodes) {
|
||||
[void] AddNodes([BaseNode[]] $nodes) {
|
||||
$nodes | ForEach-Object {
|
||||
$this.AddNode($_)
|
||||
}
|
||||
@@ -63,11 +68,15 @@ class HeaderNode: BaseNode {
|
||||
$this.AddNode([ToolVersionNode]::new($ToolName, $Version))
|
||||
}
|
||||
|
||||
[void] AddToolVersionsList([String] $ToolName, [Array] $Version, [String] $MajorVersionRegex, [Boolean] $InlineList) {
|
||||
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, $InlineList))
|
||||
[void] AddToolVersionsList([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) {
|
||||
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "List"))
|
||||
}
|
||||
|
||||
[void] AddToolVersionsListInline([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) {
|
||||
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "Inline"))
|
||||
}
|
||||
|
||||
[void] AddTable([Array] $Table) {
|
||||
[void] AddTable([PSCustomObject[]] $Table) {
|
||||
$this.AddNode([TableNode]::FromObjectsArray($Table))
|
||||
}
|
||||
|
||||
@@ -75,12 +84,12 @@ class HeaderNode: BaseNode {
|
||||
$this.AddNode([NoteNode]::new($Content))
|
||||
}
|
||||
|
||||
[String] ToMarkdown($level) {
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine()
|
||||
$sb.AppendLine("$("#" * $level) $($this.Title)")
|
||||
$sb.AppendLine("$("#" * $Level) $($this.Title)")
|
||||
$this.Children | ForEach-Object {
|
||||
$sb.AppendLine($_.ToMarkdown($level + 1))
|
||||
$sb.AppendLine($_.ToMarkdown($Level + 1))
|
||||
}
|
||||
|
||||
return $sb.ToString().TrimEnd()
|
||||
@@ -94,9 +103,9 @@ class HeaderNode: BaseNode {
|
||||
}
|
||||
}
|
||||
|
||||
static [HeaderNode] FromJsonObject($jsonObj) {
|
||||
$node = [HeaderNode]::new($jsonObj.Title)
|
||||
$jsonObj.Children | Where-Object { $_ } | ForEach-Object { $node.AddNode([NodesFactory]::ParseNodeFromObject($_)) }
|
||||
static [HeaderNode] FromJsonObject([Object] $JsonObj) {
|
||||
$node = [HeaderNode]::new($JsonObj.Title)
|
||||
$JsonObj.Children | Where-Object { $_ } | ForEach-Object { $node.AddNode([NodesFactory]::ParseNodeFromObject($_)) }
|
||||
return $node
|
||||
}
|
||||
|
||||
@@ -123,15 +132,15 @@ class HeaderNode: BaseNode {
|
||||
}
|
||||
}
|
||||
|
||||
# Node type to describe the tool with single version: "Bash 5.1.16"
|
||||
class ToolVersionNode: BaseToolNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Version
|
||||
|
||||
ToolVersionNode([String] $ToolName, [String] $Version): base($ToolName) {
|
||||
$this.Version = $Version
|
||||
}
|
||||
|
||||
[String] ToMarkdown($level) {
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
return "- $($this.ToolName) $($this.Version)"
|
||||
}
|
||||
|
||||
@@ -147,32 +156,35 @@ class ToolVersionNode: BaseToolNode {
|
||||
}
|
||||
}
|
||||
|
||||
static [BaseNode] FromJsonObject($jsonObj) {
|
||||
return [ToolVersionNode]::new($jsonObj.ToolName, $jsonObj.Version)
|
||||
static [BaseNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [ToolVersionNode]::new($JsonObj.ToolName, $JsonObj.Version)
|
||||
}
|
||||
}
|
||||
|
||||
# Node type to describe the tool with multiple versions "Toolcache Node.js 14.17.6 16.2.0 18.2.3"
|
||||
class ToolVersionsListNode: BaseToolNode {
|
||||
[Array] $Versions
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String[]] $Versions
|
||||
|
||||
[Regex] $MajorVersionRegex
|
||||
|
||||
[ValidateSet("List", "Inline")]
|
||||
[String] $ListType
|
||||
|
||||
ToolVersionsListNode([String] $ToolName, [Array] $Versions, [String] $MajorVersionRegex, [Boolean] $InlineList): base($ToolName) {
|
||||
ToolVersionsListNode([String] $ToolName, [String[]] $Versions, [String] $MajorVersionRegex, [String] $ListType): base($ToolName) {
|
||||
$this.Versions = $Versions
|
||||
$this.MajorVersionRegex = [Regex]::new($MajorVersionRegex)
|
||||
$this.ListType = $InlineList ? "Inline" : "List"
|
||||
$this.ListType = $ListType
|
||||
$this.ValidateMajorVersionRegex()
|
||||
}
|
||||
|
||||
[String] ToMarkdown($level) {
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
if ($this.ListType -eq "Inline") {
|
||||
return "- $($this.ToolName): $($this.Versions -join ', ')"
|
||||
}
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
$sb.AppendLine()
|
||||
$sb.AppendLine("$("#" * $level) $($this.ToolName)")
|
||||
$sb.AppendLine("$("#" * $Level) $($this.ToolName)")
|
||||
$this.Versions | ForEach-Object {
|
||||
$sb.AppendLine("- $_")
|
||||
}
|
||||
@@ -186,7 +198,7 @@ class ToolVersionsListNode: BaseToolNode {
|
||||
|
||||
[String] ExtractMajorVersion([String] $Version) {
|
||||
$match = $this.MajorVersionRegex.Match($Version)
|
||||
if ($match.Success -ne $true) {
|
||||
if (($match.Success -ne $true) -or [String]::IsNullOrEmpty($match.Groups[0].Value)) {
|
||||
throw "Version '$Version' doesn't match regex '$($this.PrimaryVersionRegex)'"
|
||||
}
|
||||
|
||||
@@ -203,57 +215,46 @@ class ToolVersionsListNode: BaseToolNode {
|
||||
}
|
||||
}
|
||||
|
||||
static [ToolVersionsListNode] FromJsonObject($jsonObj) {
|
||||
return [ToolVersionsListNode]::new($jsonObj.ToolName, $jsonObj.Versions, $jsonObj.MajorVersionRegex, $jsonObj.ListType -eq "Inline")
|
||||
static [ToolVersionsListNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [ToolVersionsListNode]::new($JsonObj.ToolName, $JsonObj.Versions, $JsonObj.MajorVersionRegex, $JsonObj.ListType)
|
||||
}
|
||||
|
||||
hidden [void] ValidateMajorVersionRegex() {
|
||||
$this.Versions | Group-Object { $this.ExtractMajorVersion($_) } | ForEach-Object {
|
||||
if ($_.Count -gt 1) {
|
||||
throw "Multiple versions from list $($this.GetValue()) return the same result from regex '$($this.MajorVersionRegex)': $($_.Name)"
|
||||
throw "Multiple versions from list '$($this.GetValue())' return the same result from regex '$($this.MajorVersionRegex)': $($_.Name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Node type to describe tables
|
||||
class TableNode: BaseNode {
|
||||
# It is easier to store the table as rendered lines because it will simplify finding differences in rows later
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Headers
|
||||
[System.Collections.ArrayList] $Rows
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String[]] $Rows
|
||||
|
||||
TableNode($Headers, $Rows) {
|
||||
TableNode([String] $Headers, [String[]] $Rows) {
|
||||
$this.Headers = $Headers
|
||||
$this.Rows = $Rows
|
||||
|
||||
$columnsCount = $this.Headers.Split("|").Count
|
||||
$this.Rows | ForEach-Object {
|
||||
if ($_.Split("|").Count -ne $columnsCount) {
|
||||
throw "Table has different number of columns in different rows"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Boolean] ShouldBeIncludedToDiff() {
|
||||
return $true
|
||||
}
|
||||
|
||||
static [TableNode] FromObjectsArray([Array] $Table) {
|
||||
# take column names from the first row in table because we expect all rows to have the same columns
|
||||
[String] $tableHeaders = [TableNode]::ArrayToTableRow($Table[0].PSObject.Properties.Name)
|
||||
[System.Collections.ArrayList] $tableRows = @()
|
||||
|
||||
$Table | ForEach-Object {
|
||||
$tableRows.Add([TableNode]::ArrayToTableRow($_.PSObject.Properties.Value))
|
||||
}
|
||||
|
||||
return [TableNode]::new($tableHeaders, $tableRows)
|
||||
}
|
||||
|
||||
[String] ToMarkdown($level) {
|
||||
$maxColumnWidths = $this.Headers.Split("|") | ForEach-Object { $_.Length }
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
$maxColumnWidths = $this.CalculateColumnsWidth()
|
||||
$columnsCount = $maxColumnWidths.Count
|
||||
|
||||
$this.Rows | ForEach-Object {
|
||||
$columnWidths = $_.Split("|") | ForEach-Object { $_.Length }
|
||||
for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) {
|
||||
$maxColumnWidths[$colIndex] = [Math]::Max($maxColumnWidths[$colIndex], $columnWidths[$colIndex])
|
||||
}
|
||||
}
|
||||
|
||||
$delimeterLine = [String]::Join("|", @("-") * $columnsCount)
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
@@ -273,6 +274,20 @@ class TableNode: BaseNode {
|
||||
return $sb.ToString().TrimEnd()
|
||||
}
|
||||
|
||||
hidden [Int32[]] CalculateColumnsWidth() {
|
||||
$maxColumnWidths = $this.Headers.Split("|") | ForEach-Object { $_.Length }
|
||||
$columnsCount = $maxColumnWidths.Count
|
||||
|
||||
$this.Rows | ForEach-Object {
|
||||
$columnWidths = $_.Split("|") | ForEach-Object { $_.Length }
|
||||
for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) {
|
||||
$maxColumnWidths[$colIndex] = [Math]::Max($maxColumnWidths[$colIndex], $columnWidths[$colIndex])
|
||||
}
|
||||
}
|
||||
|
||||
return $maxColumnWidths
|
||||
}
|
||||
|
||||
[PSCustomObject] ToJsonObject() {
|
||||
return [PSCustomObject]@{
|
||||
NodeType = $this.GetType().Name
|
||||
@@ -281,8 +296,8 @@ class TableNode: BaseNode {
|
||||
}
|
||||
}
|
||||
|
||||
static [TableNode] FromJsonObject($jsonObj) {
|
||||
return [TableNode]::new($jsonObj.Headers, $jsonObj.Rows)
|
||||
static [TableNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [TableNode]::new($JsonObj.Headers, $JsonObj.Rows)
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
@@ -299,9 +314,8 @@ class TableNode: BaseNode {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($this.Headers -ne $OtherNode.Headers) {
|
||||
return $false
|
||||
}
|
||||
# We don't compare $this.Headers intentionally
|
||||
# It is fine to ignore the tables where headers are changed but rows are not changed
|
||||
|
||||
if ($this.Rows.Count -ne $OtherNode.Rows.Count) {
|
||||
return $false
|
||||
@@ -316,20 +330,49 @@ class TableNode: BaseNode {
|
||||
return $true
|
||||
}
|
||||
|
||||
hidden static [String] ArrayToTableRow([Array] $Values) {
|
||||
# TO-DO: Add validation for the case when $Values contains "|"
|
||||
static [TableNode] FromObjectsArray([PSCustomObject[]] $Table) {
|
||||
if ($Table.Count -eq 0) {
|
||||
throw "Failed to create TableNode from empty objects array"
|
||||
}
|
||||
|
||||
[String] $tableHeaders = [TableNode]::ArrayToTableRow($Table[0].PSObject.Properties.Name)
|
||||
[Collections.Generic.List[String]] $tableRows = @()
|
||||
|
||||
$Table | ForEach-Object {
|
||||
$rowHeaders = [TableNode]::ArrayToTableRow($_.PSObject.Properties.Name)
|
||||
if (($rowHeaders -ne $tableHeaders)) {
|
||||
throw "Failed to create TableNode from objects array because objects have different properties"
|
||||
}
|
||||
|
||||
$tableRows.Add([TableNode]::ArrayToTableRow($_.PSObject.Properties.Value))
|
||||
}
|
||||
|
||||
return [TableNode]::new($tableHeaders, $tableRows)
|
||||
}
|
||||
|
||||
hidden static [String] ArrayToTableRow([String[]] $Values) {
|
||||
if ($Values.Count -eq 0) {
|
||||
throw "Failed to create TableNode because some objects are empty"
|
||||
}
|
||||
$Values | ForEach-Object {
|
||||
if ($_.Contains("|")) {
|
||||
throw "Failed to create TableNode because some cells '$_' contains forbidden symbol '|'"
|
||||
}
|
||||
}
|
||||
|
||||
return [String]::Join("|", $Values)
|
||||
}
|
||||
}
|
||||
|
||||
class NoteNode: BaseNode {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $Content
|
||||
|
||||
NoteNode([String] $Content) {
|
||||
$this.Content = $Content
|
||||
}
|
||||
|
||||
[String] ToMarkdown($level) {
|
||||
[String] ToMarkdown([Int32] $Level) {
|
||||
return @(
|
||||
'```',
|
||||
$this.Content,
|
||||
@@ -344,8 +387,8 @@ class NoteNode: BaseNode {
|
||||
}
|
||||
}
|
||||
|
||||
static [NoteNode] FromJsonObject($jsonObj) {
|
||||
return [NoteNode]::new($jsonObj.Content)
|
||||
static [NoteNode] FromJsonObject([Object] $JsonObj) {
|
||||
return [NoteNode]::new($JsonObj.Content)
|
||||
}
|
||||
|
||||
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
|
||||
|
||||
@@ -2,6 +2,7 @@ using module ./SoftwareReport.BaseNodes.psm1
|
||||
using module ./SoftwareReport.Nodes.psm1
|
||||
|
||||
class SoftwareReport {
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[HeaderNode] $Root
|
||||
|
||||
SoftwareReport([String] $Title) {
|
||||
@@ -16,13 +17,18 @@ class SoftwareReport {
|
||||
return $this.Root.ToJsonObject() | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
static [SoftwareReport] FromJson($jsonString) {
|
||||
$jsonObj = $jsonString | ConvertFrom-Json
|
||||
static [SoftwareReport] FromJson([String] $JsonString) {
|
||||
$jsonObj = $JsonString | ConvertFrom-Json
|
||||
$rootNode = [NodesFactory]::ParseNodeFromObject($jsonObj)
|
||||
return [SoftwareReport]::new($rootNode)
|
||||
}
|
||||
|
||||
[String] ToMarkdown() {
|
||||
return $this.Root.ToMarkdown(1).Trim()
|
||||
return $this.Root.ToMarkdown().Trim()
|
||||
}
|
||||
|
||||
[String] GetImageVersion() {
|
||||
$imageVersionNode = $this.Root.Children ?? @() | Where-Object { ($_ -is [ToolVersionNode]) -and ($_.ToolName -eq "Image Version:") } | Select-Object -First 1
|
||||
return $imageVersionNode.Version ?? "Unknown version"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
using module ../SoftwareReport.psm1
|
||||
using module ../SoftwareReport.DifferenceCalculator.psm1
|
||||
|
||||
Describe "Comparer.E2E" {
|
||||
It "Some tools are updated" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddToolVersion("ToolWillBeUpdated1", "1.0.0")
|
||||
$prevTools.AddToolVersion("ToolWillBeUpdated2", "3.0.1")
|
||||
$prevTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.0.0", "15.5.1"), "^\d+")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddToolVersion("ToolWillBeUpdated1", "2.5.0")
|
||||
$nextTools.AddToolVersion("ToolWillBeUpdated2", "3.0.2")
|
||||
$nextTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.2.0", "15.5.1"), "^\d+")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- OS Version: macOS 11.7.1 (20G817)
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Updated
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
<th>Current (20220922.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="3">Tools</td>
|
||||
<td>ToolWillBeUpdated1</td>
|
||||
<td>1.0.0</td>
|
||||
<td>2.5.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeUpdated2</td>
|
||||
<td>3.0.1</td>
|
||||
<td>3.0.2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeUpdated3</td>
|
||||
<td>14.0.0</td>
|
||||
<td>14.2.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Some tools are updated, added and removed" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
$prevLanguagesAndRuntimes = $prevInstalledSoftware.AddHeader("Language and Runtime")
|
||||
$prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeRemoved", "5.1.16(1)-release")
|
||||
$prevLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.100", "1.2.200", "1.3.500", "1.4.100", "1.4.200"), "^\d+\.\d+\.\d")
|
||||
$prevLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.1.0")
|
||||
|
||||
$prevCachedTools = $prevInstalledSoftware.AddHeader("Cached Tools")
|
||||
$prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
|
||||
$prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("14.8.0", "15.1.0", "16.4.2"), "^\d+")
|
||||
|
||||
$prevSQLSection = $prevInstalledSoftware.AddHeader("Databases")
|
||||
$prevSQLSection.AddToolVersion("MineSQL", "6.1.0")
|
||||
$prevSQLSection.AddNote("First Note")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.2 (20G922)")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
$nextLanguagesAndRuntimes = $nextInstalledSoftware.AddHeader("Language and Runtime")
|
||||
$nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeAdded", "16.18.0")
|
||||
$nextLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.200", "1.3.515", "1.4.100", "1.4.200", "1.5.800"), "^\d+\.\d+\.\d")
|
||||
$nextLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.3.0")
|
||||
|
||||
$nextCachedTools = $nextInstalledSoftware.AddHeader("Cached Tools")
|
||||
$nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
|
||||
$nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("15.1.0", "16.4.2", "17.0.1"), "^\d+")
|
||||
|
||||
$nextSQLSection = $nextInstalledSoftware.AddHeader("Databases")
|
||||
$nextSQLSection.AddToolVersion("MineSQL", "6.1.1")
|
||||
$nextSQLSection.AddNote("Second Note")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- OS Version: macOS 11.7.2 (20G922)
|
||||
- Image Version: 20220922.0
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Current (20220922.0)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="2">Language and Runtime</td>
|
||||
<td>ToolWillBeAdded</td>
|
||||
<td>16.18.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWithMultipleVersions3</td>
|
||||
<td>1.5.800</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Cached Tools</td>
|
||||
<td>ToolWithMultipleVersions2</td>
|
||||
<td>17.0.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="2">Language and Runtime</td>
|
||||
<td>ToolWithMultipleVersions3</td>
|
||||
<td>1.2.100</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeRemoved</td>
|
||||
<td>5.1.16(1)-release</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Cached Tools</td>
|
||||
<td>ToolWithMultipleVersions2</td>
|
||||
<td>14.8.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Updated
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
<th>Current (20220922.0)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1"></td>
|
||||
<td>OS Version</td>
|
||||
<td>macOS 11.7.1 (20G817)</td>
|
||||
<td>macOS 11.7.2 (20G922)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">Language and Runtime</td>
|
||||
<td>ToolWithMultipleVersions3</td>
|
||||
<td>1.3.500</td>
|
||||
<td>1.3.515</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ToolWillBeUpdated</td>
|
||||
<td>8.1.0</td>
|
||||
<td>8.3.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Databases</td>
|
||||
<td>MineSQL</td>
|
||||
<td>6.1.0</td>
|
||||
<td>6.1.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Header tree changes" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$prevInstalledSoftware.AddHeader("HeaderWillBeRemoved").AddHeader("SubheaderWillBeRemoved").AddToolVersion("ToolWillBeRemoved", "1.0.0")
|
||||
$prevInstalledSoftware.AddHeader("Header1").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0")
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0")
|
||||
$nextInstalledSoftware.AddHeader("HeaderWillBeAdded").AddHeader("SubheaderWillBeAdded").AddToolVersion("ToolWillBeAdded", "5.0.0")
|
||||
$nextInstalledSoftware.AddHeader("Header2").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0")
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- Image Version: 20220922.0
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Current (20220922.0)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">HeaderWillBeAdded ><br> SubheaderWillBeAdded</td>
|
||||
<td>ToolWillBeAdded</td>
|
||||
<td>5.0.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Header2</td>
|
||||
<td>ToolWillBeMovedToAnotherHeader</td>
|
||||
<td>3.0.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Previous (20220918.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">HeaderWillBeRemoved ><br> SubheaderWillBeRemoved</td>
|
||||
<td>ToolWillBeRemoved</td>
|
||||
<td>1.0.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Header1</td>
|
||||
<td>ToolWillBeMovedToAnotherHeader</td>
|
||||
<td>3.0.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Tables are added and removed" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevInstalledSoftware.AddHeader("HeaderWillExist").AddTable(@(
|
||||
[PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "Q"; Value = "25"},
|
||||
[PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "O"; Value = "24"}
|
||||
))
|
||||
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddHeader("HeaderWillBeRemoved").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "Z"; Value = "30"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "W"; Value = "29"}
|
||||
))
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextInstalledSoftware.AddHeader("HeaderWillExist")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddToolVersion("ToolWillBeAdded", "3.0.1")
|
||||
$nextTools.AddTable(@(
|
||||
[PSCustomObject]@{NewTableInExistingHeader = "A"; Value = "1"},
|
||||
[PSCustomObject]@{NewTableInExistingHeader = "B"; Value = "2"}
|
||||
))
|
||||
$nextTools.AddHeader("NewHeaderWithTable").AddTable(@(
|
||||
[PSCustomObject]@{NewTableInNewHeader = "C"; Value = "3"},
|
||||
[PSCustomObject]@{NewTableInNewHeader = "D"; Value = "4"}
|
||||
))
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Current (20220922.1)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">Tools</td>
|
||||
<td>ToolWillBeAdded</td>
|
||||
<td>3.0.1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
#### Tools
|
||||
| NewTableInExistingHeader | Value |
|
||||
| ------------------------ | ----- |
|
||||
| A | 1 |
|
||||
| B | 2 |
|
||||
|
||||
#### Tools > NewHeaderWithTable
|
||||
| NewTableInNewHeader | Value |
|
||||
| ------------------- | ----- |
|
||||
| C | 3 |
|
||||
| D | 4 |
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
#### HeaderWillExist
|
||||
| TableInExistingHeaderWillBeRemoved | Value |
|
||||
| ---------------------------------- | ------ |
|
||||
| ~~Q~~ | ~~25~~ |
|
||||
| ~~O~~ | ~~24~~ |
|
||||
|
||||
#### Tools > HeaderWillBeRemoved
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~Z~~ | ~~30~~ |
|
||||
| ~~W~~ | ~~29~~ |
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Tables are changed" {
|
||||
# Previous report
|
||||
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
|
||||
$prevTools.AddHeader("TableWithAddedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithRemovedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BA"; Value = "32"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithUpdatedRow").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "43"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithUpdatedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "51"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "52"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"}
|
||||
))
|
||||
$prevTools.AddHeader("TableWithComplexChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EA"; Value = "62"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "64"}
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"}
|
||||
))
|
||||
|
||||
$prevTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@(
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged = "FA"; Value = "72"},
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged = "FB"; Value = "73"}
|
||||
))
|
||||
|
||||
$prevTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GA"; Value = "82"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GB"; Value = "83"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GC"; Value = "84"}
|
||||
))
|
||||
|
||||
# Next report
|
||||
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
|
||||
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
|
||||
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
|
||||
$nextTools.AddHeader("TableWithAddedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AC"; Value = "12"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithRemovedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithUpdatedRow").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "500"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithUpdatedRows").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "5100"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "5200"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"}
|
||||
))
|
||||
$nextTools.AddHeader("TableWithComplexChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "640"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"},
|
||||
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EE"; Value = "66"}
|
||||
))
|
||||
|
||||
$nextTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@(
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FA"; Value = "72"},
|
||||
[PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FB"; Value = "73"}
|
||||
))
|
||||
|
||||
$nextTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@(
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GA"; Value = "82"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GE"; Value = "850"},
|
||||
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GC"; Value = "840"}
|
||||
))
|
||||
|
||||
# Compare reports
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
|
||||
$comparer.CompareReports()
|
||||
$comparer.GetMarkdownReport() | Should -BeExactly @'
|
||||
# :desktop_computer: Actions Runner Image: macOS 11
|
||||
- Image Version: 20220922.1
|
||||
|
||||
## :mega: What's changed?
|
||||
|
||||
### Added :heavy_plus_sign:
|
||||
|
||||
#### Tools > TableWithAddedRows
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ----- |
|
||||
| AC | 12 |
|
||||
|
||||
#### Tools > TableWithHeaderAndRowsChanges
|
||||
| TableWithHeaderAndRowsChanges2 | Value |
|
||||
| ------------------------------ | ----- |
|
||||
| GA | 82 |
|
||||
| GE | 850 |
|
||||
| GC | 840 |
|
||||
|
||||
### Deleted :heavy_minus_sign:
|
||||
|
||||
#### Tools > TableWithRemovedRows
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~BA~~ | ~~32~~ |
|
||||
|
||||
#### Tools > TableWithHeaderAndRowsChanges
|
||||
| TableWithHeaderAndRowsChanges | Value |
|
||||
| ----------------------------- | ------ |
|
||||
| ~~GA~~ | ~~82~~ |
|
||||
| ~~GB~~ | ~~83~~ |
|
||||
| ~~GC~~ | ~~84~~ |
|
||||
|
||||
### Updated
|
||||
|
||||
#### Tools > TableWithUpdatedRow
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~CB~~ | ~~43~~ |
|
||||
| CB | 500 |
|
||||
|
||||
#### Tools > TableWithUpdatedRows
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~DB~~ | ~~51~~ |
|
||||
| ~~DC~~ | ~~52~~ |
|
||||
| DB | 5100 |
|
||||
| DC | 5200 |
|
||||
|
||||
#### Tools > TableWithComplexChanges
|
||||
| TableWillBeRemovedWithHeader | Value |
|
||||
| ---------------------------- | ------ |
|
||||
| ~~EA~~ | ~~62~~ |
|
||||
| ~~EC~~ | ~~64~~ |
|
||||
| EC | 640 |
|
||||
| EE | 66 |
|
||||
|
||||
|
||||
'@
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
using module ../SoftwareReport.DifferenceCalculator.psm1
|
||||
|
||||
BeforeDiscovery {
|
||||
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
|
||||
}
|
||||
|
||||
Describe "Comparer.UnitTests" {
|
||||
Describe "Headers Tree" {
|
||||
It "Add Node to existing header" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader")
|
||||
}
|
||||
|
||||
It "Add new header with Node" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader")
|
||||
}
|
||||
|
||||
It "Remove Node from existing header" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader")
|
||||
}
|
||||
|
||||
It "Remove header with Node" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
|
||||
}
|
||||
|
||||
It "Node with minor changes" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.4")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
|
||||
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader")
|
||||
}
|
||||
|
||||
It "Node without changes" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Node is moved to different header" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader2").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader2")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
|
||||
}
|
||||
|
||||
It "Complex structure" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevSubHeader = $prevReport.AddHeader("MyHeader").AddHeader("MySubheader")
|
||||
$prevSubHeader.AddToolVersion("MyTool1", "2.1.3")
|
||||
$prevSubHeader.AddHeader("MySubSubheader").AddToolVersion("MyTool2", "2.9.1")
|
||||
$prevReport.AddHeader("MyHeader2")
|
||||
$prevReport.AddHeader("MyHeader3").AddHeader("MySubheader3").AddToolVersion("MyTool3", "14.2.1")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextSubHeader = $nextReport.AddHeader("MyHeader").AddHeader("MySubheader")
|
||||
$nextSubHeader.AddToolVersion("MyTool1", "2.1.4")
|
||||
$nextSubSubHeader = $nextSubHeader.AddHeader("MySubSubheader")
|
||||
$nextSubSubHeader.AddToolVersion("MyTool2", "2.9.1")
|
||||
$nextSubSubHeader.AddToolVersion("MyTool4", "2.7.6")
|
||||
$nextReport.AddHeader("MyHeader2")
|
||||
$nextReport.AddHeader("MyHeader3")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool4"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.7.6"
|
||||
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader", "MySubSubheader")
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
|
||||
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool3"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "14.2.1"
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader3", "MySubheader3")
|
||||
}
|
||||
}
|
||||
|
||||
Describe "ToolVersionNode" {
|
||||
It "ToolVersionNode is updated" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.4")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
|
||||
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader")
|
||||
}
|
||||
}
|
||||
|
||||
Describe "ToolVersionsListNode" {
|
||||
It "Single version is not changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Single version is changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.4"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.1.4")
|
||||
}
|
||||
|
||||
It "Major version is added" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.1.4")
|
||||
}
|
||||
|
||||
It "Major version is removed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Major version is changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.2.0"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0")
|
||||
}
|
||||
|
||||
It "Major version is added, removed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("1.0.0", "2.1.3", "3.1.4", "4.0.2"), "^\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.2.0", "4.0.2", "5.1.0"), "^\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("5.1.0")
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("1.0.0")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Minor version is added, removed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4", "2.8.0"), "^\d+\.\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.5.3", "2.6.2", "2.7.5", "2.8.0", "2.9.2", "2.10.3"), "^\d+\.\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.9.2", "2.10.3")
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.6.0", "2.7.4")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.6.2", "2.7.5")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.4.9")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Patch version is added, removed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4"), "^\d+\.\d+\.\d+")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.4.9", "2.5.4", "2.6.0", "2.7.5", "2.8.2"), "^\d+\.\d+\.\d+")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.5.4", "2.7.5", "2.8.2")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.5.3", "2.7.4")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Describe "TableNode" {
|
||||
It "Rows are added" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.AddedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.AddedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2")
|
||||
}
|
||||
|
||||
It "Rows are deleted" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("C1|C2", "D1|D2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.DeletedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.DeletedItems[0].CurrentReportNode.Rows | Should -BeArray @("C1|C2", "D1|D2")
|
||||
}
|
||||
|
||||
It "Rows are changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4")
|
||||
}
|
||||
|
||||
It "Rows are changed and updated at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4", "C1|C2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4", "C1|C2")
|
||||
}
|
||||
|
||||
It "Rows are changed and removed at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 1
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
|
||||
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2")
|
||||
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4")
|
||||
}
|
||||
|
||||
It "Rows are not changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Rows are not changed but header is changed" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
|
||||
It "Rows are changed and header is changed at the same time" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2", "C1|C2")))
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 1
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 1
|
||||
|
||||
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
|
||||
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value2"
|
||||
$comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2")
|
||||
|
||||
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
|
||||
$comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
|
||||
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
Describe "NoteNode" {
|
||||
It "NoteNode is ignored from report" {
|
||||
$prevReport = [HeaderNode]::new("Version 1")
|
||||
$prevReport.AddNote("MyFirstNote")
|
||||
$prevReport.AddHeader("MyFirstHeader").AddNote("MyFirstSubNote")
|
||||
|
||||
$nextReport = [HeaderNode]::new("Version 2")
|
||||
$nextReport.AddNote("MySecondNote")
|
||||
$nextReport.AddHeader("MySecondHeader").AddNote("MySecondSubNote")
|
||||
|
||||
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
|
||||
$comparer.CompareReports()
|
||||
|
||||
$comparer.AddedItems | Should -HaveCount 0
|
||||
$comparer.ChangedItems | Should -HaveCount 0
|
||||
$comparer.DeletedItems | Should -HaveCount 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
using module ../SoftwareReport.DifferenceRender.psm1
|
||||
|
||||
BeforeDiscovery {
|
||||
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
|
||||
}
|
||||
|
||||
Describe "ComparerReport.UnitTests" {
|
||||
BeforeAll {
|
||||
$script:DifferenceRender = [SoftwareReportDifferenceRender]::new()
|
||||
}
|
||||
|
||||
Context "CalculateHtmlTableRowSpan" {
|
||||
It "Without the equal cells" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "1" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "2" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "3" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
|
||||
$actual | Should -BeArray @(1, 1, 1)
|
||||
}
|
||||
|
||||
It "Only equal cells" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "D" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "D" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "D" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Value")
|
||||
$actual | Should -BeArray @(3, 0, 0)
|
||||
}
|
||||
|
||||
It "Single row" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "1" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
|
||||
$actual | Should -BeArray @(1)
|
||||
}
|
||||
|
||||
It "Different cells" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ Key = "A"; Value = "1" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "2" }
|
||||
[PSCustomObject]@{ Key = "B"; Value = "3" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "4" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "5" }
|
||||
[PSCustomObject]@{ Key = "C"; Value = "6" }
|
||||
[PSCustomObject]@{ Key = "D"; Value = "7" }
|
||||
[PSCustomObject]@{ Key = "E"; Value = "8" }
|
||||
[PSCustomObject]@{ Key = "E"; Value = "9" }
|
||||
[PSCustomObject]@{ Key = "F"; Value = "10" }
|
||||
)
|
||||
|
||||
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
|
||||
$actual | Should -BeArray @(1, 2, 0, 3, 0, 0, 1, 2, 0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderCategory" {
|
||||
It "With line separator" {
|
||||
$actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $true)
|
||||
$actual | Should -Be "Header 2 ><br> Header 3"
|
||||
}
|
||||
|
||||
It "Without line separator" {
|
||||
$actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $false)
|
||||
$actual | Should -Be "Header 2 > Header 3"
|
||||
}
|
||||
|
||||
It "One header" {
|
||||
$actual = $DifferenceRender.RenderCategory(@("Header 1"), $false)
|
||||
$actual | Should -Be ""
|
||||
}
|
||||
|
||||
It "Empty headers" {
|
||||
$actual = $DifferenceRender.RenderCategory(@(), $false)
|
||||
$actual | Should -Be ""
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderToolName" {
|
||||
It "Clear tool name" {
|
||||
$actual = $DifferenceRender.RenderToolName("My Tool 1")
|
||||
$actual | Should -Be "My Tool 1"
|
||||
}
|
||||
|
||||
It "Name with colon symbol" {
|
||||
$actual = $DifferenceRender.RenderToolName("My Tool 1:")
|
||||
$actual | Should -Be "My Tool 1"
|
||||
}
|
||||
}
|
||||
|
||||
Context "StrikeTableRow" {
|
||||
It "Simple row" {
|
||||
$actual = $DifferenceRender.StrikeTableRow("Test1|Test2|Test3")
|
||||
$actual | Should -Be "~~Test1~~|~~Test2~~|~~Test3~~"
|
||||
}
|
||||
|
||||
It "Row with spaces" {
|
||||
$actual = $DifferenceRender.StrikeTableRow("Test 1|Test 2|Test 3")
|
||||
$actual | Should -Be "~~Test 1~~|~~Test 2~~|~~Test 3~~"
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderHtmlTable" {
|
||||
It "Simple table" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" },
|
||||
[PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 2"; "Version" = "2.0" },
|
||||
[PSCustomObject]@{ "Category" = "C"; "Tool name" = "My Tool 3"; "Version" = "3.0" }
|
||||
)
|
||||
|
||||
$renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category")
|
||||
$renderedTable | Should -Be @'
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Version</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="1">A</td>
|
||||
<td>My Tool 1</td>
|
||||
<td>1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">B</td>
|
||||
<td>My Tool 2</td>
|
||||
<td>2.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">C</td>
|
||||
<td>My Tool 3</td>
|
||||
<td>3.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
'@
|
||||
|
||||
}
|
||||
|
||||
It "Table with the same category" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" },
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 2"; "Version" = "2.0" },
|
||||
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 3"; "Version" = "3.0" },
|
||||
[PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 4"; "Version" = "4.0" }
|
||||
)
|
||||
|
||||
$renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category")
|
||||
$renderedTable | Should -Be @'
|
||||
<table>
|
||||
<thead>
|
||||
<th>Category</th>
|
||||
<th>Tool name</th>
|
||||
<th>Version</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="3">A</td>
|
||||
<td>My Tool 1</td>
|
||||
<td>1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>My Tool 2</td>
|
||||
<td>2.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>My Tool 3</td>
|
||||
<td>3.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">B</td>
|
||||
<td>My Tool 4</td>
|
||||
<td>4.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
'@
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Context "RenderTableNodesDiff" {
|
||||
It "Add new table" {
|
||||
$previousNode = $null
|
||||
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ---- | ----- |
|
||||
| A | 1 |
|
||||
| B | 2 |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Remove existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$currentNode = $null
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~A~~ | ~~1~~ |
|
||||
| ~~B~~ | ~~2~~ |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Add new rows to existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ---- | ----- |
|
||||
| C | 3 |
|
||||
| D | 4 |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Remove rows from existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("C|3", "D|4"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~A~~ | ~~1~~ |
|
||||
| ~~B~~ | ~~2~~ |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Row is changed in existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|3"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~B~~ | ~~2~~ |
|
||||
| B | 3 |
|
||||
|
||||
'@
|
||||
}
|
||||
|
||||
It "Row is changed, added and removed at the same time in existing table" {
|
||||
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
|
||||
$currentNode = [TableNode]::new("Name|Value", @("B|2", "C|4", "D|4", "E|5"))
|
||||
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
|
||||
|
||||
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
|
||||
$actual | Should -Be @'
|
||||
#### Header 2 > Header 3
|
||||
| Name | Value |
|
||||
| ----- | ----- |
|
||||
| ~~A~~ | ~~1~~ |
|
||||
| ~~C~~ | ~~3~~ |
|
||||
| C | 4 |
|
||||
| E | 5 |
|
||||
|
||||
'@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using module ../SoftwareReport.psm1
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
|
||||
Describe "SoftwareReport.E2E" {
|
||||
Context "Report example 1" {
|
||||
BeforeEach {
|
||||
$softwareReport = [SoftwareReport]::new("macOS 11")
|
||||
$softwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7 (20G817)")
|
||||
$softwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
|
||||
$installedSoftware = $softwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
$languagesAndRuntimes = $installedSoftware.AddHeader("Language and Runtime")
|
||||
$languagesAndRuntimes.AddToolVersion("Bash", "5.1.16(1)-release")
|
||||
$languagesAndRuntimes.AddToolVersionsListInline(".NET Core SDK", @("1.2.100", "1.2.200", "3.1.414"), "^\d+\.\d+\.\d")
|
||||
$languagesAndRuntimes.AddNode([ToolVersionNode]::new("Perl", "5.34.0"))
|
||||
|
||||
$cachedTools = $installedSoftware.AddHeader("Cached Tools")
|
||||
$cachedTools.AddToolVersionsList("Ruby", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
|
||||
$cachedTools.AddToolVersionsList("Node.js", @("14.8.0", "15.1.0", "16.4.2"), "^\d+")
|
||||
|
||||
$javaSection = $installedSoftware.AddHeader("Java")
|
||||
$javaSection.AddTable(@(
|
||||
[PSCustomObject] @{ Version = "8.0.125"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_8_X64" },
|
||||
[PSCustomObject] @{ Version = "11.3.103"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_11_X64" }
|
||||
))
|
||||
|
||||
$sqlSection = $installedSoftware.AddHeader("MySQL")
|
||||
$sqlSection.AddToolVersion("MySQL", "6.1.0")
|
||||
$sqlSection.AddNote("MySQL service is disabled by default.`nUse the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'")
|
||||
|
||||
$expectedMarkdown = @'
|
||||
# macOS 11
|
||||
- OS Version: macOS 11.7 (20G817)
|
||||
- Image Version: 20220918.1
|
||||
|
||||
## Installed Software
|
||||
|
||||
### Language and Runtime
|
||||
- Bash 5.1.16(1)-release
|
||||
- .NET Core SDK: 1.2.100, 1.2.200, 3.1.414
|
||||
- Perl 5.34.0
|
||||
|
||||
### Cached Tools
|
||||
|
||||
#### Ruby
|
||||
- 2.7.3
|
||||
- 2.8.1
|
||||
- 3.1.2
|
||||
|
||||
#### Node.js
|
||||
- 14.8.0
|
||||
- 15.1.0
|
||||
- 16.4.2
|
||||
|
||||
### Java
|
||||
| Version | Vendor | Environment Variable |
|
||||
| -------- | --------- | -------------------- |
|
||||
| 8.0.125 | My Vendor | JAVA_HOME_8_X64 |
|
||||
| 11.3.103 | My Vendor | JAVA_HOME_11_X64 |
|
||||
|
||||
### MySQL
|
||||
- MySQL 6.1.0
|
||||
```
|
||||
MySQL service is disabled by default.
|
||||
Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'
|
||||
```
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown" {
|
||||
$softwareReport.ToMarkdown() | Should -Be $expectedMarkdown
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$json = $softwareReport.ToJson()
|
||||
$deserializedReport = [SoftwareReport]::FromJson($json)
|
||||
$deserializedReport.ToMarkdown() | Should -Be $expectedMarkdown
|
||||
}
|
||||
}
|
||||
|
||||
Context "GetImageVersion" {
|
||||
It "Image version exists" {
|
||||
$softwareReport = [SoftwareReport]::new("MyReport")
|
||||
$softwareReport.Root.AddToolVersion("Image Version:", "123.4")
|
||||
$softwareReport.GetImageVersion() | Should -Be "123.4"
|
||||
}
|
||||
|
||||
It "Empty report" {
|
||||
$softwareReport = [SoftwareReport]::new("MyReport")
|
||||
$softwareReport.GetImageVersion() | Should -Be "Unknown version"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
using module ../SoftwareReport.Nodes.psm1
|
||||
|
||||
BeforeDiscovery {
|
||||
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
|
||||
}
|
||||
|
||||
Describe "Nodes.UnitTests" {
|
||||
Context "ToolVersionNode" {
|
||||
It "ToMarkdown" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$node.ToMarkdown() | Should -Be "- MyTool 2.1.3"
|
||||
}
|
||||
|
||||
It "GetValue" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$node.GetValue() | Should -Be "2.1.3"
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "ToolVersionNode"
|
||||
$json.ToolName | Should -Be "MyTool"
|
||||
$json.Version | Should -Be "2.1.3"
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = ""; Version = "2.1.3" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*'
|
||||
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "" }) } | Should -Throw '*Exception setting "Version": "The argument is null or empty.*'
|
||||
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "2.1.3" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [ToolVersionNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeTrue
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeFalse
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
Context "ToolVersionsListNode" {
|
||||
It "ToMarkdown - List" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$expected = @(
|
||||
"",
|
||||
"# MyTool"
|
||||
"- 2.7.7"
|
||||
"- 3.0.5"
|
||||
"- 3.1.3"
|
||||
) -join "`n"
|
||||
$node.ToMarkdown() | Should -Be $expected
|
||||
}
|
||||
|
||||
It "ToMarkdown - Inline" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline")
|
||||
$node.ToMarkdown() | Should -Be "- MyTool: 2.7.7, 3.0.5, 3.1.3"
|
||||
}
|
||||
|
||||
It "GetValue" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$node.GetValue() | Should -Be "2.7.7, 3.0.5, 3.1.3"
|
||||
}
|
||||
|
||||
It "Serialization - List" {
|
||||
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "ToolVersionsListNode"
|
||||
$json.ToolName | Should -Be "Ruby"
|
||||
$json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3")
|
||||
$json.MajorVersionRegex | Should -Be "^.+"
|
||||
$json.ListType | Should -Be "List"
|
||||
}
|
||||
|
||||
It "Serialization - Inline" {
|
||||
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "ToolVersionsListNode"
|
||||
$json.ToolName | Should -Be "Ruby"
|
||||
$json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3")
|
||||
$json.MajorVersionRegex | Should -Be "^.+"
|
||||
$json.ListType | Should -Be "Inline"
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = ""; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null or empty.*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @(); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null, empty,*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", '2.2.4'); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw 'Multiple versions from list * return the same result from regex *'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = ""; ListType = "List" }) } | Should -Throw 'Version * doesn''t match regex *'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Fake" }) } | Should -Throw '*Exception setting "ListType": "The argument * does not belong to the set*'
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Not -Throw
|
||||
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Inline" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [ToolVersionsListNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeTrue
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List")
|
||||
) | Should -BeTrue
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
|
||||
[ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeTrue
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List")
|
||||
) | Should -BeFalse
|
||||
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
|
||||
[ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
|
||||
) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "ExtractMajorVersion" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+\.\d+", "List")
|
||||
$node.ExtractMajorVersion("2.1.3") | Should -Be "2.1"
|
||||
$node.ExtractMajorVersion("3.1.5") | Should -Be "3.1"
|
||||
$node.ExtractMajorVersion("4.0.0") | Should -Be "4.0"
|
||||
}
|
||||
|
||||
Context "ValidateMajorVersionRegex" {
|
||||
It "Major version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.3", "3.1.5", "4.0.0")
|
||||
}
|
||||
|
||||
It "Major version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "3.2.0", "4.0.0"), "^\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
|
||||
It "Minor version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.4.0", "3.1.2"), "^\d+\.\d+", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.3", "2.4.0", "3.1.2")
|
||||
}
|
||||
|
||||
It "Minor version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "3.1.2"), "^\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
|
||||
It "Patch version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.5"), "^\d+\.\d+\.\d+", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.3", "2.1.4", "2.1.5")
|
||||
}
|
||||
|
||||
It "Patch version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.4"), "^\d+\.\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
|
||||
It ".NET Core version regex - unique versions" {
|
||||
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.303"), "^\d+\.\d+\.\d", "List")
|
||||
$node.Versions | Should -BeArray @("2.1.100", "2.1.205", "2.1.303")
|
||||
}
|
||||
|
||||
It ".NET Core version regex - non-unique versions" {
|
||||
{ [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.230", "3.1.0"), "^\d+\.\d+\.\d", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context "TableNode" {
|
||||
Context "ToMarkdown" {
|
||||
It "Simple table" {
|
||||
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
|
||||
$node.ToMarkdown() | Should -Be @'
|
||||
| Name | Value |
|
||||
| ---- | ----- |
|
||||
| A | B |
|
||||
| C | D |
|
||||
'@
|
||||
}
|
||||
|
||||
It "Wide cells" {
|
||||
$node = [TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too"))
|
||||
$node.ToMarkdown() | Should -Be @'
|
||||
| Name | Value |
|
||||
| -------------------- | ---------------------------- |
|
||||
| Very long value here | B |
|
||||
| C | And very long value here too |
|
||||
'@
|
||||
}
|
||||
}
|
||||
|
||||
It "CalculateColumnsWidth" {
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).CalculateColumnsWidth() | Should -BeArray @(4, 5)
|
||||
[TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too")).CalculateColumnsWidth() | Should -BeArray @(20, 28)
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "TableNode"
|
||||
$json.Headers | Should -Be "Name|Value"
|
||||
$json.Rows | Should -BeArray @("A|B", "C|D")
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = ""; Rows = @("A|1", "B|2") }) } | Should -Throw 'Exception setting "Headers": "The argument is null or empty. *'
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @() }) } | Should -Throw 'Exception setting "Rows": "The argument is null, empty, *'
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2|T", "C|3") }) } | Should -Throw 'Table has different number of columns in different rows'
|
||||
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2") }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [TableNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeFalse
|
||||
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeFalse
|
||||
}
|
||||
|
||||
Context "FromObjectsArray" {
|
||||
It "Correct table" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B"}
|
||||
[PSCustomObject]@{Name = "C"; Value = "D"}
|
||||
)
|
||||
|
||||
$tableNode = [TableNode]::FromObjectsArray($table)
|
||||
$tableNode.Headers | Should -Be "Name|Value"
|
||||
$tableNode.Rows | Should -BeArray @("A|B", "C|D")
|
||||
}
|
||||
|
||||
It "Correct table with spaces" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A B"; "My Value" = "1 2"}
|
||||
[PSCustomObject]@{Name = "C D"; "My Value" = "3 4"}
|
||||
)
|
||||
|
||||
$tableNode = [TableNode]::FromObjectsArray($table)
|
||||
$tableNode.Headers | Should -Be "Name|My Value"
|
||||
$tableNode.Rows | Should -BeArray @("A B|1 2", "C D|3 4")
|
||||
}
|
||||
|
||||
It "Throw on empty table" {
|
||||
{ [TableNode]::FromObjectsArray(@()) } | Should -Throw "Failed to create TableNode from empty objects array"
|
||||
}
|
||||
|
||||
It "Throw on table with different columns" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B"}
|
||||
[PSCustomObject]@{Name = "C"; Value2 = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode from objects array because objects have different properties"
|
||||
}
|
||||
|
||||
It "Throw on empty row" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B"},
|
||||
[PSCustomObject]@{},
|
||||
[PSCustomObject]@{Name = "C"; Value2 = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some objects are empty"
|
||||
}
|
||||
|
||||
It "Throw on incorrect symbols in table column names" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{"Name|War" = "A"; Value = "B"}
|
||||
[PSCustomObject]@{"Name|War" = "C"; Value = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*"
|
||||
}
|
||||
|
||||
It "Throw on incorrect symbols in table rows" {
|
||||
$table = @(
|
||||
[PSCustomObject]@{Name = "A"; Value = "B|AA"}
|
||||
[PSCustomObject]@{Name = "C"; Value = "D"}
|
||||
)
|
||||
|
||||
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context "NoteNode" {
|
||||
It "ToMarkdown" {
|
||||
$node = [NoteNode]::new("Hello world`nGood Bye world")
|
||||
$node.ToMarkdown() | Should -Be @'
|
||||
```
|
||||
hello world
|
||||
Good Bye world
|
||||
```
|
||||
'@
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [NoteNode]::new("MyContent`nMyContent2")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "NoteNode"
|
||||
$json.Content | Should -Be "MyContent`nMyContent2"
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*'
|
||||
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*'
|
||||
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "MyTool" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [NoteNode]::new("MyContent`nMyContent2")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [NoteNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent")) | Should -BeTrue
|
||||
[NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent2")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent")) | Should -BeTrue
|
||||
[NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent2")) | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
Context "HeaderNode" {
|
||||
It "ToMarkdown" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.ToMarkdown(1) | Should -Be @'
|
||||
|
||||
# MyHeader
|
||||
- MyTool 2.1.3
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown (level 3)" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.ToMarkdown(3) | Should -Be @'
|
||||
|
||||
### MyHeader
|
||||
- MyTool 2.1.3
|
||||
'@
|
||||
}
|
||||
|
||||
It "ToMarkdown (multiple levels)" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddHeader("MyHeader 2").AddHeader("MyHeader 3").AddHeader("MyHeader 4").AddToolVersion("MyTool", "2.1.3")
|
||||
$node.ToMarkdown(1) | Should -Be @'
|
||||
|
||||
# MyHeader
|
||||
|
||||
## MyHeader 2
|
||||
|
||||
### MyHeader 3
|
||||
|
||||
#### MyHeader 4
|
||||
- MyTool 2.1.3
|
||||
'@
|
||||
}
|
||||
|
||||
It "Serialization" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$json.NodeType | Should -Be "HeaderNode"
|
||||
$json.Title | Should -Be "MyHeader"
|
||||
$json.Children | Should -HaveCount 1
|
||||
}
|
||||
|
||||
It "Deserialization" {
|
||||
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*'
|
||||
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*'
|
||||
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "MyHeader" }) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Serialization + Deserialization" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$json = $node.ToJsonObject()
|
||||
$node2 = [HeaderNode]::FromJsonObject($json)
|
||||
$json2 = $node2.ToJsonObject()
|
||||
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
|
||||
}
|
||||
|
||||
It "IsSimilarTo" {
|
||||
[HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader")) | Should -BeTrue
|
||||
[HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "IsIdenticalTo" {
|
||||
[HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader")) | Should -BeTrue
|
||||
[HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse
|
||||
}
|
||||
|
||||
It "FindSimilarChildNode" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
|
||||
$node.FindSimilarChildNode([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -Not -BeNullOrEmpty
|
||||
$node.FindSimilarChildNode([ToolVersionNode]::New("MyTool2", "1.0.0")) | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
Context "Detect node duplicates" {
|
||||
It "Similar HeaderNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddHeader("MySubHeader1")
|
||||
$node.AddHeader("MySubHeader2")
|
||||
{ $node.AddHeader("MySubHeader1") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar ToolVersionNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.AddToolVersion("MyTool2", "2.1.3")
|
||||
{ $node.AddToolVersion("MyTool", "2.1.3") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar ToolVersionsListNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersionsList("MyTool", @("2.1.3", "3.0.0"), "^\d+")
|
||||
$node.AddToolVersionsListInline("MyTool2", @("2.1.3", "3.0.0"), "^\d+")
|
||||
{ $node.AddToolVersionsList("MyTool", @("2.1.3", "3.0.0"), "^\d+") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar TableNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddTable(@(
|
||||
[PSCustomObject]@{Name = "Value1"},
|
||||
[PSCustomObject]@{Name = "Value2"}
|
||||
))
|
||||
{
|
||||
$node.AddTable(@(
|
||||
[PSCustomObject]@{Name = "Value1"},
|
||||
[PSCustomObject]@{Name = "Value2"}
|
||||
))
|
||||
} | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Similar NoteNode on the same header" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddNote("MyContent")
|
||||
$node.AddNote("MyContent2")
|
||||
{ $node.AddNote("MyContent") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "AddNode detects duplicates" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3"))
|
||||
{ $node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3")) } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "AddNodes detects duplicates" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddNodes(@(
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3"),
|
||||
[ToolVersionNode]::new("MyTool2", "2.1.4")
|
||||
))
|
||||
{
|
||||
$node.AddNodes(@(
|
||||
[ToolVersionNode]::new("MyTool3", "2.1.5"),
|
||||
[ToolVersionNode]::new("MyTool", "2.1.3")
|
||||
))
|
||||
} | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
|
||||
}
|
||||
|
||||
It "Doesn't allow adding non-header nodes after header node" {
|
||||
$node = [HeaderNode]::new("MyHeader")
|
||||
$node.AddToolVersion("MyTool", "2.1.3")
|
||||
$node.AddHeader("MySubHeader")
|
||||
{ $node.AddToolVersion("MyTool2", "2.1.4") } | Should -Throw "It is not allowed to add the node of type * to the HeaderNode that already contains the HeaderNode children."
|
||||
{ $node.AddHeader("MySubHeader2") } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
helpers/software-report-base/tests/TestHelpers.psm1
Normal file
34
helpers/software-report-base/tests/TestHelpers.psm1
Normal file
@@ -0,0 +1,34 @@
|
||||
function ShouldBeArray([Array] $ActualValue, [Array]$ExpectedValue, [Switch] $Negate, [String] $Because) {
|
||||
if ($Negate) {
|
||||
throw "Negation is not supported for Should-BeArray"
|
||||
}
|
||||
|
||||
if ($ExpectedValue.Count -eq 0) {
|
||||
throw "Expected array cannot be empty. Use Should-BeNullOrEmpty instead."
|
||||
}
|
||||
|
||||
$ExpectedValue | ForEach-Object {
|
||||
if ($_.GetType() -notin @([String], [Int32])) {
|
||||
throw "Only string or int arrays are supported in Should-BeArray"
|
||||
}
|
||||
}
|
||||
|
||||
$actualValueJson = $ActualValue | ConvertTo-Json
|
||||
$expectedValueJson = $ExpectedValue | ConvertTo-Json
|
||||
|
||||
$succeeded = ($ActualValue.Count -eq $ExpectedValue.Count) -and ($actualValueJson -eq $expectedValueJson)
|
||||
|
||||
if (-not $succeeded) {
|
||||
$failureMessage = "Expected array '$actualValueJson' to be equal to '$expectedValueJson'"
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
Add-ShouldOperator -Name BeArray `
|
||||
-InternalName 'ShouldBeArray' `
|
||||
-Test ${function:ShouldBeArray} `
|
||||
-SupportsArrayInput
|
||||
@@ -33,7 +33,7 @@ $installedSoftware = $softwareReport.Root.AddHeader("Installed Software")
|
||||
|
||||
# Language and Runtime
|
||||
$languageAndRuntime = $installedSoftware.AddHeader("Language and Runtime")
|
||||
$languageAndRuntime.AddToolVersionsList(".NET Core SDK", $(Get-DotnetVersionList), '^\d+\.\d+\.\d', $true)
|
||||
$languageAndRuntime.AddToolVersionsListInline(".NET Core SDK", $(Get-DotnetVersionList), '^\d+\.\d+\.\d')
|
||||
$languageAndRuntime.AddToolVersion("Bash", $(Get-BashVersion))
|
||||
$languageAndRuntime.AddNodes($(Get-ClangLLVMVersions))
|
||||
$languageAndRuntime.AddNodes($(Get-GccVersions))
|
||||
@@ -45,7 +45,7 @@ $languageAndRuntime.AddToolVersion("Mono", $(Get-MonoVersion))
|
||||
$languageAndRuntime.AddToolVersion("MSBuild", $(Get-MSBuildVersion))
|
||||
$languageAndRuntime.AddToolVersion("Node.js", $(Get-NodeVersion))
|
||||
$languageAndRuntime.AddToolVersion("NVM", $(Get-NVMVersion))
|
||||
$languageAndRuntime.AddToolVersionsList("NVM - Cached node versions", $(Get-NVMNodeVersionList), '^\d+', $true)
|
||||
$languageAndRuntime.AddToolVersionsListInline("NVM - Cached node versions", $(Get-NVMNodeVersionList), '^\d+')
|
||||
$languageAndRuntime.AddToolVersion("Perl", $(Get-PerlVersion))
|
||||
$languageAndRuntime.AddToolVersion("PHP", $(Get-PHPVersion))
|
||||
$languageAndRuntime.AddToolVersion("Python", $(Get-PythonVersion))
|
||||
|
||||
@@ -35,11 +35,11 @@ function Get-ToolcacheGoVersions {
|
||||
|
||||
function Build-ToolcacheSection {
|
||||
return @(
|
||||
[ToolVersionsListNode]::new("Ruby", $(Get-ToolcacheRubyVersions), '^\d+\.\d+', $false),
|
||||
[ToolVersionsListNode]::new("Python", $(Get-ToolcachePythonVersions), '^\d+\.\d+', $false),
|
||||
[ToolVersionsListNode]::new("PyPy", $(Get-ToolcachePyPyVersions), '^\d+\.\d+', $false),
|
||||
[ToolVersionsListNode]::new("Node.js", $(Get-ToolcacheNodeVersions), '^\d+', $false),
|
||||
[ToolVersionsListNode]::new("Go", $(Get-ToolcacheGoVersions), '^\d+\.\d+', $false)
|
||||
[ToolVersionsListNode]::new("Ruby", $(Get-ToolcacheRubyVersions), '^\d+\.\d+', "List"),
|
||||
[ToolVersionsListNode]::new("Python", $(Get-ToolcachePythonVersions), '^\d+\.\d+', "List"),
|
||||
[ToolVersionsListNode]::new("PyPy", $(Get-ToolcachePyPyVersions), '^\d+\.\d+', "List"),
|
||||
[ToolVersionsListNode]::new("Node.js", $(Get-ToolcacheNodeVersions), '^\d+', "List"),
|
||||
[ToolVersionsListNode]::new("Go", $(Get-ToolcacheGoVersions), '^\d+\.\d+', "List")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,6 @@ function Get-PowerShellModules {
|
||||
$modules | ForEach-Object {
|
||||
$moduleName = $_
|
||||
$moduleVersions = Get-Module -Name $moduleName -ListAvailable | Select-Object -ExpandProperty Version | Sort-Object -Unique
|
||||
return [ToolVersionsListNode]::new($moduleName, $moduleVersions, '^\d+', $true)
|
||||
return [ToolVersionsListNode]::new($moduleName, $moduleVersions, '^\d+', "Inline")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user