go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/selector/parse_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package selector
     9  
    10  import (
    11  	"fmt"
    12  	"testing"
    13  
    14  	. "go.charczuk.com/sdk/assert"
    15  )
    16  
    17  func Test_MustParse(t *testing.T) {
    18  	ItsEqual(t, "x == a", MustParse("x==a").String())
    19  
    20  	var err error
    21  	func() {
    22  		defer func() {
    23  			if r := recover(); r != nil {
    24  				err = fmt.Errorf("%v", r)
    25  			}
    26  		}()
    27  		MustParse("x!!")
    28  	}()
    29  	ItsNotNil(t, err)
    30  }
    31  
    32  func Test_Parse_invalid(t *testing.T) {
    33  	testBadStrings := []string{
    34  		"x=a||y=b",
    35  		"x==a==b",
    36  		"!x=a",
    37  		"x<a",
    38  		"x>1",
    39  		"x>1,z<5",
    40  		"x=",
    41  		"x= ",
    42  		"x=,z= ",
    43  		"x= ,z= ",
    44  		"foo == bar foo",
    45  	}
    46  	var err error
    47  	for _, str := range testBadStrings {
    48  		_, err = Parse(str)
    49  		ItsNotNil(t, err, str)
    50  	}
    51  }
    52  
    53  func Test_Parse_semiValid(t *testing.T) {
    54  	testGoodStrings := []string{
    55  		"",
    56  		"x=a,y=b,z=c",
    57  		"x!=a,y=b",
    58  		"x",
    59  		"!x",
    60  		"!x,y",
    61  		"!x,y,z",
    62  		"!x,y,!z",
    63  		"!x,y,z,a",
    64  		"!x,y,z,!a",
    65  	}
    66  
    67  	var err error
    68  	for _, str := range testGoodStrings {
    69  		_, err = Parse(str)
    70  		ItsNil(t, err, str)
    71  	}
    72  }
    73  
    74  func Test_Parse_trailingComma(t *testing.T) {
    75  	testBadStrings := []string{
    76  		",",
    77  		"x,",
    78  		"!x,",
    79  		"foo==bar,",
    80  		"foo!=bar,",
    81  		"foo in (bar,baz),",
    82  		"foo not in (bar,baz),",
    83  	}
    84  
    85  	var err error
    86  	for _, str := range testBadStrings {
    87  		_, err = Parse(str)
    88  		ItsNotNil(t, err, str)
    89  	}
    90  }
    91  
    92  func Test_Parse_equals(t *testing.T) {
    93  	valid := Labels{
    94  		"foo": "bar",
    95  		"moo": "lar",
    96  	}
    97  	invalid := Labels{
    98  		"zoo": "mar",
    99  		"moo": "lar",
   100  	}
   101  
   102  	selector, err := Parse("foo == bar")
   103  	ItsNil(t, err)
   104  	ItsEqual(t, true, selector.Matches(valid))
   105  	ItsEqual(t, false, selector.Matches(invalid))
   106  }
   107  
   108  func Test_Parse_notEquals(t *testing.T) {
   109  	valid := Labels{
   110  		"foo": "far",
   111  		"moo": "lar",
   112  	}
   113  	invalidPresent := Labels{
   114  		"foo": "bar",
   115  		"moo": "lar",
   116  	}
   117  	invalidMissing := Labels{
   118  		"zoo": "mar",
   119  		"moo": "lar",
   120  	}
   121  
   122  	selector, err := Parse("foo != bar")
   123  	ItsNil(t, err)
   124  	ItsEqual(t, true, selector.Matches(valid))
   125  	ItsEqual(t, true, selector.Matches(invalidMissing))
   126  	ItsEqual(t, false, selector.Matches(invalidPresent))
   127  }
   128  
   129  func Test_Parse_In(t *testing.T) {
   130  	valid := Labels{
   131  		"foo": "far",
   132  		"moo": "lar",
   133  	}
   134  	valid2 := Labels{
   135  		"foo": "bar",
   136  		"moo": "lar",
   137  	}
   138  	invalid := Labels{
   139  		"foo": "mar",
   140  		"moo": "lar",
   141  	}
   142  	invalidMissing := Labels{
   143  		"zoo": "mar",
   144  		"moo": "lar",
   145  	}
   146  
   147  	selector, err := Parse("foo in (bar,far)")
   148  	ItsNil(t, err)
   149  	ItsEqual(t, true, selector.Matches(valid), selector.String())
   150  	ItsEqual(t, true, selector.Matches(valid2))
   151  	ItsEqual(t, false, selector.Matches(invalidMissing))
   152  	ItsEqual(t, false, selector.Matches(invalid), selector.String())
   153  }
   154  
   155  func Test_Parse_group(t *testing.T) {
   156  	valid := Labels{
   157  		"zoo":   "mar",
   158  		"moo":   "lar",
   159  		"thing": "map",
   160  	}
   161  	invalid := Labels{
   162  		"zoo":   "mar",
   163  		"moo":   "something",
   164  		"thing": "map",
   165  	}
   166  	invalid2 := Labels{
   167  		"zoo":    "mar",
   168  		"moo":    "lar",
   169  		"!thing": "map",
   170  	}
   171  	selector, err := Parse("zoo=mar, moo=lar, thing")
   172  	ItsNil(t, err)
   173  	ItsEqual(t, true, selector.Matches(valid))
   174  	ItsEqual(t, false, selector.Matches(invalid))
   175  	ItsEqual(t, false, selector.Matches(invalid2))
   176  
   177  	complicated, err := Parse("zoo in (mar,lar,dar),moo,!thingy")
   178  	ItsNil(t, err)
   179  	ItsNotNil(t, complicated)
   180  	ItsEqual(t, true, complicated.Matches(valid))
   181  }
   182  
   183  func Test_Parse_groupComplicated(t *testing.T) {
   184  	valid := Labels{
   185  		"zoo":   "mar",
   186  		"moo":   "lar",
   187  		"thing": "map",
   188  	}
   189  	complicated, err := Parse("zoo in (mar,lar,dar),moo,thing == map,!thingy")
   190  	ItsNil(t, err)
   191  	ItsNotNil(t, complicated)
   192  	ItsEqual(t, true, complicated.Matches(valid))
   193  }
   194  
   195  func Test_Parse_docsExample(t *testing.T) {
   196  	sel, err := Parse("x in (foo,,baz),y,z notin ()")
   197  	ItsNil(t, err)
   198  	ItsNotNil(t, sel)
   199  }
   200  
   201  func Test_Parse_subdomainKey(t *testing.T) {
   202  	sel, err := Parse("example.com/failure-domain == primary")
   203  	ItsNil(t, err)
   204  	ItsNotNil(t, sel)
   205  	ItsEqual(t, "example.com/failure-domain == primary", sel.String())
   206  	ItsEqual(t, true, sel.Matches(map[string]string{
   207  		"bar":                        "foo",
   208  		"example.com/failure-domain": "primary",
   209  		"foo":                        "bar",
   210  	}))
   211  }
   212  
   213  func Test_Parse_equalsOperators(t *testing.T) {
   214  	selector, err := Parse("notin=in")
   215  	ItsNil(t, err)
   216  
   217  	typed, isTyped := selector.(Equals)
   218  	ItsEqual(t, true, isTyped)
   219  	ItsEqual(t, "notin", typed.Key)
   220  	ItsEqual(t, "in", typed.Value)
   221  }
   222  
   223  func Test_Parse_validate(t *testing.T) {
   224  	_, err := Parse("zoo=bar")
   225  	ItsNil(t, err)
   226  
   227  	_, err = Parse("_zoo=bar")
   228  	ItsNotNil(t, err)
   229  
   230  	_, err = Parse("_zoo=_bar")
   231  	ItsNotNil(t, err)
   232  
   233  	_, err = Parse("zoo=bar,foo=_mar")
   234  	ItsNotNil(t, err)
   235  }
   236  
   237  func Test_Parse_regressionCSVSymbols(t *testing.T) {
   238  	sel, err := Parse("foo in (bar-bar, baz.baz, buzz_buzz), moo=boo")
   239  	ItsNil(t, err, "regression is csv values can have '-', '.' and '_' in them")
   240  	ItsNotEmpty(t, sel.String())
   241  }
   242  
   243  func Test_Parse_regressionCSVEarlyTermination(t *testing.T) {
   244  	sel, err := Parse("foo in (bar,), moo=boo")
   245  	ItsNil(t, err)
   246  	ItsNotEmpty(t, sel.String())
   247  }
   248  
   249  func Test_Parse_regressionCSVMultipleSpaces(t *testing.T) {
   250  	sel, err := Parse("foo in (bar,  foo), moo=boo")
   251  	ItsNil(t, err)
   252  	ItsNotEmpty(t, sel.String())
   253  }
   254  
   255  func Test_Parse_regressionIn(t *testing.T) {
   256  	_, err := Parse("foo in bar, buzz)")
   257  	ItsNotNil(t, err)
   258  }
   259  
   260  func Test_Parse_multiByte(t *testing.T) {
   261  	selector, err := Parse("함=수,목=록") // number=number, number=rock
   262  	ItsNil(t, err)
   263  	ItsNotNil(t, selector)
   264  
   265  	typed, isTyped := selector.(And)
   266  	ItsEqual(t, true, isTyped)
   267  	ItsEqual(t, 2, Len(typed))
   268  }
   269  
   270  func Test_Parse_options(t *testing.T) {
   271  	selQuery := "bar=foo@bar"
   272  	labels := Labels{
   273  		"foo": "bar",
   274  		"bar": "foo@bar",
   275  	}
   276  
   277  	sel, err := Parse(selQuery)
   278  	ItsNotNil(t, err)
   279  	ItsNil(t, sel)
   280  
   281  	sel, err = Parse(selQuery, SkipValidation)
   282  	ItsNil(t, err)
   283  	ItsNotNil(t, sel)
   284  
   285  	ItsEqual(t, true, sel.Matches(labels))
   286  }
   287  
   288  func Benchmark_Parse(b *testing.B) {
   289  	valid := Labels{
   290  		"zoo":   "mar",
   291  		"moo":   "lar",
   292  		"thing": "map",
   293  	}
   294  
   295  	for i := 0; i < b.N; i++ {
   296  		selector, err := Parse("zoo in (mar,lar,dar),moo,!thingy")
   297  		if err != nil {
   298  			b.Fail()
   299  		}
   300  		if !selector.Matches(valid) {
   301  			b.Fail()
   302  		}
   303  	}
   304  }
   305  
   306  func Test_Parse_fuzzRegressions(t *testing.T) {
   307  	var sel Selector
   308  	var err error
   309  	testBadStrings := []string{
   310  		"!0!0",
   311  		"0!=0,!",
   312  	}
   313  	for _, str := range testBadStrings {
   314  		_, err = Parse(str)
   315  		ItsNotNil(t, err, str, err)
   316  	}
   317  
   318  	testGoodStrings := []string{
   319  		"0,!0",
   320  		"0 in (0), !0",
   321  	}
   322  	for _, str := range testGoodStrings {
   323  		sel, err = Parse(str)
   324  		ItsNil(t, err)
   325  		_, err = Parse(sel.String())
   326  		ItsNil(t, err)
   327  	}
   328  }
   329  
   330  func TestParse_InRegression(t *testing.T) {
   331  	good0 := Labels{
   332  		"role": "job",
   333  		"team": "internal-engineering",
   334  	}
   335  	good1 := Labels{
   336  		"role": "job-worker",
   337  		"team": "internal-engineering",
   338  	}
   339  	bad0 := Labels{
   340  		"role": "not-job",
   341  	}
   342  
   343  	sel, err := Parse("role in (job, job-worker),team=internal-engineering")
   344  	ItsNil(t, err)
   345  
   346  	ItsEqual(t, true, sel.Matches(good0))
   347  	ItsEqual(t, true, sel.Matches(good1))
   348  	ItsEqual(t, false, sel.Matches(bad0))
   349  }