src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/filter/compile_test.go (about)

     1  package filter_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"src.elv.sh/pkg/edit/filter"
     7  	"src.elv.sh/pkg/parse"
     8  )
     9  
    10  func TestCompile(t *testing.T) {
    11  	test(t,
    12  		That("empty filter matches anything").
    13  			Filter("").Matches("foo", "bar", " ", ""),
    14  
    15  		That("bareword matches any string containing it").
    16  			Filter("foo").Matches("foobar", "afoo").DoesNotMatch("", "faoo"),
    17  		That("bareword is case-insensitive is filter is all lower case").
    18  			Filter("foo").Matches("FOO", "Foo", "FOObar").DoesNotMatch("", "faoo"),
    19  		That("bareword is case-sensitive is filter is not all lower case").
    20  			Filter("Foo").Matches("Foobar").DoesNotMatch("foo", "FOO"),
    21  
    22  		That("double quoted string works like bareword").
    23  			Filter(`"foo"`).Matches("FOO", "Foo", "FOObar").DoesNotMatch("", "faoo"),
    24  
    25  		That("single quoted string works like bareword").
    26  			Filter(`'foo'`).Matches("FOO", "Foo", "FOObar").DoesNotMatch("", "faoo"),
    27  
    28  		That("space-separated words work like an AND filter").
    29  			Filter("foo bar").
    30  			Matches("foobar", "bar foo", "foo lorem ipsum bar").
    31  			DoesNotMatch("foo", "bar", ""),
    32  
    33  		That("quoted string can be used when string contains spaces").
    34  			Filter(`"foo bar"`).
    35  			Matches("__foo bar xyz").
    36  			DoesNotMatch("foobar"),
    37  
    38  		That("AND filter matches if all components match").
    39  			Filter("[and foo bar]").Matches("foobar", "bar foo").DoesNotMatch("foo"),
    40  		That("OR filter matches if any component matches").
    41  			Filter("[or foo bar]").Matches("foo", "bar", "foobar").DoesNotMatch(""),
    42  		That("RE filter uses component as regular expression to match").
    43  			Filter("[re f..]").Matches("foo", "f..").DoesNotMatch("fo", ""),
    44  
    45  		// Invalid queries
    46  		That("empty list is invalid").
    47  			Filter("[]").DoesNotCompile("empty subfilter"),
    48  		That("starting list with non-literal is invalid").
    49  			Filter("[[foo] bar]").
    50  			DoesNotCompile("non-literal subfilter head not supported"),
    51  		That("RE filter with no argument is invalid").
    52  			Filter("[re]").
    53  			DoesNotCompile("re subfilter with no argument not supported"),
    54  		That("RE filter with two or more arguments is invalid").
    55  			Filter("[re foo bar]").
    56  			DoesNotCompile("re subfilter with two or more arguments not supported"),
    57  		That("RE filter with invalid regular expression is invalid").
    58  			Filter("[re '[']").
    59  			DoesNotCompile("error parsing regexp: missing closing ]: `[`"),
    60  		That("invalid syntax results in parse error").
    61  			Filter("[and").DoesNotParse("parse error: [filter]:1:5: should be ']'"),
    62  
    63  		// Unsupported for now, but may be in future
    64  		That("options are not supported yet").
    65  			Filter("foo &k=v").DoesNotCompile("option not supported"),
    66  		That("compound expressions are not supported yet").
    67  			Filter(`a"foo"`).DoesNotCompile("compound expression not supported"),
    68  		That("indexing expressions are not supported yet").
    69  			Filter("foo[0]").DoesNotCompile("indexing expression not supported"),
    70  		That("variable references are not supported yet").
    71  			Filter("$a").
    72  			DoesNotCompile("primary expression of type Variable not supported"),
    73  		That("variable references in RE subfilter are not supported yet").
    74  			Filter("[re $a]").
    75  			DoesNotCompile("re subfilter with primary expression of type Variable not supported"),
    76  		That("variable references in AND subfilter are not supported yet").
    77  			Filter("[and $a]").
    78  			DoesNotCompile("primary expression of type Variable not supported"),
    79  		That("variable references in OR subfilter are not supported yet").
    80  			Filter("[or $a]").
    81  			DoesNotCompile("primary expression of type Variable not supported"),
    82  		That("other subqueries are not supported yet").
    83  			Filter("[other foo bar]").
    84  			DoesNotCompile("head other not supported"),
    85  	)
    86  }
    87  
    88  func test(t *testing.T, tests ...testCase) {
    89  	for _, test := range tests {
    90  		t.Run(test.name, func(t *testing.T) {
    91  			q, err := filter.Compile(test.filter)
    92  			if errType := getErrorType(err); errType != test.errorType {
    93  				t.Errorf("%q should have %s, but has %s",
    94  					test.filter, test.errorType, errType)
    95  			}
    96  			if err != nil {
    97  				if err.Error() != test.errorMessage {
    98  					t.Errorf("%q should have error message %q, but is %q",
    99  						test.filter, test.errorMessage, err)
   100  				}
   101  				return
   102  			}
   103  			for _, s := range test.matches {
   104  				ok := q.Match(s)
   105  				if !ok {
   106  					t.Errorf("%q should match %q, but doesn't", test.filter, s)
   107  				}
   108  			}
   109  			for _, s := range test.doesntMatch {
   110  				ok := q.Match(s)
   111  				if ok {
   112  					t.Errorf("%q shouldn't match %q, but does", test.filter, s)
   113  				}
   114  			}
   115  		})
   116  	}
   117  }
   118  
   119  type testCase struct {
   120  	name         string
   121  	filter       string
   122  	matches      []string
   123  	doesntMatch  []string
   124  	errorType    errorType
   125  	errorMessage string
   126  }
   127  
   128  func That(name string) testCase {
   129  	return testCase{name: name}
   130  }
   131  
   132  func (t testCase) Filter(q string) testCase {
   133  	t.filter = q
   134  	return t
   135  }
   136  
   137  func (t testCase) DoesNotParse(message string) testCase {
   138  	t.errorType = parseError
   139  	t.errorMessage = message
   140  	return t
   141  }
   142  
   143  func (t testCase) DoesNotCompile(message string) testCase {
   144  	t.errorType = compileError
   145  	t.errorMessage = message
   146  	return t
   147  }
   148  
   149  func (t testCase) Matches(s ...string) testCase {
   150  	t.matches = s
   151  	return t
   152  }
   153  
   154  func (t testCase) DoesNotMatch(s ...string) testCase {
   155  	t.doesntMatch = s
   156  	return t
   157  }
   158  
   159  type errorType uint
   160  
   161  const (
   162  	noError errorType = iota
   163  	parseError
   164  	compileError
   165  )
   166  
   167  func getErrorType(err error) errorType {
   168  	if err == nil {
   169  		return noError
   170  	} else if parse.UnpackErrors(err) != nil {
   171  		return parseError
   172  	} else {
   173  		return compileError
   174  	}
   175  }
   176  
   177  func (et errorType) String() string {
   178  	switch et {
   179  	case noError:
   180  		return "no error"
   181  	case parseError:
   182  		return "parse error"
   183  	case compileError:
   184  		return "compile error"
   185  	default:
   186  		panic("unreachable")
   187  	}
   188  }