github.com/CiscoM31/godata@v1.0.10/expression_parser_test.go (about)

     1  package godata
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  	"testing"
     9  )
    10  
    11  func TestTokenTypes(t *testing.T) {
    12  	if expressionTokenLast.String() != "expressionTokenLast" {
    13  		t.Errorf("Unexpected String() value: %v", expressionTokenLast)
    14  	}
    15  }
    16  
    17  func TestExpressionDateTime(t *testing.T) {
    18  	ctx := context.Background()
    19  	tokenizer := NewExpressionTokenizer()
    20  	tokens := map[string]ExpressionTokenType{
    21  		"2011-08-29T21:58Z":             ExpressionTokenDateTime,
    22  		"2011-08-29T21:58:33Z":          ExpressionTokenDateTime,
    23  		"2011-08-29T21:58:33.123Z":      ExpressionTokenDateTime,
    24  		"2011-08-29T21:58+11:23":        ExpressionTokenDateTime,
    25  		"2011-08-29T21:58:33+11:23":     ExpressionTokenDateTime,
    26  		"2011-08-29T21:58:33.123+11:23": ExpressionTokenDateTime,
    27  		"2011-08-29T21:58:33-11:23":     ExpressionTokenDateTime,
    28  		"2011-08-29":                    ExpressionTokenDate,
    29  		"21:58:33":                      ExpressionTokenTime,
    30  	}
    31  	for tokenValue, tokenType := range tokens {
    32  		// Previously, the unit test had no space character after 'gt'
    33  		// E.g. 'CreateTime gt2011-08-29T21:58Z' was considered valid.
    34  		// However the ABNF notation for ODATA logical operators is:
    35  		//   gtExpr = RWS "gt" RWS commonExpr
    36  		//   RWS = 1*( SP / HTAB / "%20" / "%09" )  ; "required" whitespace
    37  		//
    38  		// See http://docs.oasis-open.org/odata/odata/v4.01/csprd03/abnf/odata-abnf-construction-rules.txt
    39  		input := "CreateTime gt " + tokenValue
    40  		expect := []*Token{
    41  			{Value: "CreateTime", Type: ExpressionTokenLiteral},
    42  			{Value: "gt", Type: ExpressionTokenLogical},
    43  			{Value: tokenValue, Type: tokenType},
    44  		}
    45  		output, err := tokenizer.Tokenize(ctx, input)
    46  		if err != nil {
    47  			t.Errorf("Failed to tokenize input %s. Error: %v", input, err)
    48  		}
    49  
    50  		result, err := CompareTokens(expect, output)
    51  		if !result {
    52  			var a []string
    53  			for _, t := range output {
    54  				a = append(a, t.Value)
    55  			}
    56  
    57  			t.Errorf("Unexpected tokens for input '%s'. Tokens: %s Error: %v", input, strings.Join(a, ", "), err)
    58  		}
    59  	}
    60  }
    61  
    62  func TestValidBooleanExpressionSyntax(t *testing.T) {
    63  	queries := []string{
    64  		"substring(CompanyName,1,2) eq 'lf'", // substring with 3 arguments.
    65  		// Bolean values
    66  		"true",
    67  		"false",
    68  		"(true)",
    69  		"((true))",
    70  		"((true)) or false",
    71  		"not true",
    72  		"not false",
    73  		"not (not true)",
    74  		// TODO: this should work because 'not' is inherently right-associative.
    75  		// I.e. it should be interpreted as not (not true)
    76  		// If it were left-associative, it would be interpreted as (not not) true, which is invalid.
    77  		"not not true",
    78  		// String functions
    79  		"contains(CompanyName,'freds')",
    80  		"endswith(CompanyName,'Futterkiste')",
    81  		"startswith(CompanyName,'Alfr')",
    82  		"length(CompanyName) eq 19",
    83  		"indexof(CompanyName,'lfreds') eq 1",
    84  		"substring(CompanyName,1) eq 'lfreds Futterkiste'", // substring() with 2 arguments.
    85  		"'lfreds Futterkiste' eq substring(CompanyName,1)", // Same as above, but order of operands is reversed.
    86  		"substring(CompanyName,1,2) eq 'lf'",               // substring() with 3 arguments.
    87  		"'lf' eq substring(CompanyName,1,2) ",              // Same as above, but order of operands is reversed.
    88  		"substringof('Alfreds', CompanyName) eq true",
    89  		"tolower(CompanyName) eq 'alfreds futterkiste'",
    90  		"toupper(CompanyName) eq 'ALFREDS FUTTERKISTE'",
    91  		"trim(CompanyName) eq 'Alfreds Futterkiste'",
    92  		"concat(concat(City,', '), Country) eq 'Berlin, Germany'",
    93  		// GUID
    94  		"GuidValue eq 01234567-89ab-cdef-0123-456789abcdef", // According to ODATA ABNF notation, GUID values do not have quotes.
    95  		// Date and Time functions
    96  		"StartDate eq 2012-12-03",
    97  		"DateTimeOffsetValue eq 2012-12-03T07:16:23Z",
    98  		// duration      = [ "duration" ] SQUOTE durationValue SQUOTE
    99  		"DurationValue eq duration'P12DT23H59M59.999999999999S'", // See ODATA ABNF notation
   100  		"TimeOfDayValue eq 07:59:59.999",
   101  		"year(BirthDate) eq 0",
   102  		"month(BirthDate) eq 12",
   103  		"day(StartTime) eq 8",
   104  		"hour(StartTime) eq 1",
   105  		"hour    (StartTime) eq 12",     // function followed by space characters
   106  		"hour    ( StartTime   ) eq 15", // function followed by space characters
   107  		"minute(StartTime) eq 0",
   108  		"totaloffsetminutes(StartTime) eq 0",
   109  		"second(StartTime) eq 0",
   110  		"fractionalseconds(StartTime) lt 0.123456", // The fractionalseconds function returns the fractional seconds component of the
   111  		// DateTimeOffset or TimeOfDay parameter value as a non-negative decimal value less than 1.
   112  		"date(StartTime) ne date(EndTime)",
   113  		"totaloffsetminutes(StartTime) eq 60",
   114  		"StartTime eq mindatetime()",
   115  		"totalseconds(EndTime sub StartTime) lt duration'PT23H59M'", // The totalseconds function returns the duration of the value in total seconds, including fractional seconds.
   116  		"EndTime eq maxdatetime()",
   117  		"time(StartTime) le StartOfDay",
   118  		"time('2015-10-14T23:30:00.104+02:00') lt now()",
   119  		"time(2015-10-14T23:30:00.104+02:00) lt now()",
   120  		// Math functions
   121  		"round(Freight) eq 32",
   122  		"floor(Freight) eq 32",
   123  		"ceiling(Freight) eq 33",
   124  		"Rating mod 5 eq 0",
   125  		"Price div 2 eq 3",
   126  		// Functions
   127  		"contains(Name,'Ted')",
   128  		"startswith(Name,'Ted')",
   129  		"endswith(Name,'Lasso')",
   130  		"isof(ShipCountry,Edm.String)",
   131  		"isof(NorthwindModel.BigOrder)",
   132  		// Parameter aliases
   133  		// See http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752288
   134  		"Region eq @p1", // Aliases start with @
   135  		// Logical operators
   136  		"'Milk' eq 'Milk'",  // Compare two literals
   137  		"'Water' ne 'Milk'", // Compare two literals
   138  		"Name eq 'Milk'",
   139  		"Name EQ 'Milk'", // operators are case insensitive in ODATA 4.0.1
   140  		"Name ne 'Milk'",
   141  		"Name NE 'Milk'",
   142  		"Name gt 'Milk'",
   143  		"Name ge 'Milk'",
   144  		"Name lt 'Milk'",
   145  		"Name le 'Milk'",
   146  		"Name eq Name", // parameter equals to itself
   147  		"Name eq 'Milk' and Price lt 2.55",
   148  		"not endswith(Name,'ilk')",
   149  		"Name eq 'Milk' or Price lt 2.55",
   150  		"City eq 'Dallas' or City eq 'Houston'",
   151  		// Nested properties
   152  		"Product/Name eq 'Milk'",
   153  		"Region/Product/Name eq 'Milk'",
   154  		"Country/Region/Product/Name eq 'Milk'",
   155  		//"style has Sales.Pattern'Yellow'", // TODO
   156  		// Arithmetic operators
   157  		"Price add 2.45 eq 5.00",
   158  		"2.46 add Price eq 5.00",
   159  		"Price add (2.47) eq 5.00",
   160  		"(Price add (2.48)) eq 5.00",
   161  		"Price ADD 2.49 eq 5.00", // 4.01 Services MUST support case-insensitive operator names.
   162  		"Price sub 0.55 eq 2.00",
   163  		"Price SUB 0.56 EQ 2.00", // 4.01 Services MUST support case-insensitive operator names.
   164  		"Price mul 2.0 eq 5.10",
   165  		"Price div 2.55 eq 1",
   166  		"Rating div 2 eq 2",
   167  		"Rating mod 5 eq 0",
   168  		// Grouping
   169  		"(4 add 5) mod (4 sub 1) eq 0",
   170  		"not (City eq 'Dallas') or Name in ('a', 'b', 'c') and not (State eq 'California')",
   171  		// Nested functions
   172  		"length(trim(CompanyName)) eq length(CompanyName)",
   173  		"concat(concat(City, ', '), Country) eq 'Berlin, Germany'",
   174  		// Various parenthesis combinations
   175  		"City eq 'Dallas'",
   176  		"City eq ('Dallas')",
   177  		"'Dallas' eq City",
   178  		"not (City eq 'Dallas')",
   179  		"City in ('Dallas')",
   180  		"(City in ('Dallas'))",
   181  		"(City in ('Dallas', 'Houston'))",
   182  		"not (City in ('Dallas'))",
   183  		"not (City in ('Dallas', 'Houston'))",
   184  		"not (((City eq 'Dallas')))",
   185  		"not(S1 eq 'foo')",
   186  		// Lambda operators
   187  		"Tags/any()",                    // The any operator without an argument returns true if the collection is not empty
   188  		"Tags/any(tag:tag eq 'London')", // 'Tags' is array of strings
   189  		"Tags/any(tag:tag eq 'London' or tag eq 'Berlin')",          // 'Tags' is array of strings
   190  		"Tags/any(var:var/Key eq 'Site' and var/Value eq 'London')", // 'Tags' is array of {"Key": "abc", "Value": "def"}
   191  		"Tags/ANY(var:var/Key eq 'Site' AND var/Value eq 'London')",
   192  		"Tags/any(var:var/Key eq 'Site' and var/Value eq 'London') and not (City in ('Dallas'))",
   193  		"Tags/all(var:var/Key eq 'Site' and var/Value eq 'London')",
   194  		"Price/any(t:not (12345 eq t))",
   195  		// A long query.
   196  		"Tags/any(var:var/Key eq 'Site' and var/Value eq 'London') or " +
   197  			"Tags/any(var:var/Key eq 'Site' and var/Value eq 'Berlin') or " +
   198  			"Tags/any(var:var/Key eq 'Site' and var/Value eq 'Paris') or " +
   199  			"Tags/any(var:var/Key eq 'Site' and var/Value eq 'New York City') or " +
   200  			"Tags/any(var:var/Key eq 'Site' and var/Value eq 'San Francisco')",
   201  	}
   202  	ctx := context.Background()
   203  	p := NewExpressionParser()
   204  	p.ExpectBoolExpr = true
   205  	for _, input := range queries {
   206  		t.Logf("Testing expression %s", input)
   207  		q, err := p.ParseExpressionString(ctx, input)
   208  		if err != nil {
   209  			t.Errorf("Error parsing query '%s'. Error: %v", input, err)
   210  		} else {
   211  			if q.Tree == nil {
   212  				t.Errorf("Error parsing query '%s'. Tree is nil", input)
   213  			} else if q.Tree.Token == nil {
   214  				t.Errorf("Error parsing query '%s'. Root token is nil", input)
   215  			} else if q.Tree.Token.Type == ExpressionTokenLiteral {
   216  				t.Errorf("Error parsing query '%s'. Unexpected root token type: %+v", input, q.Tree.Token)
   217  			}
   218  		}
   219  		//printTree(q.Tree)
   220  	}
   221  }
   222  
   223  // The URLs below are not valid ODATA syntax, the parser should return an error.
   224  func TestInvalidBooleanExpressionSyntax(t *testing.T) {
   225  	ctx := context.Background()
   226  	queries := []string{
   227  		"(TRUE)",  // Should be true lowercase
   228  		"(City)",  // The literal City is not boolean
   229  		"12345",   // Number 12345 is not a boolean expression
   230  		"0",       // Number 0 is not a boolean expression
   231  		"'123'",   // String '123' is not a boolean expression
   232  		"TRUE",    // Should be 'true' lowercase
   233  		"FALSE",   // Should be 'false' lowercase
   234  		"yes",     // yes is not a boolean expression, though it's a literal value
   235  		"no",      // yes is not a boolean expression, though it's a literal value
   236  		"add 2 3", // Missing operands
   237  		"City",    // Just a single literal
   238  		"Tags/any(var:var/Key eq 'Site') orTags/any(var:var/Key eq 'Site')",
   239  		"contains(Name, 'a', 'b', 'c', 'd')", // Too many function arguments
   240  		"cast(ShipCountry,Edm.String)",
   241  		// Geo functions
   242  		"geo.distance(CurrentPosition,TargetPosition)",
   243  		"geo.length(DirectRoute)",
   244  		"geo.intersects(Position,TargetArea)",
   245  		"GEO.INTERSECTS(Position,TargetArea)", // functions are case insensitive in ODATA 4.0.1
   246  		"now()",
   247  		"tolower(Name)",
   248  		"concat(First,Last)",
   249  		"case(false:0,true:1)",
   250  	}
   251  	p := NewExpressionParser()
   252  	p.ExpectBoolExpr = true
   253  	for _, input := range queries {
   254  		q, err := p.ParseExpressionString(ctx, input)
   255  		if err == nil {
   256  			// The parser has incorrectly determined the syntax is valid.
   257  			t.Errorf("The expression '%s' is not valid ODATA syntax. The ODATA parser should return an error. Tree:\n%v", input, q.Tree)
   258  		}
   259  	}
   260  }
   261  
   262  func TestExpressionWithLenientFlags(t *testing.T) {
   263  	testCases := []struct {
   264  		expression string
   265  		valid      bool // true if parsing expression should be successful.
   266  		cfg        OdataComplianceConfig
   267  		setCtx     bool
   268  		tree       []expectedParseNode // The expected tree.
   269  	}{
   270  		{
   271  			expression: "(a, b, )",
   272  			valid:      false,
   273  			setCtx:     false,
   274  		},
   275  		{
   276  			expression: "(a, b, )",
   277  			valid:      false,
   278  			setCtx:     true,
   279  		},
   280  		{
   281  			expression: "(a, b, )",
   282  			valid:      false,
   283  			setCtx:     true,
   284  			cfg:        ComplianceStrict,
   285  		},
   286  		{
   287  			expression: "(a, b, )",
   288  			valid:      true, // Normally this would not be valid, but the ComplianceIgnoreInvalidComma flag is set.
   289  			setCtx:     true,
   290  			cfg:        ComplianceIgnoreInvalidComma,
   291  		},
   292  		{
   293  			expression: "City in ('Dallas', 'Houston', )",
   294  			valid:      true,
   295  			setCtx:     true,
   296  			cfg:        ComplianceIgnoreInvalidComma,
   297  			tree: []expectedParseNode{
   298  				{Value: "in", Depth: 0, Type: ExpressionTokenLogical},
   299  				{Value: "City", Depth: 1, Type: ExpressionTokenLiteral},
   300  				{Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr},
   301  				{Value: "'Dallas'", Depth: 2, Type: ExpressionTokenString},
   302  				{Value: "'Houston'", Depth: 2, Type: ExpressionTokenString},
   303  			},
   304  		},
   305  		{
   306  			expression: "(a, , b)", // This is not a list.
   307  			valid:      false,
   308  		},
   309  		{
   310  			expression: "(, a, b)", // This is not a list.
   311  			valid:      false,
   312  		},
   313  		{
   314  			expression: "(,)", // A comma by itself is not an expression
   315  			valid:      false,
   316  		},
   317  		{
   318  			expression: "(,)", // A comma by itself is not an expression
   319  			valid:      false,
   320  			setCtx:     true,
   321  			cfg:        ComplianceIgnoreInvalidComma,
   322  		},
   323  	}
   324  
   325  	p := NewExpressionParser()
   326  	p.ExpectBoolExpr = false
   327  	for _, testCase := range testCases {
   328  		t.Logf("testing: %s", testCase.expression)
   329  		ctx := context.Background()
   330  		if testCase.setCtx {
   331  			ctx = WithOdataComplianceConfig(ctx, testCase.cfg)
   332  		}
   333  		q, err := p.ParseExpressionString(ctx, testCase.expression)
   334  		if testCase.valid && err != nil {
   335  			// The parser has incorrectly determined the syntax is invalid.
   336  			t.Errorf("The expression '%s' is valid ODATA syntax. Cfg: %v The ODATA parser should not have returned an error",
   337  				testCase.expression, testCase.cfg)
   338  		} else if !testCase.valid && err == nil {
   339  			// The parser has incorrectly determined the syntax is valid.
   340  			t.Errorf("The expression '%s' is not valid ODATA syntax. The ODATA parser should return an error. Tree:\n%v",
   341  				testCase.expression, q.Tree)
   342  		} else if testCase.valid && testCase.tree != nil {
   343  			pos := 0
   344  			err = CompareTree(q.Tree, testCase.tree, &pos, 0)
   345  			if err != nil {
   346  				t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree)
   347  			}
   348  		}
   349  	}
   350  }
   351  
   352  func TestInvalidExpressionSyntax(t *testing.T) {
   353  	queries := []string{
   354  		"()", // It's not a boolean expression
   355  		"(",
   356  		"((((",
   357  		")",
   358  		"",                                     // Empty string.
   359  		"eq",                                   // Just a single logical operator
   360  		"and",                                  // Just a single logical operator
   361  		"add",                                  // Just a single arithmetic operator
   362  		"add ",                                 // Just a single arithmetic operator
   363  		"add 2",                                // Missing operands
   364  		"City City City City",                  // Sequence of literals
   365  		"City eq",                              // Missing operand
   366  		"City eq (",                            // Wrong operand
   367  		"City eq )",                            // Wrong operand
   368  		"City equals 'Dallas'",                 // Unknown operator that starts with the same letters as a known operator
   369  		"City near 'Dallas'",                   // Unknown operator that starts with the same letters as a known operator
   370  		"City isNot 'Dallas'",                  // Unknown operator
   371  		"not [City eq 'Dallas']",               // Wrong delimiter
   372  		"not (City eq )",                       // Missing operand
   373  		"not ((City eq 'Dallas'",               // Missing closing parenthesis
   374  		"not (City eq 'Dallas'",                // Missing closing parenthesis
   375  		"not (City eq 'Dallas'))",              // Extraneous closing parenthesis
   376  		"not City eq 'Dallas')",                // Missing open parenthesis
   377  		"City eq 'Dallas' orCity eq 'Houston'", // missing space between or and City
   378  		"not (City eq 'Dallas') and Name eq 'Houston')",
   379  		"Tags/all()",                   // The all operator cannot be used without an argument expression.
   380  		"LastName contains 'Smith'",    // Previously the godata library was not returning an error.
   381  		"contains",                     // Function with missing parenthesis and arguments
   382  		"contains()",                   // Function with missing arguments
   383  		"contains LastName, 'Smith'",   // Missing parenthesis
   384  		"contains(LastName)",           // Insufficent number of function arguments
   385  		"contains(LastName, 'Smith'))", // Extraneous closing parenthesis
   386  		"contains(LastName, 'Smith'",   // Missing closing parenthesis
   387  		"contains LastName, 'Smith')",  // Missing open parenthesis
   388  		"City eq 'Dallas' 'Houston'",   // extraneous string value
   389  		"(numCore neq 12)",             // Invalid operator. It should be 'ne'
   390  		"numCore neq 12",               // Invalid operator. It should be 'ne'
   391  		"(a b c d e)",                  // This is not a list.
   392  		"(a, b, )",                     // This is not a list.
   393  		"(a, , b)",                     // This is not a list.
   394  		"(, a, b)",                     // This is not a list.
   395  		"(a, not b c)",                 // Missing comma between (not b) and (c)
   396  		",",                            // A comma by itself is not an expression
   397  		",,,",                          // A comma by itself is not an expression
   398  		"(,)",                          // A comma by itself is not an expression
   399  		"contains(LastName, 'Smith'),", // Extra comma after the function call
   400  		"contains(LastName, 'Smith',)", // Extra comma after the last argument
   401  		"contains(,LastName, 'Smith')", // Extra comma before the first argument
   402  		"eq eq eq",                     // Invalid sequence of operators
   403  		"not not",                      // Invalid sequence of operators
   404  		"true true",                    // Invalid sequence of booleans
   405  		"1 2 3",                        // Invalid sequence of numbers
   406  		"1.4 2.34 3.1415",              // Invalid sequence of numbers
   407  		"a b c",                        // Invalid sequence of literals.
   408  		"'a' 'b' 'c'",                  // Invalid sequence of strings.
   409  	}
   410  	ctx := context.Background()
   411  	p := NewExpressionParser()
   412  	p.ExpectBoolExpr = false
   413  	for _, input := range queries {
   414  		t.Logf("testing: %s", input)
   415  		q, err := p.ParseExpressionString(ctx, input)
   416  		if err == nil {
   417  			// The parser has incorrectly determined the syntax is valid.
   418  			t.Errorf("The expression '%s' is not valid ODATA syntax. The ODATA parser should return an error. Tree:\n%v", input, q.Tree)
   419  		}
   420  	}
   421  }
   422  
   423  func BenchmarkExpressionTokenizer(b *testing.B) {
   424  	ctx := context.Background()
   425  	t := NewExpressionTokenizer()
   426  	for i := 0; i < b.N; i++ {
   427  		input := "Name eq 'Milk' and Price lt 2.55"
   428  		if _, err := t.Tokenize(ctx, input); err != nil {
   429  			b.Fatalf("Failed to tokenize expression: %v", err)
   430  		}
   431  	}
   432  }
   433  
   434  func tokenArrayToString(list []*Token) string {
   435  	var sb []string
   436  	for _, t := range list {
   437  		sb = append(sb, fmt.Sprintf("%s[%d]", t.Value, t.Type))
   438  	}
   439  	return strings.Join(sb, ", ")
   440  }
   441  
   442  // Check if two slices of tokens are the same.
   443  func CompareTokens(expected, actual []*Token) (bool, error) {
   444  	if len(expected) != len(actual) {
   445  		return false, fmt.Errorf("Infix tokens unexpected lengths. Expected %d, Got len=%d. Tokens=%v",
   446  			len(expected), len(actual), tokenArrayToString(actual))
   447  	}
   448  	for i := range expected {
   449  		if expected[i].Type != actual[i].Type {
   450  			return false, fmt.Errorf("Infix token types at index %d. Expected %v, Got %v. Value: %v",
   451  				i, expected[i].Type, actual[i].Type, expected[i].Value)
   452  		}
   453  		if expected[i].Value != actual[i].Value {
   454  			return false, fmt.Errorf("Infix token values at index %d. Expected %v, Got %v",
   455  				i, expected[i].Value, actual[i].Value)
   456  		}
   457  	}
   458  	return true, nil
   459  }
   460  
   461  func CompareQueue(expect []*Token, b *tokenQueue) error {
   462  	if b == nil {
   463  		return fmt.Errorf("Got nil token queue")
   464  	}
   465  	bl := func() int {
   466  		if b.Empty() {
   467  			return 0
   468  		}
   469  		l := 1
   470  		for node := b.Head; node != b.Tail; node = node.Next {
   471  			l++
   472  		}
   473  		return l
   474  	}()
   475  	if len(expect) != bl {
   476  		return fmt.Errorf("Postfix queue unexpected length. Got len=%d, expected %d. queue=%v",
   477  			bl, len(expect), b)
   478  	}
   479  	node := b.Head
   480  	for i := range expect {
   481  		if expect[i].Type != node.Token.Type {
   482  			return fmt.Errorf("Postfix token types at index %d. Got: %v, expected: %v. Expected value: %v",
   483  				i, node.Token.Type, expect[i].Type, expect[i].Value)
   484  		}
   485  		if expect[i].Value != node.Token.Value {
   486  			return fmt.Errorf("Postfix token values at index %d. Got: %v, expected: %v",
   487  				i, node.Token.Value, expect[i].Value)
   488  		}
   489  		node = node.Next
   490  	}
   491  	return nil
   492  }
   493  
   494  func printTokens(tokens []*Token) {
   495  	s := make([]string, len(tokens))
   496  	for i := range tokens {
   497  		s[i] = tokens[i].Value
   498  	}
   499  	fmt.Printf("TOKENS: %s\n", strings.Join(s, " "))
   500  }
   501  
   502  // CompareTree compares a tree representing a ODATA filter with the expected results.
   503  // The expected values are a slice of nodes in breadth-first traversal.
   504  func CompareTree(node *ParseNode, expect []expectedParseNode, pos *int, level int) error {
   505  	if *pos >= len(expect) {
   506  		return fmt.Errorf("Unexpected token at pos %d. Got %s, expected no value",
   507  			*pos, node.Token.Value)
   508  	}
   509  	if node == nil {
   510  		return fmt.Errorf("Node should not be nil")
   511  	}
   512  	if node.Token.Value !=
   513  		expect[*pos].Value {
   514  		return fmt.Errorf("Unexpected token at pos %d. Got %s -> %d, expected: %s -> %d",
   515  			*pos, node.Token.Value, level, expect[*pos].Value, expect[*pos].Depth)
   516  	}
   517  	if node.Token.Type != expect[*pos].Type {
   518  		return fmt.Errorf("Unexpected token type at pos %d. Got %v -> %d, expected: %v -> %d",
   519  			*pos, node.Token.Type, level, expect[*pos].Type, expect[*pos].Depth)
   520  	}
   521  	if level != expect[*pos].Depth {
   522  		return fmt.Errorf("Unexpected level at pos %d. Got %s -> %d, expected: %s -> %d",
   523  			*pos, node.Token.Value, level, expect[*pos].Value, expect[*pos].Depth)
   524  	}
   525  	for _, v := range node.Children {
   526  		*pos++
   527  		if err := CompareTree(v, expect, pos, level+1); err != nil {
   528  			return err
   529  		}
   530  	}
   531  	if level == 0 && *pos+1 != len(expect) {
   532  		return fmt.Errorf("Expected number of tokens: %d, got %d", len(expect), *pos+1)
   533  	}
   534  	return nil
   535  }
   536  
   537  func TestExpressions(t *testing.T) {
   538  	ctx := context.Background()
   539  	p := NewExpressionParser()
   540  	for _, testCase := range testCases {
   541  		t.Logf("Expression: %s", testCase.expression)
   542  		tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, testCase.expression)
   543  		if err != nil {
   544  			t.Errorf("Failed to tokenize expression '%s'. Error: %v", testCase.expression, err)
   545  			continue
   546  		}
   547  		if testCase.infixTokens != nil {
   548  			if result, err := CompareTokens(testCase.infixTokens, tokens); !result {
   549  				t.Errorf("Unexpected tokens: %v", err)
   550  				continue
   551  			}
   552  		}
   553  		output, err := p.InfixToPostfix(ctx, tokens)
   554  		if err != nil {
   555  			t.Errorf("Failed to convert expression to postfix notation: %v", err)
   556  			continue
   557  		}
   558  		if testCase.postfixTokens != nil {
   559  			if err := CompareQueue(testCase.postfixTokens, output); err != nil {
   560  				t.Errorf("Unexpected postfix tokens: %v", err)
   561  				continue
   562  			}
   563  		}
   564  		tree, err := p.PostfixToTree(ctx, output)
   565  		if err != nil {
   566  			t.Errorf("Failed to parse expression '%s'. Error: %v", testCase.expression, err)
   567  			continue
   568  		}
   569  		pos := 0
   570  		err = CompareTree(tree, testCase.tree, &pos, 0)
   571  		if err != nil {
   572  			t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree)
   573  		}
   574  	}
   575  
   576  }
   577  func TestDuration(t *testing.T) {
   578  	testCases := []struct {
   579  		value string
   580  		valid bool
   581  	}{
   582  		{value: "duration'P12DT23H59M59.999999999999S'", valid: true},
   583  		// three years, six months, four days, twelve hours, thirty minutes, and five seconds
   584  		{value: "duration'P3Y6M4DT12H30M5S'", valid: true},
   585  		// Date and time elements including their designator may be omitted if their value is zero,
   586  		// and lower-order elements may also be omitted for reduced precision.
   587  		{value: "duration'P23DT23H'", valid: true},
   588  		{value: "duration'P4Y'", valid: true},
   589  		// However, at least one element must be present,
   590  		// thus "P" is not a valid representation for a duration of 0 seconds.
   591  		{value: "duration'P'", valid: false},
   592  		// "PT0S" or "P0D", however, are both valid and represent the same duration.
   593  		{value: "duration'PT0S'", valid: true},
   594  		{value: "duration'P0D'", valid: true},
   595  		// To resolve ambiguity, "P1M" is a one-month duration and "PT1M" is a one-minute duration
   596  		{value: "duration'P1M'", valid: true},
   597  		{value: "duration'PT1M'", valid: true},
   598  		// The standard does not prohibit date and time values in a duration representation
   599  		// from exceeding their "carry over points" except as noted below.
   600  		// Thus, "PT36H" could be used as well as "P1DT12H" for representing the same duration.
   601  		{value: "duration'PT36H'", valid: true},
   602  		{value: "duration'P1DT12H'", valid: true},
   603  		{value: "duration'PT23H59M'", valid: true},
   604  		{value: "duration'PT23H59'", valid: false}, // missing units
   605  
   606  		{value: "duration'H0D'", valid: false},
   607  		{value: "foo", valid: false},
   608  
   609  		// TODO: the duration values below should be valid
   610  		// The smallest value used may also have a decimal fraction,[35] as in "P0.5Y" to indicate half a year.
   611  		{value: "duration'P0.5Y'", valid: false}, // half a year
   612  		{value: "duration'P0.5M'", valid: false}, // half a month
   613  		// This decimal fraction may be specified with either a comma or a full stop, as in "P0,5Y" or "P0.5Y".
   614  		{value: "duration'P0,5Y'", valid: false},
   615  	}
   616  	re, err := regexp.Compile(tokenDurationRe)
   617  	if err != nil {
   618  		t.Fatalf("Invalid regex: %v", err)
   619  	}
   620  	for _, testCase := range testCases {
   621  		m := re.MatchString(testCase.value)
   622  		if m != testCase.valid {
   623  			t.Errorf("Value: %s. Expected regex match: %v, got %v",
   624  				testCase.value, testCase.valid, m)
   625  		}
   626  	}
   627  }