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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 { | |||
| 55 | 55 | // the JavaScript expression library: no position suffix, and the function name | |
| 56 | 56 | // in arity errors. package expr follows the C# runner, which is what runs the | |
| 57 | 57 | // 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. | |
| 60 | 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 { | |
| 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 | + | } | |
| 70 | 118 | } | |
| 71 | - | return out | |
| 72 | 119 | } | |
| 73 | 120 | ||
| 74 | 121 | // isYAMLError recognises the reference's YAML syntax errors, whose text is | |
| @@ -122,7 +169,7 @@ func TestConvertAgainstReference(t *testing.T) { | |||
| 122 | 169 | } | |
| 123 | 170 | return | |
| 124 | 171 | } | |
| 125 | - | if !reflect.DeepEqual(runnerNeutral(nonNil(got)), runnerNeutral(nonNil(want.Errors))) { | |
| 172 | + | if !sameErrorsAsReference(nonNil(got), nonNil(want.Errors)) { | |
| 126 | 173 | t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors) | |
| 127 | 174 | } | |
| 128 | 175 | 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 name | 55 | // 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 the | 56 | // in arity errors. package expr follows the C# runner, which is what runs the | ||
| 57 | // workflow: "<description>: '<token>'. Located at position N within | 57 | // workflow: "<description>: '<token>'. Located at position N within | ||
| 58 | - | // expression: <expression>", with the "(" token in arity errors. The tests | 58 | + | // 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 is | 121 | // isYAMLError recognises the reference's YAML syntax errors, whose text is | ||
| @@ -122,7 +169,7 @@ func TestConvertAgainstReference(t *testing.T) { | |||||
| 122 | } | 169 | } | ||
| 123 | return | 170 | 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 { | ||