Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
*.png binary
*.dll binary
*.so binary
*.zip binary
*.zip binary -eol
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,24 +163,32 @@ does not fit the data.

## Conformance corpus

The offline test gate runs the complete released `yaml-test-suite` data corpus
at commit `6ad3d2c62885d82fc349026c136ef560838fdf3d` (generated from source
commit `45db50ae`). The pinned archive contains 402 inputs:

- all 94 fixtures marked invalid are rejected;
- 306 of 308 fixtures marked valid are accepted;
- the other two valid-syntax fixtures, `2JQS` and `X38W`, are deliberately
rejected as a load/composition policy after syntactic recognition because this
module rejects duplicate mapping keys in the representation graph;
- 282 fixtures include `in.json`; three belong to invalid inputs, 277 of the
279 applicable constructions match exactly, and two use a different
documented projection policy.

The two construction-policy differences are `565N`, where this module
constructs `!!binary` as `byte[]` instead of a base64 string, and `J7PZ`, where
the explicitly supported `!!omap` tag becomes an ordered dictionary instead of
remaining a sequence of one-entry mappings. The deterministic runner reports
398 passing cases, four policy exclusions, and no unexplained failures.
The offline test gate runs the latest official `yaml-test-suite` source release,
`v2022-01-17`, at commit `45db50aecf9b1520f8258938c88f396e96f30831`.
Its `data-2022-01-17` export is pinned at commit
`6e6c296ae9c9d2d5c4134b4b64d01b29ac19ff6f` with archive SHA-256
`47C173AFFEB480517B30FB77DC8C76FD48609B9B65DD1C1D3D0D0BAEE48D6AA9`.
The archive contains 402 inputs:

| Surface | Pass | PolicyDifference | Fail | NotApplicable |
| --- | ---: | ---: | ---: | ---: |
| Syntax and composition | 400 | 2 | 0 | 0 |
| Representation events | 308 | 0 | 0 | 94 |
| JSON projection | 277 | 2 | 0 | 123 |
| `out.yaml` projection | 241 | 0 | 0 | 161 |
| Emit and round trip | 306 | 0 | 0 | 96 |

All 94 fixtures marked invalid are rejected. The valid `2JQS` and `X38W`
inputs are syntactically recognized and produce matching representation
events, then are rejected during load validation because representation
mapping keys must be unique. They are not unsupported grammar.

The two JSON projection differences are `565N`, where `!!binary` intentionally
becomes `byte[]` instead of a Base64 string, and `J7PZ`, where legacy `!!omap`
intentionally becomes `System.Collections.Specialized.OrderedDictionary`
instead of remaining a sequence of one-entry mappings. No event mismatch is
classified as policy. The deterministic runner reports no unexplained
failures.

These results are a pinned compatibility measurement, not a claim that a finite
corpus proves complete YAML 1.2.2 compliance.
Expand Down
94 changes: 83 additions & 11 deletions tests/Conformance.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@ param()

