bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/expr/parse/lex_test.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package parse
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  )
    11  
    12  // Make the types prettyprint.
    13  var itemName = map[itemType]string{
    14  	itemError:      "error",
    15  	itemEOF:        "EOF",
    16  	itemNot:        "!",
    17  	itemAnd:        "&&",
    18  	itemOr:         "||",
    19  	itemGreater:    ">",
    20  	itemLess:       "<",
    21  	itemGreaterEq:  ">=",
    22  	itemLessEq:     "<=",
    23  	itemEq:         "==",
    24  	itemNotEq:      "!=",
    25  	itemPlus:       "+",
    26  	itemMinus:      "-",
    27  	itemMult:       "*",
    28  	itemDiv:        "/",
    29  	itemMod:        "%",
    30  	itemNumber:     "number",
    31  	itemComma:      ",",
    32  	itemLeftParen:  "(",
    33  	itemRightParen: ")",
    34  	itemString:     "string",
    35  	itemFunc:       "func",
    36  }
    37  
    38  func (i itemType) String() string {
    39  	s := itemName[i]
    40  	if s == "" {
    41  		return fmt.Sprintf("item%d", int(i))
    42  	}
    43  	return s
    44  }
    45  
    46  type lexTest struct {
    47  	name  string
    48  	input string
    49  	items []item
    50  }
    51  
    52  var (
    53  	tEOF   = item{itemEOF, 0, ""}
    54  	tLpar  = item{itemLeftParen, 0, "("}
    55  	tRpar  = item{itemRightParen, 0, ")"}
    56  	tComma = item{itemComma, 0, ","}
    57  	tLt    = item{itemLess, 0, "<"}
    58  	tGt    = item{itemGreater, 0, ">"}
    59  	tOr    = item{itemOr, 0, "||"}
    60  	tNot   = item{itemNot, 0, "!"}
    61  	tAnd   = item{itemAnd, 0, "&&"}
    62  	tLtEq  = item{itemLessEq, 0, "<="}
    63  	tGtEq  = item{itemGreaterEq, 0, ">="}
    64  	tNotEq = item{itemNotEq, 0, "!="}
    65  	tEq    = item{itemEq, 0, "=="}
    66  	tPlus  = item{itemPlus, 0, "+"}
    67  	tMinus = item{itemMinus, 0, "-"}
    68  	tMult  = item{itemMult, 0, "*"}
    69  	tDiv   = item{itemDiv, 0, "/"}
    70  	tMod   = item{itemMod, 0, "%"}
    71  )
    72  
    73  var lexTests = []lexTest{
    74  	{"empty", "", []item{tEOF}},
    75  	{"spaces", " \t\n", []item{tEOF}},
    76  	{"text", `"now is the time"`, []item{{itemString, 0, `"now is the time"`}, tEOF}},
    77  	{"operators", "! && || < > <= >= == != + - * / %", []item{
    78  		tNot,
    79  		tAnd,
    80  		tOr,
    81  		tLt,
    82  		tGt,
    83  		tLtEq,
    84  		tGtEq,
    85  		tEq,
    86  		tNotEq,
    87  		tPlus,
    88  		tMinus,
    89  		tMult,
    90  		tDiv,
    91  		tMod,
    92  		tEOF,
    93  	}},
    94  	{"numbers", "1 02 0x14 7.2 1e3 1.2e-4", []item{
    95  		{itemNumber, 0, "1"},
    96  		{itemNumber, 0, "02"},
    97  		{itemNumber, 0, "0x14"},
    98  		{itemNumber, 0, "7.2"},
    99  		{itemNumber, 0, "1e3"},
   100  		{itemNumber, 0, "1.2e-4"},
   101  		tEOF,
   102  	}},
   103  	{"expression", `avg(q("sum:sys.cpu.user{host=*-web*}", "1m")) < 0.2 || avg(q("sum:sys.cpu.user{host=*-web*}", "1m")) > 0.4`, []item{
   104  		{itemFunc, 0, "avg"},
   105  		tLpar,
   106  		{itemFunc, 0, "q"},
   107  		tLpar,
   108  		{itemString, 0, `"sum:sys.cpu.user{host=*-web*}"`},
   109  		tComma,
   110  		{itemString, 0, `"1m"`},
   111  		tRpar,
   112  		tRpar,
   113  		tLt,
   114  		{itemNumber, 0, "0.2"},
   115  		tOr,
   116  		{itemFunc, 0, "avg"},
   117  		tLpar,
   118  		{itemFunc, 0, "q"},
   119  		tLpar,
   120  		{itemString, 0, `"sum:sys.cpu.user{host=*-web*}"`},
   121  		tComma,
   122  		{itemString, 0, `"1m"`},
   123  		tRpar,
   124  		tRpar,
   125  		tGt,
   126  		{itemNumber, 0, "0.4"},
   127  		tEOF,
   128  	}},
   129  	{"simple triple quote", `'''select'''`, []item{
   130  		{itemTripleQuotedString, 0, `'''select'''`},
   131  		tEOF,
   132  	}},
   133  	{"expression with triple quotes", `influx("db", '''select value from "mymetric.name.with.dots" where  "key" = 'single quoted value' and "other_key" = '' group by *''', "1h", "")`, []item{
   134  		{itemFunc, 0, "influx"},
   135  		tLpar,
   136  		{itemString, 0, `"db"`},
   137  		tComma,
   138  		{itemTripleQuotedString, 0, `'''select value from "mymetric.name.with.dots" where  "key" = 'single quoted value' and "other_key" = '' group by *'''`},
   139  		tComma,
   140  		{itemString, 0, `"1h"`},
   141  		tComma,
   142  		{itemString, 0, `""`},
   143  		tRpar,
   144  		tEOF,
   145  	}},
   146  	// errors
   147  	{"unclosed quote", "\"", []item{
   148  		{itemError, 0, "unterminated string"},
   149  	}},
   150  	{"single quote", "'single quote is invalid'", []item{
   151  		{itemError, 0, "invalid start of string, must use double qutoes or triple single quotes"},
   152  	}},
   153  	{"unclosed triple quote", "''' unclosed triple quote ''", []item{
   154  		{itemError, 0, "unterminated string"},
   155  	}},
   156  }
   157  
   158  // collect gathers the emitted items into a slice.
   159  func collect(t *lexTest) (items []item) {
   160  	l := lex(t.input)
   161  	for {
   162  		item := l.nextItem()
   163  		items = append(items, item)
   164  		if item.typ == itemEOF || item.typ == itemError {
   165  			break
   166  		}
   167  	}
   168  	return
   169  }
   170  
   171  func equal(i1, i2 []item, checkPos bool) bool {
   172  	if len(i1) != len(i2) {
   173  		return false
   174  	}
   175  	for k := range i1 {
   176  		if i1[k].typ != i2[k].typ {
   177  			return false
   178  		}
   179  		if i1[k].val != i2[k].val {
   180  			return false
   181  		}
   182  		if checkPos && i1[k].pos != i2[k].pos {
   183  			return false
   184  		}
   185  	}
   186  	return true
   187  }
   188  
   189  func TestLex(t *testing.T) {
   190  	for _, test := range lexTests {
   191  		items := collect(&test)
   192  		if !equal(items, test.items, false) {
   193  			t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
   194  		}
   195  	}
   196  }
   197  
   198  /*
   199  var lexPosTests = []lexTest{
   200  	{"empty", "", []item{tEOF}},
   201  	{"punctuation", "{{,@%#}}", []item{
   202  		{itemLeftDelim, 0, "{{"},
   203  		{itemChar, 2, ","},
   204  		{itemChar, 3, "@"},
   205  		{itemChar, 4, "%"},
   206  		{itemChar, 5, "#"},
   207  		{itemRightDelim, 6, "}}"},
   208  		{itemEOF, 8, ""},
   209  	}},
   210  	{"sample", "0123{{hello}}xyz", []item{
   211  		{itemText, 0, "0123"},
   212  		{itemLeftDelim, 4, "{{"},
   213  		{itemIdentifier, 6, "hello"},
   214  		{itemRightDelim, 11, "}}"},
   215  		{itemText, 13, "xyz"},
   216  		{itemEOF, 16, ""},
   217  	}},
   218  }
   219  
   220  // The other tests don't check position, to make the test cases easier to construct.
   221  // This one does.
   222  func TestPos(t *testing.T) {
   223  	for _, test := range lexPosTests {
   224  		items := collect(&test, "", "")
   225  		if !equal(items, test.items, true) {
   226  			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
   227  			if len(items) == len(test.items) {
   228  				// Detailed print; avoid item.String() to expose the position value.
   229  				for i := range items {
   230  					if !equal(items[i:i+1], test.items[i:i+1], true) {
   231  						i1 := items[i]
   232  						i2 := test.items[i]
   233  						t.Errorf("\t#%d: got {%v %d %q} expected  {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val)
   234  					}
   235  				}
   236  			}
   237  		}
   238  	}
   239  }
   240  */