github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/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  }