test: improve littest diagnostics and coverage

This commit is contained in:
ZhouGuangyuan
2026-04-02 07:51:58 +08:00
parent ac9b865b0d
commit 9c014174df
5 changed files with 344 additions and 52 deletions
+1 -1
View File
@@ -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")
}
}
+21 -7
View File
@@ -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
}
+198 -43
View File
@@ -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)
}
}
+5 -1
View File
@@ -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{
+119
View File
@@ -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(`