test(template): compare plain scalar typing with the reference YAML library
- commit
- 6ecb01e6dd9ef9388b7abd30b22bb8a61339774b
- author
- brice · · 2026-10-12 16:00 UTC
- committer
- brice · · 2026-10-12 16:17 UTC
- parent
- b5f3fab
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +76 -3
M template/PORTING.md +4 -3
| @@ -19,9 +19,10 @@ available. Nothing here has been checked against it. | |||
| 19 | 19 | core schema: `yes`/`no`/`on`/`off` are strings, `0o17` and `0x1F` are | |
| 20 | 20 | numbers, `.inf` and `.nan` are numbers, `0b11`, `1_000` and sexagesimal | |
| 21 | 21 | numbers are strings. Quoted, block and `!!str` scalars are strings. | |
| 22 | - | Verify: octal `017` (1.2 reads 17), timestamps, `!!binary`, merge keys `<<` | |
| 23 | - | (yaml.v3 resolves them when decoding but not in the Node API; they are kept | |
| 24 | - | as an ordinary key `<<`, as in the reference). | |
| 22 | + | `TestPlainScalarsAgainstReference` checks 64 plain scalars (octal `017`, | |
| 23 | + | `00`, `1.`, `.inf`, `-.nan`, `0b11`, `1_000`, dates...) against the | |
| 24 | + | JavaScript library's answers (`testdata/scalars.json`). Not checked: tags | |
| 25 | + | other than `!!str`, `!!binary`, merge keys `<<` (kept as an ordinary key). | |
| 25 | 26 | - **YAML syntax errors** have yaml.v3's wording and only a line number, and are | |
| 26 | 27 | reported as `file.yml: message at line N`. The reference's are | |
| 27 | 28 | `file.yml: <message> at line N, column M:\n\n<excerpt>`. Only the shape | |
| @@ -19,9 +19,10 @@ available. Nothing here has been checked against it. | |||||
| 19 | core schema: `yes`/`no`/`on`/`off` are strings, `0o17` and `0x1F` are | 19 | core schema: `yes`/`no`/`on`/`off` are strings, `0o17` and `0x1F` are | ||
| 20 | numbers, `.inf` and `.nan` are numbers, `0b11`, `1_000` and sexagesimal | 20 | numbers, `.inf` and `.nan` are numbers, `0b11`, `1_000` and sexagesimal | ||
| 21 | numbers are strings. Quoted, block and `!!str` scalars are strings. | 21 | numbers are strings. Quoted, block and `!!str` scalars are strings. | ||
| 22 | - | Verify: octal `017` (1.2 reads 17), timestamps, `!!binary`, merge keys `<<` | 22 | + | `TestPlainScalarsAgainstReference` checks 64 plain scalars (octal `017`, |
| 23 | - | (yaml.v3 resolves them when decoding but not in the Node API; they are kept | 23 | + | `00`, `1.`, `.inf`, `-.nan`, `0b11`, `1_000`, dates...) against the |
| 24 | - | as an ordinary key `<<`, as in the reference). | 24 | + | JavaScript library's answers (`testdata/scalars.json`). Not checked: tags |
| 25 | + | other than `!!str`, `!!binary`, merge keys `<<` (kept as an ordinary key). | |||
| 25 | - **YAML syntax errors** have yaml.v3's wording and only a line number, and are | 26 | - **YAML syntax errors** have yaml.v3's wording and only a line number, and are | ||
| 26 | reported as `file.yml: message at line N`. The reference's are | 27 | reported as `file.yml: message at line N`. The reference's are | ||
| 27 | `file.yml: <message> at line N, column M:\n\n<excerpt>`. Only the shape | 28 | `file.yml: <message> at line N, column M:\n\n<excerpt>`. Only the shape | ||
A template/gen/scalars.mjs +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | // | |
| 3 | + | // Generates testdata/scalars.json: how the JavaScript `yaml` library (2.x, the | |
| 4 | + | // one @actions/workflow-parser uses) types plain scalars. | |
| 5 | + | // | |
| 6 | + | // node gen/scalars.mjs > testdata/scalars.json | |
| 7 | + | ||
| 8 | + | import {parseDocument} from "yaml"; | |
| 9 | + | const vals = ["yes","no","on","off","y","n","~","null","Null","NULL","","true","True","TRUE","tRUE","false","0","-0","+1","017","0o17","0O17","0x1F","0X1f","0b11","1_000","1:30","1e3","1E3","1.","1.5","-.5",".5","+.5",".inf","-.inf",".Inf",".INF","+.inf",".nan",".NaN",".NAN","-.nan","1e","e1","0x","0xg","12abc","2001-01-01","1.5e+3","0.","00","08","-017","+0x1F","-0x1F","1,000","Infinity","NaN","inf","=","<<","!!str 1","!!int 5"]; | |
| 10 | + | const out = {}; | |
| 11 | + | for (const v of vals) { | |
| 12 | + | const d = parseDocument("k: " + v + "\n"); | |
| 13 | + | const x = d.get("k"); | |
| 14 | + | out[v] = d.errors.length ? "ERR" : (x === null || x === undefined ? "null" : typeof x === "number" ? "num:" + (Number.isNaN(x) ? "NaN" : x) : typeof x === "boolean" ? "bool:" + x : typeof x === "string" ? "str:" + x : typeof x); | |
| 15 | + | } | |
| 16 | + | console.log(JSON.stringify(out)); | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | // SPDX-License-Identifier: MIT | |||
| 2 | + | // | |||
| 3 | + | // Generates testdata/scalars.json: how the JavaScript `yaml` library (2.x, the | |||
| 4 | + | // one @actions/workflow-parser uses) types plain scalars. | |||
| 5 | + | // | |||
| 6 | + | // node gen/scalars.mjs > testdata/scalars.json | |||
| 7 | + | ||||
| 8 | + | import {parseDocument} from "yaml"; | |||
| 9 | + | const vals = ["yes","no","on","off","y","n","~","null","Null","NULL","","true","True","TRUE","tRUE","false","0","-0","+1","017","0o17","0O17","0x1F","0X1f","0b11","1_000","1:30","1e3","1E3","1.","1.5","-.5",".5","+.5",".inf","-.inf",".Inf",".INF","+.inf",".nan",".NaN",".NAN","-.nan","1e","e1","0x","0xg","12abc","2001-01-01","1.5e+3","0.","00","08","-017","+0x1F","-0x1F","1,000","Infinity","NaN","inf","=","<<","!!str 1","!!int 5"]; | |||
| 10 | + | const out = {}; | |||
| 11 | + | for (const v of vals) { | |||
| 12 | + | const d = parseDocument("k: " + v + "\n"); | |||
| 13 | + | const x = d.get("k"); | |||
| 14 | + | out[v] = d.errors.length ? "ERR" : (x === null || x === undefined ? "null" : typeof x === "number" ? "num:" + (Number.isNaN(x) ? "NaN" : x) : typeof x === "boolean" ? "bool:" + x : typeof x === "string" ? "str:" + x : typeof x); | |||
| 15 | + | } | |||
| 16 | + | console.log(JSON.stringify(out)); | |||
A template/scalar_ref_test.go +53 -0
| @@ -0,0 +1,53 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package template | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "encoding/json" | |
| 7 | + | "fmt" | |
| 8 | + | "math" | |
| 9 | + | "os" | |
| 10 | + | "testing" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // TestPlainScalarsAgainstReference compares how plain scalars are typed with | |
| 14 | + | // the JavaScript yaml library the reference uses. testdata/scalars.json is | |
| 15 | + | // generated by gen/scalars.mjs. | |
| 16 | + | func TestPlainScalarsAgainstReference(t *testing.T) { | |
| 17 | + | data, err := os.ReadFile("testdata/scalars.json") | |
| 18 | + | if err != nil { | |
| 19 | + | t.Fatal(err) | |
| 20 | + | } | |
| 21 | + | var want map[string]string | |
| 22 | + | if err := json.Unmarshal(data, &want); err != nil { | |
| 23 | + | t.Fatal(err) | |
| 24 | + | } | |
| 25 | + | for src, w := range want { | |
| 26 | + | res := readYAMLDocument("f.yml", []byte("k: "+src+"\n")) | |
| 27 | + | if w == "ERR" { | |
| 28 | + | continue | |
| 29 | + | } | |
| 30 | + | if len(res.errs) > 0 { | |
| 31 | + | t.Errorf("%q: error %v, reference %s", src, res.errs[0], w) | |
| 32 | + | continue | |
| 33 | + | } | |
| 34 | + | var got string | |
| 35 | + | switch v := res.root.vals[0].lit.(type) { | |
| 36 | + | case *Null: | |
| 37 | + | got = "null" | |
| 38 | + | case *Number: | |
| 39 | + | if math.IsNaN(v.Value) { | |
| 40 | + | got = "num:NaN" | |
| 41 | + | } else { | |
| 42 | + | got = "num:" + JSNumberString(v.Value) | |
| 43 | + | } | |
| 44 | + | case *Boolean: | |
| 45 | + | got = fmt.Sprintf("bool:%v", v.Value) | |
| 46 | + | case *String: | |
| 47 | + | got = "str:" + v.Value | |
| 48 | + | } | |
| 49 | + | if got != w { | |
| 50 | + | t.Errorf("%q: got %s, reference %s", src, got, w) | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | } | |
| @@ -0,0 +1,53 @@ | |||||
| 1 | + | // SPDX-License-Identifier: MIT | |||
| 2 | + | ||||
| 3 | + | package template | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "encoding/json" | |||
| 7 | + | "fmt" | |||
| 8 | + | "math" | |||
| 9 | + | "os" | |||
| 10 | + | "testing" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // TestPlainScalarsAgainstReference compares how plain scalars are typed with | |||
| 14 | + | // the JavaScript yaml library the reference uses. testdata/scalars.json is | |||
| 15 | + | // generated by gen/scalars.mjs. | |||
| 16 | + | func TestPlainScalarsAgainstReference(t *testing.T) { | |||
| 17 | + | data, err := os.ReadFile("testdata/scalars.json") | |||
| 18 | + | if err != nil { | |||
| 19 | + | t.Fatal(err) | |||
| 20 | + | } | |||
| 21 | + | var want map[string]string | |||
| 22 | + | if err := json.Unmarshal(data, &want); err != nil { | |||
| 23 | + | t.Fatal(err) | |||
| 24 | + | } | |||
| 25 | + | for src, w := range want { | |||
| 26 | + | res := readYAMLDocument("f.yml", []byte("k: "+src+"\n")) | |||
| 27 | + | if w == "ERR" { | |||
| 28 | + | continue | |||
| 29 | + | } | |||
| 30 | + | if len(res.errs) > 0 { | |||
| 31 | + | t.Errorf("%q: error %v, reference %s", src, res.errs[0], w) | |||
| 32 | + | continue | |||
| 33 | + | } | |||
| 34 | + | var got string | |||
| 35 | + | switch v := res.root.vals[0].lit.(type) { | |||
| 36 | + | case *Null: | |||
| 37 | + | got = "null" | |||
| 38 | + | case *Number: | |||
| 39 | + | if math.IsNaN(v.Value) { | |||
| 40 | + | got = "num:NaN" | |||
| 41 | + | } else { | |||
| 42 | + | got = "num:" + JSNumberString(v.Value) | |||
| 43 | + | } | |||
| 44 | + | case *Boolean: | |||
| 45 | + | got = fmt.Sprintf("bool:%v", v.Value) | |||
| 46 | + | case *String: | |||
| 47 | + | got = "str:" + v.Value | |||
| 48 | + | } | |||
| 49 | + | if got != w { | |||
| 50 | + | t.Errorf("%q: got %s, reference %s", src, got, w) | |||
| 51 | + | } | |||
| 52 | + | } | |||
| 53 | + | } | |||
A template/testdata/SOURCE +2 -0
| @@ -0,0 +1,2 @@ | |||
| 1 | + | scalars.json is the output of the yaml npm package (https://github.com/eemeli/yaml, ISC) used by | |
| 2 | + | @actions/workflow-parser 0.3.61, on the scalars listed in ../gen/scalars.mjs. | |
| @@ -0,0 +1,2 @@ | |||||
| 1 | + | scalars.json is the output of the yaml npm package (https://github.com/eemeli/yaml, ISC) used by | |||
| 2 | + | @actions/workflow-parser 0.3.61, on the scalars listed in ../gen/scalars.mjs. | |||
A template/testdata/scalars.json +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | {"0":"num:0","yes":"str:yes","no":"str:no","on":"str:on","off":"str:off","y":"str:y","n":"str:n","~":"null","null":"null","Null":"null","NULL":"null","":"null","true":"bool:true","True":"bool:true","TRUE":"bool:true","tRUE":"str:tRUE","false":"bool:false","-0":"num:0","+1":"num:1","017":"num:17","0o17":"num:15","0O17":"str:0O17","0x1F":"num:31","0X1f":"str:0X1f","0b11":"str:0b11","1_000":"str:1_000","1:30":"str:1:30","1e3":"num:1000","1E3":"num:1000","1.":"num:1","1.5":"num:1.5","-.5":"num:-0.5",".5":"num:0.5","+.5":"num:0.5",".inf":"num:Infinity","-.inf":"num:-Infinity",".Inf":"num:Infinity",".INF":"num:Infinity","+.inf":"num:Infinity",".nan":"num:NaN",".NaN":"num:NaN",".NAN":"num:NaN","-.nan":"str:-.nan","1e":"str:1e","e1":"str:e1","0x":"str:0x","0xg":"str:0xg","12abc":"str:12abc","2001-01-01":"str:2001-01-01","1.5e+3":"num:1500","0.":"num:0","00":"num:0","08":"num:8","-017":"num:-17","+0x1F":"str:+0x1F","-0x1F":"str:-0x1F","1,000":"str:1,000","Infinity":"str:Infinity","NaN":"str:NaN","inf":"str:inf","=":"str:=","<<":"str:<<","!!str 1":"str:1","!!int 5":"num:5"} | |
| @@ -0,0 +1,1 @@ | |||||
| 1 | + | {"0":"num:0","yes":"str:yes","no":"str:no","on":"str:on","off":"str:off","y":"str:y","n":"str:n","~":"null","null":"null","Null":"null","NULL":"null","":"null","true":"bool:true","True":"bool:true","TRUE":"bool:true","tRUE":"str:tRUE","false":"bool:false","-0":"num:0","+1":"num:1","017":"num:17","0o17":"num:15","0O17":"str:0O17","0x1F":"num:31","0X1f":"str:0X1f","0b11":"str:0b11","1_000":"str:1_000","1:30":"str:1:30","1e3":"num:1000","1E3":"num:1000","1.":"num:1","1.5":"num:1.5","-.5":"num:-0.5",".5":"num:0.5","+.5":"num:0.5",".inf":"num:Infinity","-.inf":"num:-Infinity",".Inf":"num:Infinity",".INF":"num:Infinity","+.inf":"num:Infinity",".nan":"num:NaN",".NaN":"num:NaN",".NAN":"num:NaN","-.nan":"str:-.nan","1e":"str:1e","e1":"str:e1","0x":"str:0x","0xg":"str:0xg","12abc":"str:12abc","2001-01-01":"str:2001-01-01","1.5e+3":"num:1500","0.":"num:0","00":"num:0","08":"num:8","-017":"num:-17","+0x1F":"str:+0x1F","-0x1F":"str:-0x1F","1,000":"str:1,000","Infinity":"str:Infinity","NaN":"str:NaN","inf":"str:inf","=":"str:=","<<":"str:<<","!!str 1":"str:1","!!int 5":"num:5"} | |||