|
11 | 11 | [CmdletBinding()] |
12 | 12 | param() |
13 | 13 |
|
14 | | -Describe 'Packaged dependency' { |
15 | | - BeforeAll { |
16 | | - $repositoryRoot = Split-Path -Parent $PSScriptRoot |
17 | | - } |
| 14 | +$importedYamlCommand = Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue |
| 15 | +$skipArtifactTests = [string]::IsNullOrWhiteSpace($env:PSMODULE_YAML_TEST_ARTIFACT) -and ( |
| 16 | + $null -eq $importedYamlCommand -or $importedYamlCommand.ModuleName -ne 'Yaml' |
| 17 | +) |
18 | 18 |
|
19 | | - It 'bundles the exact YamlDotNet 18.1.0 netstandard2.0 assembly' { |
20 | | - $hash = Get-FileHash -Path ( |
21 | | - Join-Path $repositoryRoot 'src\assemblies\YamlDotNet.dll' |
22 | | - ) -Algorithm SHA256 |
| 19 | +BeforeAll { |
| 20 | + . (Join-Path $PSScriptRoot 'TestBootstrap.ps1') |
| 21 | + $repositoryRoot = Split-Path -Parent $PSScriptRoot |
| 22 | + $loadedYamlModule = if (-not [string]::IsNullOrWhiteSpace( |
| 23 | + $env:PSMODULE_YAML_TEST_ARTIFACT |
| 24 | + )) { |
| 25 | + Import-Module -Name $env:PSMODULE_YAML_TEST_ARTIFACT -Force -Global -PassThru | |
| 26 | + Where-Object Name -EQ 'Yaml' | |
| 27 | + Select-Object -First 1 |
| 28 | + } else { |
| 29 | + $command = Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue |
| 30 | + if ($null -ne $command -and $command.ModuleName -eq 'Yaml') { |
| 31 | + $command.Module |
| 32 | + } |
| 33 | + } |
| 34 | + $artifactManifestPath = if ($null -ne $loadedYamlModule) { |
| 35 | + Join-Path $loadedYamlModule.ModuleBase 'Yaml.psd1' |
| 36 | + } else { |
| 37 | + $null |
| 38 | + } |
| 39 | +} |
23 | 40 |
|
24 | | - $hash.Hash | Should -Be '91CD6D1FD0AE5B64BF6B252EB3B1AF2382CBC599C29045427C45FE8403D4295D' |
| 41 | +Describe 'Dependency-free package source' { |
| 42 | + It 'contains no external parser assembly, license, or notice payload' { |
| 43 | + @( |
| 44 | + Get-ChildItem -Path (Join-Path $repositoryRoot 'src\assemblies') ` |
| 45 | + -File -ErrorAction SilentlyContinue |
| 46 | + ).Count | Should -Be 0 |
| 47 | + Test-Path (Join-Path $repositoryRoot 'src\licenses\YamlDotNet.LICENSE.txt') | Should -BeFalse |
| 48 | + Test-Path (Join-Path $repositoryRoot 'src\THIRD-PARTY-NOTICES.txt') | Should -BeFalse |
25 | 49 | } |
26 | 50 |
|
27 | 51 | It 'keeps RequiredAssemblies out of the source manifest' { |
28 | 52 | $manifest = Import-PowerShellDataFile -Path ( |
29 | 53 | Join-Path $repositoryRoot 'src\manifest.psd1' |
30 | 54 | ) |
31 | 55 |
|
32 | | - $manifest.DotNetFrameworkVersion | Should -Be '4.7.2' |
| 56 | + $manifest.ContainsKey('DotNetFrameworkVersion') | Should -BeFalse |
33 | 57 | $manifest.ContainsKey('RequiredAssemblies') | Should -BeFalse |
34 | 58 | } |
35 | 59 |
|
36 | | - It 'packages the upstream license and dependency provenance' { |
37 | | - $license = Get-Content -Path ( |
38 | | - Join-Path $repositoryRoot 'src\licenses\YamlDotNet.LICENSE.txt' |
39 | | - ) -Raw |
40 | | - $notice = Get-Content -Path ( |
41 | | - Join-Path $repositoryRoot 'src\THIRD-PARTY-NOTICES.txt' |
42 | | - ) -Raw |
| 60 | + It 'contains no external parser references or custom assembly loader' { |
| 61 | + $sourceFiles = Get-ChildItem -Path (Join-Path $repositoryRoot 'src') -Recurse -File |
| 62 | + $source = $sourceFiles | |
| 63 | + Where-Object Extension -In @('.ps1', '.psd1', '.psm1') | |
| 64 | + Get-Content -Raw |
| 65 | + |
| 66 | + $source | Should -Not -Match 'YamlDotNet' |
| 67 | + $source | Should -Not -Match '\bAdd-Type\b' |
| 68 | + $source | Should -Not -Match 'Assembly\]::Load' |
| 69 | + } |
43 | 70 |
|
44 | | - $license | Should -Match 'Copyright \(c\) 2008, 2009, 2010' |
45 | | - $license | Should -Match 'Permission is hereby granted, free of charge' |
46 | | - $notice | Should -Match 'YamlDotNet 18\.1\.0' |
47 | | - $notice | Should -Match 'lib/netstandard2\.0/YamlDotNet\.dll' |
48 | | - $notice | Should -Match '59FFE65ADE67AD9D886267F877B634A450363CB81B94E19DB9CA4C36461416F6' |
49 | | - $notice | Should -Match '91CD6D1FD0AE5B64BF6B252EB3B1AF2382CBC599C29045427C45FE8403D4295D' |
| 71 | + It 'keeps the owned processor layers explicit and source-level' { |
| 72 | + $privatePath = Join-Path $repositoryRoot 'src\functions\private' |
| 73 | + @( |
| 74 | + 'New-YamlReaderContext.ps1', |
| 75 | + 'Read-YamlDirectiveBlock.ps1', |
| 76 | + 'New-YamlSyntaxNode.ps1', |
| 77 | + 'ConvertFrom-YamlSyntaxTree.ps1', |
| 78 | + 'Resolve-YamlScalar.ps1', |
| 79 | + 'ConvertFrom-YamlNode.ps1', |
| 80 | + 'Get-YamlSerializationShape.ps1', |
| 81 | + 'ConvertTo-YamlNode.ps1', |
| 82 | + 'Write-YamlNodeText.ps1' |
| 83 | + ) | ForEach-Object { |
| 84 | + Test-Path -LiteralPath (Join-Path $privatePath $_) | |
| 85 | + Should -BeTrue -Because "$_ defines a required processor layer" |
| 86 | + } |
50 | 87 | } |
51 | 88 |
|
52 | | - It 'uses Process-PSModule 6.1.4 and treats tests as important changes' { |
| 89 | + It 'uses Process-PSModule 6.1.13 and treats tests as important changes' { |
53 | 90 | $workflow = Get-Content -Path ( |
54 | 91 | Join-Path $repositoryRoot '.github\workflows\Process-PSModule.yml' |
55 | 92 | ) -Raw |
56 | 93 |
|
57 | | - $workflow | Should -Match 'workflow\.yml@da180bac16b13bfbcdf08b2e4e221b5b49e5ff28 # v6\.1\.4' |
| 94 | + $workflow | Should -Match 'workflow\.yml@fb1bdb8fefd243292f779d2a856a38db6fe6daf4 # v6\.1\.13' |
58 | 95 | $workflow | Should -Match '\^src/' |
59 | 96 | $workflow | Should -Match '\^tests/' |
60 | 97 | } |
61 | 98 |
|
62 | | - It 'does not add a custom assembly loader to module source' { |
63 | | - $source = Get-ChildItem -Path (Join-Path $repositoryRoot 'src') -Recurse -File | |
64 | | - Where-Object Extension -EQ '.ps1' | |
65 | | - Get-Content -Raw |
| 99 | + It 'does not skip generated documentation' { |
| 100 | + $configuration = Get-Content -Path ( |
| 101 | + Join-Path $repositoryRoot '.github\PSModule.yml' |
| 102 | + ) -Raw |
66 | 103 |
|
67 | | - $source | Should -Not -Match '\bAdd-Type\b' |
68 | | - $source | Should -Not -Match 'Assembly\]::Load' |
| 104 | + $configuration | Should -Not -Match '(?ms)Build:\s+Docs:\s+.*Skip:\s*true' |
| 105 | + } |
| 106 | + |
| 107 | + It 'skips site build until zensical.toml is adopted' { |
| 108 | + $configuration = Get-Content -Path ( |
| 109 | + Join-Path $repositoryRoot '.github\PSModule.yml' |
| 110 | + ) -Raw |
| 111 | + |
| 112 | + $configuration | Should -Match '(?ms)Build:\s+Site:\s+.*Skip:\s*true' |
| 113 | + Test-Path -Path (Join-Path $repositoryRoot 'zensical.toml') | Should -BeFalse |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +Describe 'Generated artifact package' { |
| 118 | + It 'has no RequiredAssemblies or packaged DLL and has a complete FileList' ` |
| 119 | + -Skip:$skipArtifactTests { |
| 120 | + $manifest = Import-PowerShellDataFile -Path $artifactManifestPath |
| 121 | + $moduleBase = Split-Path -Parent $artifactManifestPath |
| 122 | + |
| 123 | + $manifest.ContainsKey('RequiredAssemblies') | Should -BeFalse |
| 124 | + $manifest.ContainsKey('DotNetFrameworkVersion') | Should -BeFalse |
| 125 | + @($manifest.FunctionsToExport | Sort-Object) | |
| 126 | + Should -Be @('ConvertFrom-Yaml', 'ConvertTo-Yaml', 'Test-Yaml') |
| 127 | + @($manifest.FileList) | Should -Contain 'Yaml.psm1' |
| 128 | + @($manifest.FileList | Where-Object { $_ -match '\.(?:dll|exe)$' }).Count | Should -Be 0 |
| 129 | + @($manifest.FileList | Where-Object { $_ -match 'THIRD-PARTY|YamlDotNet' }).Count | Should -Be 0 |
| 130 | + @(Get-ChildItem -Path $moduleBase -Recurse -File -Filter '*.dll').Count | Should -Be 0 |
| 131 | + { Test-ModuleManifest -Path $artifactManifestPath } | Should -Not -Throw |
| 132 | + } |
| 133 | + |
| 134 | + It 'imports in a fresh PowerShell process beside an existing YamlDotNet identity' ` |
| 135 | + -Skip:($skipArtifactTests -or $null -eq (Get-Command pwsh -ErrorAction SilentlyContinue)) { |
| 136 | + $script = @' |
| 137 | +$ErrorActionPreference = 'Stop' |
| 138 | +$name = [System.Reflection.AssemblyName]::new('YamlDotNet') |
| 139 | +try { |
| 140 | + $null = [System.Reflection.Emit.AssemblyBuilder]::DefineDynamicAssembly( |
| 141 | + $name, |
| 142 | + [System.Reflection.Emit.AssemblyBuilderAccess]::Run |
| 143 | + ) |
| 144 | +} catch { |
| 145 | + $null = [AppDomain]::CurrentDomain.DefineDynamicAssembly( |
| 146 | + $name, |
| 147 | + [System.Reflection.Emit.AssemblyBuilderAccess]::Run |
| 148 | + ) |
| 149 | +} |
| 150 | +Import-Module -Name '__MANIFEST__' -Force |
| 151 | +$value = 'v: []' | ConvertFrom-Yaml -AsHashtable |
| 152 | +if ($value['v'] -isnot [object[]] -or $value['v'].Count -ne 0) { |
| 153 | + throw 'The empty sequence did not survive import.' |
| 154 | +} |
| 155 | +if (-not ('name: Ada' | Test-Yaml)) { |
| 156 | + throw 'The imported parser did not validate YAML.' |
| 157 | +} |
| 158 | +$shared = [ordered]@{ value = 1 } |
| 159 | +$roundTrip = [ordered]@{ first = $shared; second = $shared } | |
| 160 | + ConvertTo-Yaml | |
| 161 | + ConvertFrom-Yaml -AsHashtable |
| 162 | +if (-not [object]::ReferenceEquals($roundTrip['first'], $roundTrip['second'])) { |
| 163 | + throw 'The fresh-process graph round trip lost alias identity.' |
| 164 | +} |
| 165 | +'coexistence-ok' |
| 166 | +'@.Replace('__MANIFEST__', $artifactManifestPath.Replace("'", "''")) |
| 167 | + |
| 168 | + (& pwsh -NoLogo -NoProfile -Command $script) | Should -Contain 'coexistence-ok' |
| 169 | + $LASTEXITCODE | Should -Be 0 |
| 170 | + } |
| 171 | + |
| 172 | + It 'preserves internal arrays and aliases in a fresh Windows PowerShell 5.1 process' ` |
| 173 | + -Skip:($skipArtifactTests -or $null -eq (Get-Command powershell.exe -ErrorAction SilentlyContinue)) { |
| 174 | + $script = @' |
| 175 | +$ErrorActionPreference = 'Stop' |
| 176 | +$name = [System.Reflection.AssemblyName]::new('YamlDotNet') |
| 177 | +$null = [AppDomain]::CurrentDomain.DefineDynamicAssembly( |
| 178 | + $name, |
| 179 | + [System.Reflection.Emit.AssemblyBuilderAccess]::Run |
| 180 | +) |
| 181 | +Import-Module -Name '__MANIFEST__' -Force |
| 182 | +$empty = 'v: []' | ConvertFrom-Yaml -AsHashtable |
| 183 | +if ($empty['v'] -isnot [object[]] -or $empty['v'].Count -ne 0) { |
| 184 | + throw 'The empty sequence was corrupted.' |
| 185 | +} |
| 186 | +$binary = "a: &x !!binary SGVsbG8=`nb: *x" | ConvertFrom-Yaml -AsHashtable |
| 187 | +if ($binary['a'] -isnot [byte[]] -or -not [object]::ReferenceEquals($binary['a'], $binary['b'])) { |
| 188 | + throw 'The binary value or alias identity was corrupted.' |
| 189 | +} |
| 190 | +$sequence = "a: &x []`nb: *x" | ConvertFrom-Yaml -AsHashtable |
| 191 | +if ($sequence['a'] -isnot [object[]] -or |
| 192 | + -not [object]::ReferenceEquals($sequence['a'], $sequence['b'])) { |
| 193 | + throw 'The sequence alias identity was corrupted.' |
| 194 | +} |
| 195 | +$recursive = 'a: &x [*x]' | ConvertFrom-Yaml -AsHashtable |
| 196 | +if (-not [object]::ReferenceEquals($recursive['a'], $recursive['a'][0])) { |
| 197 | + throw 'The recursive alias was corrupted.' |
| 198 | +} |
| 199 | +$negativeZero = [BitConverter]::Int64BitsToDouble([long]::MinValue) |
| 200 | +if ((ConvertTo-Yaml -InputObject $negativeZero).Trim() -ne '-0.0') { |
| 201 | + throw 'The negative-zero sign was lost.' |
| 202 | +} |
| 203 | +$flow = ('[' * 127) + 'null' + (']' * 127) |
| 204 | +if (-not (Test-Yaml -Yaml $flow -Depth 128 -MaxNodes 200)) { |
| 205 | + throw 'The public maximum parse depth failed.' |
| 206 | +} |
| 207 | +$atLimit = [ordered]@{} |
| 208 | +$current = $atLimit |
| 209 | +for ($level = 1; $level -lt 127; $level++) { |
| 210 | + $next = [ordered]@{} |
| 211 | + $current['nested'] = $next |
| 212 | + $current = $next |
| 213 | +} |
| 214 | +$current['value'] = 1 |
| 215 | +$deepYaml = ConvertTo-Yaml -InputObject $atLimit -Depth 128 -MaxNodes 300 |
| 216 | +if (-not (Test-Yaml -Yaml $deepYaml -Depth 128 -MaxNodes 300)) { |
| 217 | + throw 'The public maximum serialization depth failed.' |
| 218 | +} |
| 219 | +'windows-powershell-ok' |
| 220 | +'@.Replace('__MANIFEST__', $artifactManifestPath.Replace("'", "''")) |
| 221 | + |
| 222 | + (& powershell.exe -NoLogo -NoProfile -Command $script) | |
| 223 | + Should -Contain 'windows-powershell-ok' |
| 224 | + $LASTEXITCODE | Should -Be 0 |
69 | 225 | } |
70 | 226 | } |
0 commit comments