package main import ( "os" "os/exec" "strings" "testing" ) func must[T any](t T, err error) T { if err != nil { panic(err) } return t } var defaultOptions options = options{ tongue: " ", eyes: "oo", leftEye: "o", rightEye: "o", wrapWidth: 40, } func TestGenerateCow(t *testing.T) { type args struct { cow string say string opt options } tests := []struct { name string args args expected string }{ { name: "basic cow", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: "test text", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/basic.cow"))), }, { name: "multiline test", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: "test\ntext", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/multiline.cow"))), }, { name: "multiline with wordwrap test", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: "this is a long block of text.\n\n\nIt goes over many lines! It'll even word-wrap for us.", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/longer_multiline.cow"))), }, { name: "offset bubble", args: args{ cow: string(must(os.ReadFile("./cows/dragon-and-cow.cow"))), say: "this is a long block of text.\n\n\nIt goes over many lines! It'll even word-wrap for us.", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/dragon.cow"))), }, { name: "leading space cow", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: " text", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/leading_space.cow"))), }, { name: "extra spaces cow", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: "some text", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/extra_spaces.cow"))), }, { name: "vertical tab cow", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: "some\vtext", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/extra_spaces.cow"))), }, { name: "a word that's too long cow", args: args{ cow: string(must(os.ReadFile("./cows/default.cow"))), say: "00000000000000000000000000000000000000000000000000 11", opt: defaultOptions, }, expected: string(must(os.ReadFile("./testdata/too_long.cow"))), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { out := generateCow(tt.args.cow, tt.args.say, tt.args.opt) if out != tt.expected { t.Logf("expected: \n%v", tt.expected) t.Logf("got: \n%v", out) t.Fatal() } }) } } // fuzz testing requires perl to be installed and in the PATH func FuzzCow(f *testing.F) { cowString := string(must(os.ReadFile("./cows/default.cow"))) f.Add("phrase") f.Add("phrase with many many words in it so that we get a word-wrap situation") f.Fuzz(func(t *testing.T, phrase string) { if strings.TrimSpace(phrase) != phrase || phrase == "" { // uninteresting degenerate cases where there's a bug in the perl t.Skip() } res := generateCow(cowString, phrase, options{tongue: " ", eyes: "oo", wrapWidth: 40}) cmd := exec.Command("perl", "./perl/cowsay", "-f", "./perl/cows/default.cow") cmd.Stdin = strings.NewReader(phrase) var out strings.Builder cmd.Stdout = &out err := cmd.Run() if err != nil { t.Error(err) } if res != out.String() { t.Errorf("mismatch between perl and go.\nPerl:\n%s\ngo:\n%s", out.String(), res) } }) }