github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/datadriven/test_data_reader.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. See the License for the specific language governing 13 // permissions and limitations under the License. 14 15 package datadriven 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "regexp" 22 "strings" 23 "testing" 24 ) 25 26 type testDataReader struct { 27 sourceName string 28 reader io.Reader 29 scanner *lineScanner 30 data TestData 31 rewrite *bytes.Buffer 32 } 33 34 func newTestDataReader( 35 t *testing.T, sourceName string, file io.Reader, record bool, 36 ) *testDataReader { 37 t.Helper() 38 39 var rewrite *bytes.Buffer 40 if record { 41 rewrite = &bytes.Buffer{} 42 } 43 return &testDataReader{ 44 sourceName: sourceName, 45 reader: file, 46 scanner: newLineScanner(file), 47 rewrite: rewrite, 48 } 49 } 50 51 func (r *testDataReader) Next(t *testing.T) bool { 52 t.Helper() 53 54 r.data = TestData{} 55 for r.scanner.Scan() { 56 line := r.scanner.Text() 57 r.emit(line) 58 59 line = strings.TrimSpace(line) 60 if strings.HasPrefix(line, "#") { 61 // Skip comment lines. 62 continue 63 } 64 // Support wrapping directive lines using \, for example: 65 // build-scalar \ 66 // vars(int) 67 for strings.HasSuffix(line, `\`) && r.scanner.Scan() { 68 nextLine := r.scanner.Text() 69 r.emit(nextLine) 70 line = strings.TrimSuffix(line, `\`) + " " + strings.TrimSpace(nextLine) 71 } 72 73 fields := splitDirectives(t, line) 74 if len(fields) == 0 { 75 continue 76 } 77 cmd := fields[0] 78 r.data.Pos = fmt.Sprintf("%s:%d", r.sourceName, r.scanner.line) 79 r.data.Cmd = cmd 80 81 for _, arg := range fields[1:] { 82 key := arg 83 var vals []string 84 if pos := strings.IndexByte(key, '='); pos >= 0 { 85 key = arg[:pos] 86 val := arg[pos+1:] 87 88 if len(val) > 2 && val[0] == '(' && val[len(val)-1] == ')' { 89 vals = strings.Split(val[1:len(val)-1], ",") 90 for i := range vals { 91 vals[i] = strings.TrimSpace(vals[i]) 92 } 93 } else { 94 vals = []string{val} 95 } 96 } 97 r.data.CmdArgs = append(r.data.CmdArgs, CmdArg{Key: key, Vals: vals}) 98 } 99 100 var buf bytes.Buffer 101 var separator bool 102 for r.scanner.Scan() { 103 line := r.scanner.Text() 104 if line == "----" { 105 separator = true 106 break 107 } 108 109 r.emit(line) 110 fmt.Fprintln(&buf, line) 111 } 112 113 r.data.Input = strings.TrimSpace(buf.String()) 114 115 if separator { 116 r.readExpected() 117 } 118 return true 119 } 120 return false 121 } 122 123 func (r *testDataReader) readExpected() { 124 var buf bytes.Buffer 125 var line string 126 var allowBlankLines bool 127 128 if r.scanner.Scan() { 129 line = r.scanner.Text() 130 if line == "----" { 131 allowBlankLines = true 132 } 133 } 134 135 if allowBlankLines { 136 // Look for two successive lines of "----" before terminating. 137 for r.scanner.Scan() { 138 line = r.scanner.Text() 139 140 if line == "----" { 141 if r.scanner.Scan() { 142 line2 := r.scanner.Text() 143 if line2 == "----" { 144 break 145 } 146 147 fmt.Fprintln(&buf, line) 148 fmt.Fprintln(&buf, line2) 149 continue 150 } 151 } 152 153 fmt.Fprintln(&buf, line) 154 } 155 } else { 156 // Terminate on first blank line. 157 for { 158 if strings.TrimSpace(line) == "" { 159 break 160 } 161 162 fmt.Fprintln(&buf, line) 163 164 if !r.scanner.Scan() { 165 break 166 } 167 168 line = r.scanner.Text() 169 } 170 } 171 172 r.data.Expected = buf.String() 173 } 174 175 func (r *testDataReader) emit(s string) { 176 if r.rewrite != nil { 177 r.rewrite.WriteString(s) 178 r.rewrite.WriteString("\n") 179 } 180 } 181 182 var splitDirectivesRE = regexp.MustCompile(`^ *[a-zA-Z0-9_\/,-\.]+(|=[-a-zA-Z0-9_@]+|=\([^)]*\))( |$)`) 183 184 // splits a directive line into tokens, where each token is 185 // either: 186 // - a,list,of,things 187 // - argument 188 // - argument=value 189 // - argument=(values, ...) 190 func splitDirectives(t *testing.T, line string) []string { 191 var res []string 192 193 for line != "" { 194 str := splitDirectivesRE.FindString(line) 195 if len(str) == 0 { 196 t.Fatalf("cannot parse directive %s\n", line) 197 } 198 res = append(res, strings.TrimSpace(line[0:len(str)])) 199 line = line[len(str):] 200 } 201 return res 202 }