goldmark/testutil/testutil_test.go
Abhinav Gupta 282e1428bc ParseTestCaseFile: Don't panic
Instead of panicking, ParseTestCaseFile now reports errors.
The errors take the form,

    line $line: $msg: $cause

For example,

    line 12: invalid case No: parse error

As a result of this change,
we no longer discard the error returned by strconv.Atoi or json.Marshal
when we reject the test file,
and include it in the error message instead.

Note that the errors do not include the file name
because the file name is always the same
so the caller can add that if necessary
(which it will, in the next commit).
2022-11-11 08:24:20 -08:00

205 lines
4.5 KiB
Go

package testutil
import (
"errors"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
// This will fail to compile if the TestingT interface is changed in a way
// that doesn't conform to testing.T.
var _ TestingT = (*testing.T)(nil)
func TestParseTestCaseFile(t *testing.T) {
tests := []struct {
desc string
give string // contents of the test file
want []MarkdownTestCase
}{
{
desc: "empty",
give: "",
want: []MarkdownTestCase{},
},
{
desc: "simple",
give: strings.Join([]string{
"1",
"//- - - - - - - - -//",
"input",
"//- - - - - - - - -//",
"output",
"//= = = = = = = = = = = = = = = = = = = = = = = =//",
}, "\n"),
want: []MarkdownTestCase{
{
No: 1,
Markdown: "input",
Expected: "output\n",
},
},
},
{
desc: "description",
give: strings.Join([]string{
"2:check something",
"//- - - - - - - - -//",
"hello",
"//- - - - - - - - -//",
"<p>hello</p>",
"//= = = = = = = = = = = = = = = = = = = = = = = =//",
}, "\n"),
want: []MarkdownTestCase{
{
No: 2,
Description: "check something",
Markdown: "hello",
Expected: "<p>hello</p>\n",
},
},
},
{
desc: "options",
give: strings.Join([]string{
"3",
`OPTIONS: {"trim": true}`,
"//- - - - - - - - -//",
"world",
"//- - - - - - - - -//",
"<p>world</p>",
"//= = = = = = = = = = = = = = = = = = = = = = = =//",
}, "\n"),
want: []MarkdownTestCase{
{
No: 3,
Options: MarkdownTestCaseOptions{Trim: true},
Markdown: "world",
Expected: "<p>world</p>\n",
},
},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
filename := filepath.Join(t.TempDir(), "give.txt")
if err := os.WriteFile(filename, []byte(tt.give), 0o644); err != nil {
t.Fatal(err)
}
got, err := ParseTestCaseFile(filename)
if err != nil {
t.Fatalf("could not parse: %v", err)
}
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("output did not match:")
t.Errorf(" got = %#v", got)
t.Errorf("want = %#v", tt.want)
}
})
}
}
func TestParseTestCaseFile_Errors(t *testing.T) {
tests := []struct {
desc string
give string // contents of the test file
errMsg string
}{
{
desc: "bad number/no description",
give: strings.Join([]string{
"1 not a number",
"//- - - - - - - - -//",
"world",
"//- - - - - - - - -//",
"<p>world</p>",
"//= = = = = = = = = = = = = = = = = = = = = = = =//",
}, "\n"),
errMsg: "line 1: invalid case No",
},
{
desc: "bad number/description",
give: strings.Join([]string{
"1 not a number:description",
"//- - - - - - - - -//",
"world",
"//- - - - - - - - -//",
"<p>world</p>",
"//= = = = = = = = = = = = = = = = = = = = = = = =//",
}, "\n"),
errMsg: "line 1: invalid case No",
},
{
desc: "eof after number",
give: strings.Join([]string{
"1",
}, "\n"),
errMsg: "line 1: invalid case: expected content after",
},
{
desc: "bad options",
give: strings.Join([]string{
"3",
`OPTIONS: {not valid JSON}`,
"//- - - - - - - - -//",
"world",
"//- - - - - - - - -//",
"<p>world</p>",
"//= = = = = = = = = = = = = = = = = = = = = = = =//",
}, "\n"),
errMsg: "line 2: invalid options:",
},
{
desc: "bad separator",
give: strings.Join([]string{
"3",
"// not the right separator //",
}, "\n"),
errMsg: `line 2: invalid separator "// not the right separator //"`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
filename := filepath.Join(t.TempDir(), "give.txt")
if err := os.WriteFile(filename, []byte(tt.give), 0o644); err != nil {
t.Fatal(err)
}
cases, err := ParseTestCaseFile(filename)
if err == nil {
t.Fatalf("expected error, got:\n%#v", cases)
}
if got := err.Error(); !strings.Contains(got, tt.errMsg) {
t.Errorf("unexpected error message:")
t.Errorf(" got = %v", got)
t.Errorf("does not contain = %v", tt.errMsg)
}
})
}
}
func TestTestCaseParseError(t *testing.T) {
wrapped := errors.New("great sadness")
err := &testCaseParseError{Line: 42, Err: wrapped}
t.Run("Error", func(t *testing.T) {
want := "line 42: great sadness"
got := err.Error()
if want != got {
t.Errorf("Error() = %q, want %q", got, want)
}
})
t.Run("Unwrap", func(t *testing.T) {
if !errors.Is(err, wrapped) {
t.Errorf("error %#v should unwrap to %#v", err, wrapped)
}
})
}