detached 6ecb01e

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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.
1919 core schema: `yes`/`no`/`on`/`off` are strings, `0o17` and `0x1F` are
2020 numbers, `.inf` and `.nan` are numbers, `0b11`, `1_000` and sexagesimal
2121 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).
2526 - **YAML syntax errors** have yaml.v3's wording and only a line number, and are
2627 reported as `file.yml: message at line N`. The reference's are
2728 `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` are19 core schema: `yes`/`no`/`on`/`off` are strings, `0o17` and `0x1F` are
20 numbers, `.inf` and `.nan` are numbers, `0b11`, `1_000` and sexagesimal20 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 kept23+ `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 are26 - **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 are27 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 shape28 `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"}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close