mirror of
https://github.com/goplus/llgo.git
synced 2026-04-22 15:57:31 +08:00
test: improve littest diagnostics and coverage
This commit is contained in:
+1
-1
@@ -217,7 +217,7 @@ func testFrom(t *testing.T, pkgDir, sel string) {
|
||||
return
|
||||
}
|
||||
if test.Diff(t, pkgDir+"/result.txt", []byte(v), []byte(spec.Text)) {
|
||||
t.Fatal("llgen.GenFrom: unexpect result")
|
||||
t.Fatal("llgen.GenFrom: unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,14 +51,18 @@ type match struct {
|
||||
end pos
|
||||
}
|
||||
|
||||
func HasDirectives(text string) bool {
|
||||
func HasDirectives(text string) (bool, error) {
|
||||
lines := splitLines(text)
|
||||
for _, line := range lines {
|
||||
if _, _, ok, _ := parseDirectiveLine(line); ok {
|
||||
return true
|
||||
_, hasDirective, ok, err := parseDirectiveLine(line)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if hasDirective && ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func Match(filename, checks, input string) error {
|
||||
@@ -175,7 +179,7 @@ func parseDirectiveLine(line string) (directive, bool, bool, error) {
|
||||
}
|
||||
rest = strings.TrimLeft(rest[2:], " \t")
|
||||
if strings.HasPrefix(rest, "CHECK:") {
|
||||
pattern := strings.TrimSpace(strings.TrimPrefix(rest, "CHECK:"))
|
||||
pattern := trimDirectivePattern(strings.TrimPrefix(rest, "CHECK:"))
|
||||
re, err := compilePattern(pattern)
|
||||
if err != nil {
|
||||
return directive{}, true, false, err
|
||||
@@ -191,10 +195,10 @@ func parseDirectiveLine(line string) (directive, bool, bool, error) {
|
||||
return directive{}, true, false, fmt.Errorf("missing ':' in directive")
|
||||
}
|
||||
suffix := suffixRest[:colon]
|
||||
pattern := strings.TrimSpace(suffixRest[colon+1:])
|
||||
pattern := trimDirectivePattern(suffixRest[colon+1:])
|
||||
k, ok := parseKind(suffix)
|
||||
if !ok {
|
||||
return directive{}, false, false, nil
|
||||
return directive{}, true, false, fmt.Errorf("unknown directive CHECK-%s", suffix)
|
||||
}
|
||||
if k == kindEmpty {
|
||||
if pattern != "" {
|
||||
@@ -355,3 +359,13 @@ func splitLines(text string) []string {
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func trimDirectivePattern(pattern string) string {
|
||||
if pattern == "" {
|
||||
return ""
|
||||
}
|
||||
if pattern[0] == ' ' || pattern[0] == '\t' {
|
||||
return pattern[1:]
|
||||
}
|
||||
return pattern
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package filecheck
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -15,11 +16,19 @@ func main() {
|
||||
// CHECK-NEXT: done
|
||||
}
|
||||
`
|
||||
input := `
|
||||
func main
|
||||
value 42
|
||||
done
|
||||
input := "func main\nvalue 42\ndone\n"
|
||||
if err := Match("test.go", spec, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchPreservesPatternWhitespace(t *testing.T) {
|
||||
spec := `// LITTEST
|
||||
package main
|
||||
|
||||
// CHECK: value 42
|
||||
`
|
||||
input := " value 42 \n"
|
||||
if err := Match("test.go", spec, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -35,11 +44,7 @@ func main() {
|
||||
// CHECK: done
|
||||
}
|
||||
`
|
||||
input := `
|
||||
value 42
|
||||
|
||||
done
|
||||
`
|
||||
input := "value 42\n\ndone\n"
|
||||
if err := Match("test.go", spec, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -52,11 +57,7 @@ package main
|
||||
// CHECK-LINE: begin
|
||||
// CHECK-LINE: end
|
||||
`
|
||||
input := `
|
||||
begin
|
||||
middle
|
||||
end
|
||||
`
|
||||
input := "begin\nmiddle\nend\n"
|
||||
if err := Match("test.go", spec, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -68,10 +69,7 @@ package main
|
||||
|
||||
// CHECK-LABEL: begin
|
||||
`
|
||||
input := `
|
||||
prefix begin
|
||||
`
|
||||
err := Match("test.go", spec, input)
|
||||
err := Match("test.go", spec, "prefix begin\n")
|
||||
if err == nil {
|
||||
t.Fatal("Match succeeded unexpectedly")
|
||||
}
|
||||
@@ -88,10 +86,7 @@ func main() {
|
||||
// CHECK: done
|
||||
}
|
||||
`
|
||||
input := `
|
||||
value 42
|
||||
done
|
||||
`
|
||||
input := "value 42\ndone\n"
|
||||
if err := Match("test.go", spec, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -107,18 +102,8 @@ func main() {
|
||||
// CHECK: done
|
||||
}
|
||||
`
|
||||
input := `
|
||||
ok
|
||||
panic
|
||||
done
|
||||
`
|
||||
err := Match("test.go", spec, input)
|
||||
if err == nil {
|
||||
t.Fatal("Match succeeded unexpectedly")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "forbidden text") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err := Match("test.go", spec, "ok\npanic\ndone\n")
|
||||
requireErrContains(t, err, "forbidden text")
|
||||
}
|
||||
|
||||
func TestMatchDoesNotCarryCheckNotAcrossLabels(t *testing.T) {
|
||||
@@ -131,28 +116,41 @@ package main
|
||||
// CHECK-LABEL: second
|
||||
// CHECK: panic
|
||||
`
|
||||
input := `
|
||||
first
|
||||
ok
|
||||
second
|
||||
panic
|
||||
`
|
||||
input := "first\nok\nsecond\npanic\n"
|
||||
if err := Match("test.go", spec, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasDirectives(t *testing.T) {
|
||||
if !HasDirectives("// CHECK: value\n") {
|
||||
ok, err := HasDirectives("// CHECK: value\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("HasDirectives returned false")
|
||||
}
|
||||
if HasDirectives("value\n") {
|
||||
|
||||
ok, err = HasDirectives("value\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatal("HasDirectives returned true unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasDirectivesPropagatesDirectiveErrors(t *testing.T) {
|
||||
_, err := HasDirectives("// CHECK: {{[invalid\n")
|
||||
requireErrContains(t, err, "unterminated '{{' in pattern")
|
||||
}
|
||||
|
||||
func TestHasDirectivesIgnoresNonSlashSlashComments(t *testing.T) {
|
||||
if HasDirectives("; CHECK: value\n") {
|
||||
ok, err := HasDirectives("; CHECK: value\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatal("HasDirectives returned true unexpectedly")
|
||||
}
|
||||
}
|
||||
@@ -164,3 +162,160 @@ func TestMatchSupportsCRLF(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchReportsDirectiveAndPatternErrors(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
spec string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty pattern",
|
||||
spec: "// CHECK:\n",
|
||||
want: "empty pattern",
|
||||
},
|
||||
{
|
||||
name: "unterminated regex",
|
||||
spec: "// CHECK: {{[0-9]+\n",
|
||||
want: "unterminated '{{' in pattern",
|
||||
},
|
||||
{
|
||||
name: "invalid regex",
|
||||
spec: "// CHECK: {{(}}\n",
|
||||
want: "error parsing regexp",
|
||||
},
|
||||
{
|
||||
name: "unknown directive",
|
||||
spec: "// CHECK-BOGUS: value\n",
|
||||
want: "unknown directive CHECK-BOGUS",
|
||||
},
|
||||
{
|
||||
name: "empty pattern not allowed",
|
||||
spec: "// CHECK-EMPTY: value\n",
|
||||
want: "CHECK-EMPTY must not have a pattern",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := Match("test.go", tc.spec, "")
|
||||
requireErrContains(t, err, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchReportsPreconditionErrors(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
spec string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "next",
|
||||
spec: "// CHECK-NEXT: value\n",
|
||||
want: "CHECK-NEXT requires a prior positive match",
|
||||
},
|
||||
{
|
||||
name: "same",
|
||||
spec: "// CHECK-SAME: value\n",
|
||||
want: "CHECK-SAME requires a prior positive match",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
spec: "// CHECK-EMPTY:\n",
|
||||
want: "CHECK-EMPTY requires a prior positive match",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := Match("test.go", tc.spec, "value\n")
|
||||
requireErrContains(t, err, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchReportsSearchFailures(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
spec string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "next wrong line",
|
||||
spec: "// CHECK: value\n// CHECK-NEXT: done\n",
|
||||
input: "value\nother\n",
|
||||
want: `CHECK-NEXT "done" did not match`,
|
||||
},
|
||||
{
|
||||
name: "same wrong line",
|
||||
spec: "// CHECK: value\n// CHECK-SAME: done\n",
|
||||
input: "value other\n",
|
||||
want: `CHECK-SAME "done" did not match`,
|
||||
},
|
||||
{
|
||||
name: "empty non-empty line",
|
||||
spec: "// CHECK: value\n// CHECK-EMPTY:\n",
|
||||
input: "value\nother\n",
|
||||
want: "CHECK-EMPTY expected input:2 to be empty",
|
||||
},
|
||||
{
|
||||
name: "empty past end",
|
||||
spec: "// CHECK: value\n// CHECK-EMPTY:\n",
|
||||
input: "value",
|
||||
want: "CHECK-EMPTY expected an empty line after input:1",
|
||||
},
|
||||
{
|
||||
name: "trailing not checks remaining input",
|
||||
spec: "// CHECK: value\n// CHECK-NOT: forbidden\n",
|
||||
input: "value\nforbidden\n",
|
||||
want: "forbidden text",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := Match("test.go", tc.spec, tc.input)
|
||||
requireErrContains(t, err, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchHelpers(t *testing.T) {
|
||||
re := regexp.MustCompile("x")
|
||||
|
||||
if got := trimDirectivePattern("\tvalue"); got != "value" {
|
||||
t.Fatalf("trimDirectivePattern(tab) = %q", got)
|
||||
}
|
||||
if got := trimDirectivePattern("value"); got != "value" {
|
||||
t.Fatalf("trimDirectivePattern(plain) = %q", got)
|
||||
}
|
||||
|
||||
if !before(pos{line: 0, col: 1}, pos{line: 1, col: 0}) {
|
||||
t.Fatal("before returned false unexpectedly")
|
||||
}
|
||||
if !before(pos{line: 0, col: 1}, pos{line: 0, col: 2}) {
|
||||
t.Fatal("before returned false unexpectedly")
|
||||
}
|
||||
|
||||
if _, ok := findForward([]string{"ab", "x"}, pos{line: 0, col: 10}, re); !ok {
|
||||
t.Fatal("findForward did not clamp and continue")
|
||||
}
|
||||
if _, ok := findLine([]string{"x"}, -1, 0, re); ok {
|
||||
t.Fatal("findLine matched unexpectedly")
|
||||
}
|
||||
if _, ok := findLine([]string{"x"}, 0, 10, re); ok {
|
||||
t.Fatal("findLine matched unexpectedly")
|
||||
}
|
||||
if _, _, ok := findForbidden([]string{"abc"}, pos{line: 0, col: 2}, pos{line: 0, col: 1}, re); ok {
|
||||
t.Fatal("findForbidden matched unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func requireErrContains(t *testing.T, err error, want string) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Fatalf("error %q does not contain %q", err, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,11 @@ func loadSourceSpec(pkgDir string) (Spec, bool, error) {
|
||||
return Spec{}, false, err
|
||||
}
|
||||
text := string(data)
|
||||
if !filecheck.HasDirectives(text) {
|
||||
ok, err := filecheck.HasDirectives(text)
|
||||
if err != nil {
|
||||
return Spec{}, false, err
|
||||
}
|
||||
if !ok {
|
||||
return Spec{}, false, fmt.Errorf("%s: %s is marked %s but has no FileCheck directives", pkgDir, filepath.Base(marked), marker)
|
||||
}
|
||||
return Spec{
|
||||
|
||||
@@ -3,6 +3,7 @@ package littest
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,13 @@ define void @main() {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecReportsMissingDirectory(t *testing.T) {
|
||||
_, err := LoadSpec(filepath.Join(t.TempDir(), "missing"))
|
||||
if err == nil {
|
||||
t.Fatal("LoadSpec succeeded unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecRejectsMultipleMarkedSources(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(`// LITTEST
|
||||
@@ -93,6 +101,24 @@ package main
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecReportsMalformedDirective(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(filepath.Join(dir, "in.go"), []byte(`// LITTEST
|
||||
// CHECK: {{[invalid
|
||||
package main
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = LoadSpec(dir)
|
||||
if err == nil {
|
||||
t.Fatal("LoadSpec succeeded unexpectedly")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unterminated '{{' in pattern") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecFallsBackToOutLLWithoutMarker(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(filepath.Join(dir, "in.go"), []byte(`// CHECK: ret void
|
||||
@@ -121,6 +147,18 @@ define void @main() {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecReportsMissingOutLL(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(filepath.Join(dir, "in.go"), []byte("package main\n"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = LoadSpec(dir)
|
||||
if err == nil {
|
||||
t.Fatal("LoadSpec succeeded unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecSupportsSkipOutLL(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(filepath.Join(dir, "out.ll"), []byte(`;`), 0644)
|
||||
@@ -136,6 +174,87 @@ func TestLoadSpecSupportsSkipOutLL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasMarker(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
ok, err := hasMarker(filepath.Join(dir, "missing.go"))
|
||||
if err == nil || ok {
|
||||
t.Fatalf("hasMarker(missing) = (%v, %v)", ok, err)
|
||||
}
|
||||
|
||||
empty := filepath.Join(dir, "empty.go")
|
||||
err = os.WriteFile(empty, nil, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok, err = hasMarker(empty)
|
||||
if err != nil || ok {
|
||||
t.Fatalf("hasMarker(empty) = (%v, %v)", ok, err)
|
||||
}
|
||||
|
||||
plain := filepath.Join(dir, "plain.go")
|
||||
err = os.WriteFile(plain, []byte("package main\n"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ok, err = hasMarker(plain)
|
||||
if err != nil || ok {
|
||||
t.Fatalf("hasMarker(plain) = (%v, %v)", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
spec Spec
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "skip",
|
||||
spec: Spec{Path: "skip", Mode: ModeSkip},
|
||||
},
|
||||
{
|
||||
name: "literal match",
|
||||
spec: Spec{Path: "literal", Text: "ok", Mode: ModeLiteral},
|
||||
text: "ok",
|
||||
},
|
||||
{
|
||||
name: "literal mismatch",
|
||||
spec: Spec{Path: "literal", Text: "ok", Mode: ModeLiteral},
|
||||
text: "bad",
|
||||
want: "literal LLVM IR mismatch",
|
||||
},
|
||||
{
|
||||
name: "filecheck match",
|
||||
spec: Spec{Path: "check.go", Text: "// CHECK: ok\n", Mode: ModeFileCheck},
|
||||
text: "ok\n",
|
||||
},
|
||||
{
|
||||
name: "invalid mode",
|
||||
spec: Spec{Path: "bad", Mode: Mode(99)},
|
||||
want: "unknown lit spec mode",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := Check(tc.spec, tc.text)
|
||||
if tc.want == "" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("Check succeeded unexpectedly")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSpecRequiresMarkerOnFirstLine(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
err := os.WriteFile(filepath.Join(dir, "in.go"), []byte(`
|
||||
|
||||
Reference in New Issue
Block a user