detached a970bd2

test(workflow): rewrite only the reference's side and require the runner suffix on what we report (GO-006)

commit
a970bd2f16ceb9700b7374676d99f29945d7c183
author
brice · · 2026-10-12 18:32 UTC
committer
brice · · 2026-10-18 14:59 UTC
parent
736f11e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +58 -11
M workflow/convert_test.go +58 -11
@@ -55,20 +55,67 @@ func errorTexts(errs []Error) []string {
5555 // the JavaScript expression library: no position suffix, and the function name
5656 // in arity errors. package expr follows the C# runner, which is what runs the
5757 // workflow: "<description>: '<token>'. Located at position N within
58-// expression: <expression>", with the "(" token in arity errors. The tests
59-// compare after reducing both to the part the two share.
58+// expression: <expression>", with the "(" token in arity errors. Only the
59+// reference's side is rewritten (sameErrorAsReference); what this package reports
60+// is compared as it is, and must carry the runner's suffix.
6061 var (
61- runnerSuffix = regexp.MustCompile(`(?s)\. Located at position \d+ within expression: .*$`)
62- runnerArity = regexp.MustCompile(`((?:Too few|Too many) parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '[^']*'`)
62+ runnerArity = regexp.MustCompile(`((?:Too few|Too many) parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '[^']*'`)
63+ // exprError recognises the messages of package expr that the runner ends
64+ // with its position suffix.
65+ exprError = regexp.MustCompile(`: (?:Unrecognized named-value|Unrecognized function|Unexpected symbol|Unexpected end of expression|Too few parameters supplied|Too many parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '`)
66+ runnerSuffix = regexp.MustCompile(`^\. Located at position \d+ within expression: .+$`)
6367 )
6468
65-func runnerNeutral(msgs []string) []string {
66- out := make([]string, len(msgs))
67- for i, m := range msgs {
68- m = runnerSuffix.ReplaceAllString(m, "")
69- out[i] = runnerArity.ReplaceAllString(m, "$1: '...'")
69+// sameErrorAsReference reports whether got, the message of this package, is the
70+// reference's want in the runner's wording: the reference's arity errors name
71+// the function where the runner names "(", and its expression errors lack the
72+// position suffix that the runner adds (when the reference has it already, as
73+// for lexer errors, the message must be the same).
74+func sameErrorAsReference(got, want string) bool {
75+ want = runnerArity.ReplaceAllString(want, "$1: '('")
76+ if !exprError.MatchString(want) {
77+ return got == want
78+ }
79+ if strings.Contains(want, ". Located at position ") {
80+ return got == want
81+ }
82+ rest, ok := strings.CutPrefix(got, want)
83+ return ok && runnerSuffix.MatchString(rest)
84+}
85+
86+func sameErrorsAsReference(got, want []string) bool {
87+ if len(got) != len(want) {
88+ return false
89+ }
90+ for i := range got {
91+ if !sameErrorAsReference(got[i], want[i]) {
92+ return false
93+ }
94+ }
95+ return true
96+}
97+
98+func TestSameErrorAsReference(t *testing.T) {
99+ const loc = ". Located at position 3 within expression: a b"
100+ for _, tc := range []struct {
101+ name, got, want string
102+ ok bool
103+ }{
104+ {"suffix added", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'" + loc, "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", true},
105+ {"suffix missing", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", false},
106+ {"suffix empty expression", "f.yml (Line: 1, Col: 1): Unexpected symbol: 'a'. Located at position 3 within expression: ", "f.yml (Line: 1, Col: 1): Unexpected symbol: 'a'", false},
107+ {"arity names the paren", "f.yml (Line: 1, Col: 1): Too many parameters supplied: '('" + loc, "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'", true},
108+ {"arity names the function", "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'" + loc, "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'", false},
109+ {"different token", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'b'" + loc, "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", false},
110+ {"reference has the suffix", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", true},
111+ {"reference suffix differs", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 4 within expression: 1 +", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", false},
112+ {"not an expression error", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", true},
113+ {"not an expression error with a suffix", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'" + loc, "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", false},
114+ } {
115+ if got := sameErrorAsReference(tc.got, tc.want); got != tc.ok {
116+ t.Errorf("%s: got %v, want %v", tc.name, got, tc.ok)
117+ }
70118 }
71- return out
72119 }
73120
74121 // isYAMLError recognises the reference's YAML syntax errors, whose text is
@@ -122,7 +169,7 @@ func TestConvertAgainstReference(t *testing.T) {
122169 }
123170 return
124171 }
125- if !reflect.DeepEqual(runnerNeutral(nonNil(got)), runnerNeutral(nonNil(want.Errors))) {
172+ if !sameErrorsAsReference(nonNil(got), nonNil(want.Errors)) {
126173 t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors)
127174 }
128175 if len(want.Errors) > 0 {
@@ -55,20 +55,67 @@ func errorTexts(errs []Error) []string {
55 // the JavaScript expression library: no position suffix, and the function name55 // the JavaScript expression library: no position suffix, and the function name
56 // in arity errors. package expr follows the C# runner, which is what runs the56 // in arity errors. package expr follows the C# runner, which is what runs the
57 // workflow: "<description>: '<token>'. Located at position N within57 // workflow: "<description>: '<token>'. Located at position N within
58-// expression: <expression>", with the "(" token in arity errors. The tests58+// expression: <expression>", with the "(" token in arity errors. Only the
59-// compare after reducing both to the part the two share.59+// reference's side is rewritten (sameErrorAsReference); what this package reports
60+// is compared as it is, and must carry the runner's suffix.
60 var (61 var (
61- runnerSuffix = regexp.MustCompile(`(?s)\. Located at position \d+ within expression: .*$`)62+ runnerArity = regexp.MustCompile(`((?:Too few|Too many) parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '[^']*'`)
62- runnerArity = regexp.MustCompile(`((?:Too few|Too many) parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '[^']*'`)63+ // exprError recognises the messages of package expr that the runner ends
64+ // with its position suffix.
65+ exprError = regexp.MustCompile(`: (?:Unrecognized named-value|Unrecognized function|Unexpected symbol|Unexpected end of expression|Too few parameters supplied|Too many parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '`)
66+ runnerSuffix = regexp.MustCompile(`^\. Located at position \d+ within expression: .+$`)
63 )67 )
64 68
65-func runnerNeutral(msgs []string) []string {69+// sameErrorAsReference reports whether got, the message of this package, is the
66- out := make([]string, len(msgs))70+// reference's want in the runner's wording: the reference's arity errors name
67- for i, m := range msgs {71+// the function where the runner names "(", and its expression errors lack the
68- m = runnerSuffix.ReplaceAllString(m, "")72+// position suffix that the runner adds (when the reference has it already, as
69- out[i] = runnerArity.ReplaceAllString(m, "$1: '...'")73+// for lexer errors, the message must be the same).
74+func sameErrorAsReference(got, want string) bool {
75+ want = runnerArity.ReplaceAllString(want, "$1: '('")
76+ if !exprError.MatchString(want) {
77+ return got == want
78+ }
79+ if strings.Contains(want, ". Located at position ") {
80+ return got == want
81+ }
82+ rest, ok := strings.CutPrefix(got, want)
83+ return ok && runnerSuffix.MatchString(rest)
84+}
85+
86+func sameErrorsAsReference(got, want []string) bool {
87+ if len(got) != len(want) {
88+ return false
89+ }
90+ for i := range got {
91+ if !sameErrorAsReference(got[i], want[i]) {
92+ return false
93+ }
94+ }
95+ return true
96+}
97+
98+func TestSameErrorAsReference(t *testing.T) {
99+ const loc = ". Located at position 3 within expression: a b"
100+ for _, tc := range []struct {
101+ name, got, want string
102+ ok bool
103+ }{
104+ {"suffix added", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'" + loc, "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", true},
105+ {"suffix missing", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", false},
106+ {"suffix empty expression", "f.yml (Line: 1, Col: 1): Unexpected symbol: 'a'. Located at position 3 within expression: ", "f.yml (Line: 1, Col: 1): Unexpected symbol: 'a'", false},
107+ {"arity names the paren", "f.yml (Line: 1, Col: 1): Too many parameters supplied: '('" + loc, "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'", true},
108+ {"arity names the function", "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'" + loc, "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'", false},
109+ {"different token", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'b'" + loc, "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", false},
110+ {"reference has the suffix", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", true},
111+ {"reference suffix differs", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 4 within expression: 1 +", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", false},
112+ {"not an expression error", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", true},
113+ {"not an expression error with a suffix", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'" + loc, "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", false},
114+ } {
115+ if got := sameErrorAsReference(tc.got, tc.want); got != tc.ok {
116+ t.Errorf("%s: got %v, want %v", tc.name, got, tc.ok)
117+ }
70 }118 }
71- return out
72 }119 }
73 120
74 // isYAMLError recognises the reference's YAML syntax errors, whose text is121 // isYAMLError recognises the reference's YAML syntax errors, whose text is
@@ -122,7 +169,7 @@ func TestConvertAgainstReference(t *testing.T) {
122 }169 }
123 return170 return
124 }171 }
125- if !reflect.DeepEqual(runnerNeutral(nonNil(got)), runnerNeutral(nonNil(want.Errors))) {172+ if !sameErrorsAsReference(nonNil(got), nonNil(want.Errors)) {
126 t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors)173 t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors)
127 }174 }
128 if len(want.Errors) > 0 {175 if len(want.Errors) > 0 {

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