github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/ast/test_util.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package ast 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "os" 11 "path/filepath" 12 "regexp" 13 "sort" 14 "strings" 15 "testing" 16 ) 17 18 type ErrorMatcher struct { 19 t *testing.T 20 Data []byte 21 expect []*errorDesc 22 got []*errorDesc 23 } 24 25 type errorDesc struct { 26 pos Pos 27 text string 28 matched bool 29 } 30 31 func NewErrorMatcher(t *testing.T, file string) *ErrorMatcher { 32 data, err := os.ReadFile(file) 33 if err != nil { 34 t.Fatalf("failed to open input file: %v", err) 35 } 36 var stripped []byte 37 var errors []*errorDesc 38 s := bufio.NewScanner(bytes.NewReader(data)) 39 for i := 1; s.Scan(); i++ { 40 ln := s.Bytes() 41 for { 42 pos := bytes.LastIndex(ln, []byte("###")) 43 if pos == -1 { 44 break 45 } 46 errors = append(errors, &errorDesc{ 47 pos: Pos{File: filepath.Base(file), Line: i}, 48 text: strings.TrimSpace(string(ln[pos+3:])), 49 }) 50 ln = ln[:pos] 51 } 52 stripped = append(stripped, ln...) 53 stripped = append(stripped, '\n') 54 } 55 if err := s.Err(); err != nil { 56 t.Fatalf("failed to scan input file: %v", err) 57 } 58 return &ErrorMatcher{ 59 t: t, 60 Data: stripped, 61 expect: errors, 62 } 63 } 64 65 var errorLocationRe = regexp.MustCompile(`at [a-z][a-z0-9]+\.txt:[0-9]+:[0-9]+`) 66 67 func (em *ErrorMatcher) ErrorHandler(pos Pos, msg string) { 68 if match := errorLocationRe.FindStringSubmatchIndex(msg); match != nil { 69 msg = msg[0:match[0]] + "at LOCATION" + msg[match[1]:] 70 } 71 em.got = append(em.got, &errorDesc{ 72 pos: pos, 73 text: msg, 74 }) 75 } 76 77 func (em *ErrorMatcher) Count() int { 78 return len(em.got) 79 } 80 81 func (em *ErrorMatcher) Check() { 82 em.t.Helper() 83 errors := make(map[Pos][]string) 84 nextErr: 85 for _, e := range em.got { 86 for _, want := range em.expect { 87 if want.matched || want.pos.Line != e.pos.Line || want.text != e.text { 88 continue 89 } 90 want.matched = true 91 continue nextErr 92 } 93 pos := e.pos 94 pos.Col = 0 95 pos.Off = 0 96 errors[pos] = append(errors[pos], fmt.Sprintf("unexpected: %v", e.text)) 97 } 98 for _, want := range em.expect { 99 if want.matched { 100 continue 101 } 102 errors[want.pos] = append(errors[want.pos], fmt.Sprintf("unmatched : %v", want.text)) 103 } 104 105 if len(errors) == 0 { 106 return 107 } 108 type Sorted struct { 109 pos Pos 110 msgs []string 111 } 112 sorted := []Sorted{} 113 for pos, msgs := range errors { 114 sorted = append(sorted, Sorted{pos, msgs}) 115 } 116 sort.Slice(sorted, func(i, j int) bool { 117 return sorted[i].pos.less(sorted[j].pos) 118 }) 119 buf := new(bytes.Buffer) 120 for _, err := range sorted { 121 if len(err.msgs) == 1 { 122 fmt.Fprintf(buf, "%v: %v\n", err.pos, err.msgs[0]) 123 continue 124 } 125 sort.Strings(err.msgs) 126 fmt.Fprintf(buf, "%v:\n\t%v\n", err.pos, strings.Join(err.msgs, "\n\t")) 127 } 128 em.t.Errorf("\n%s", buf.Bytes()) 129 } 130 131 func (em *ErrorMatcher) DumpErrors() { 132 em.t.Helper() 133 for _, e := range em.got { 134 em.t.Logf("%v: %v", e.pos, e.text) 135 } 136 }