BeforeAll {
$archivePath = Join-Path $PSScriptRoot 'fixtures\yaml-test-suite\yaml-test-suite-data-2022-01-17.zip'
$sourcesPath = Join-Path $PSScriptRoot 'fixtures\yaml-test-suite\SOURCES.txt'
$repositoryRoot = Split-Path -Parent $PSScriptRoot
$suitePath = Join-Path $TestDrive 'yaml-test-suite'
. (Join-Path $PSScriptRoot 'TestBootstrap.ps1')
$conformanceYamlModule = $yamlModule
Expand-Archive -LiteralPath $archivePath -DestinationPath $suitePath
$suiteRoots = @(
Get-ChildItem -LiteralPath $suitePath -Directory
)
if ($suiteRoots.Count -ne 1) {
throw "Expected one release archive root, but found $($suiteRoots.Count)."
}
$suiteDataPath = $suiteRoots[0].FullName
$suiteResults = @(
& (Join-Path $PSScriptRoot 'tools\Invoke-YamlTestSuite.ps1') `
-Path $suitePath `
-Path $suiteDataPath `
-CompareJson `
-CompareEvents `
-CompareOutYaml `
Expand All @@ -26,26 +37,48 @@ BeforeAll {
}

Describe 'Released yaml-test-suite corpus accounting' {
It 'uses the built module for conformance in GitHub Actions' {
if ($env:GITHUB_ACTIONS -eq 'true') {
$conformanceYamlModule | Should -Not -BeNullOrEmpty
$command = Get-Command -Name ConvertFrom-Yaml
$command.Module | Should -Be $conformanceYamlModule
$conformanceYamlModule.ModuleBase |
Should -Not -Be (Join-Path $repositoryRoot 'src')
$conformanceYamlModule.PowerShellVersion | Should -Be '7.6'
@($conformanceYamlModule.CompatiblePSEditions) | Should -Be @('Core')
}
}

It 'uses the pinned unmodified release archive' {
(Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash |
Should -Be 'DCC1F037B13F6C3032D5190C447B6A6EF5560738A4C104F29B4243B0AB8F8029'
Should -Be '47C173AFFEB480517B30FB77DC8C76FD48609B9B65DD1C1D3D0D0BAEE48D6AA9'
$suiteResults.Count | Should -Be 402
}

It 'accounts for syntax acceptance, rejection, and policy differences' {
It 'pins the latest official source and data release attribution' {
$sources = Get-Content -LiteralPath $sourcesPath -Raw

$sources | Should -Match 'Latest source release:\s+v2022-01-17'
$sources | Should -Match '45db50aecf9b1520f8258938c88f396e96f30831'
$sources | Should -Match '6e6c296ae9c9d2d5c4134b4b64d01b29ac19ff6f'
}

It 'accounts for syntax recognition and representation-key load policy' {
@($suiteResults | Where-Object SyntaxResult -EQ 'Pass').Count | Should -Be 400
@($suiteResults | Where-Object SyntaxResult -EQ 'PolicyDifference').Count |
Should -Be 2
@($suiteResults | Where-Object SyntaxResult -EQ 'Fail').Count | Should -Be 0
@($suiteResults | Where-Object SyntaxResult -EQ 'NotApplicable').Count |
Should -Be 0
$syntaxPolicyCases = @(
$policyResults = @(
$suiteResults |
Where-Object SyntaxResult -EQ 'PolicyDifference' |
Select-Object -ExpandProperty Case |
Sort-Object
Sort-Object Case
)
$syntaxPolicyCases | Should -Be @('2JQS', 'X38W')
@($policyResults.Case) | Should -Be @('2JQS', 'X38W')
@($policyResults.SyntaxReason | Select-Object -Unique) |
Should -Be @('RepresentationMappingKeyUniqueness')
@($policyResults | Where-Object EventResult -NE 'Pass').Count | Should -Be 0
}

It 'accounts for parser representation/event comparisons' {
Expand All @@ -64,13 +97,52 @@ Describe 'Released yaml-test-suite corpus accounting' {
@($suiteResults | Where-Object JsonResult -EQ 'Fail').Count | Should -Be 0
@($suiteResults | Where-Object JsonResult -EQ 'NotApplicable').Count |
Should -Be 123
$jsonPolicyCases = @(
$policyResults = @(
$suiteResults |
Where-Object JsonResult -EQ 'PolicyDifference' |
Select-Object -ExpandProperty Case |
Sort-Object
Sort-Object Case
)
@($policyResults.Case) | Should -Be @('565N', 'J7PZ')
@($policyResults.JsonReason) | Should -Be @(
'BinaryByteArrayProjection',
'LegacyOrderedMapProjection'
)
$jsonPolicyCases | Should -Be @('565N', 'J7PZ')
}

It 'does not mask altered JSON values as projection policy' {
$mutatedSuitePath = Join-Path $TestDrive 'mutated-policy-cases'
$null = New-Item -Path $mutatedSuitePath -ItemType Directory -Force
foreach ($case in @('565N', 'J7PZ')) {
Copy-Item -LiteralPath (Join-Path $suiteDataPath $case) `
-Destination $mutatedSuitePath -Recurse
}

$binaryJsonPath = Join-Path $mutatedSuitePath '565N\in.json'
$binaryJson = Get-Content -LiteralPath $binaryJsonPath -Raw |
ConvertFrom-Json -AsHashtable
$binaryJson['description'] = 'Altered expected value'
$binaryJson | ConvertTo-Json -Depth 10 |
Set-Content -LiteralPath $binaryJsonPath -Encoding utf8NoBOM

$orderedMapJsonPath = Join-Path $mutatedSuitePath 'J7PZ\in.json'
$orderedMapJson = Get-Content -LiteralPath $orderedMapJsonPath -Raw |
ConvertFrom-Json -AsHashtable
$orderedMapJson[0]['Mark McGwire'] = 66
$orderedMapJson | ConvertTo-Json -Depth 10 |
Set-Content -LiteralPath $orderedMapJsonPath -Encoding utf8NoBOM

$mutatedResults = @(
& (Join-Path $PSScriptRoot 'tools\Invoke-YamlTestSuite.ps1') `
-Path $mutatedSuitePath `
-CompareJson
)
@($mutatedResults | Sort-Object Case | Select-Object -ExpandProperty Case) |
Should -Be @('565N', 'J7PZ')
@($mutatedResults | Where-Object JsonResult -EQ 'PolicyDifference').Count |
Should -Be 0
@($mutatedResults | Where-Object JsonResult -EQ 'Fail').Count | Should -Be 2
@($mutatedResults.JsonReason | Select-Object -Unique) |
Should -Be @('ConstructedValueMismatch')
}

It 'accounts for out.yaml representation comparisons' {
Expand Down
42 changes: 27 additions & 15 deletions tests/ConvertFrom-Yaml.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,21 @@ mapping: !<tag:example.test,2026:object>
$result.mapping.name | Should -Be 'safe'
}

It 'retains unknown local and global tags before neutral value projection' {
$local = Get-TestYamlRepresentationRoot -Yaml '!local value'
$global = Get-TestYamlRepresentationRoot -Yaml (
'!<tag:example.test,2026:object> value'
)

$local.Tag | Should -Be '!local'
$local.HasUnknownTag | Should -BeTrue
$global.Tag | Should -Be 'tag:example.test,2026:object'
$global.HasUnknownTag | Should -BeTrue
('!local value' | ConvertFrom-Yaml) | Should -Be 'value'
('!<tag:example.test,2026:object> value' | ConvertFrom-Yaml) |
Should -Be 'value'
}

It 'preserves repeated collection references' {
$result = @'
source: &source
Expand Down Expand Up @@ -364,21 +379,18 @@ date: !!timestamp 2001-12-14
}

It 'decodes percent escapes in expanded tags using UTF-8' {
$yaml = @'
%TAG !e! tag:example.com,2000:app/
---
first: !e!tag%21 value
second: !e!currency%E2%82%AC amount
'@
$representation = Read-YamlStreamCore -Yaml $yaml -Depth 32 -MaxNodes 64 -MaxAliases 16 `
-MaxScalarLength 4096 -MaxTagLength 1024 -MaxTotalTagLength 4096 `
-MaxNumericLength 64

$representation.Value.Count | Should -Be 1
$entries = $representation.Value[0].Entries
$entries.Count | Should -Be 2
$entries[0].Value.Tag | Should -Be 'tag:example.com,2000:app/tag!'
$entries[1].Value.Tag | Should -Be ('tag:example.com,2000:app/currency' + [char] 0x20AC)
$escaped = Get-TestYamlRepresentationRoot -Yaml (
"%TAG !e! tag:example.com,2000:app/`n--- !e!tag%21 value"
)
$multibyte = Get-TestYamlRepresentationRoot -Yaml (
"%TAG !e! tag:example.com,2000:app/`n--- !e!currency%E2%82%AC amount"
)

$escaped.Tag | Should -Be 'tag:example.com,2000:app/tag!'
$escaped.HasUnknownTag | Should -BeTrue
$multibyte.Tag |
Should -Be ('tag:example.com,2000:app/currency' + [char] 0x20AC)
$multibyte.HasUnknownTag | Should -BeTrue
}

It 'rejects malformed or non-UTF8 tag percent escapes' {
Expand Down
Loading
Loading