github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/schemadsl/lexer/lex_test.go (about)

     1  package lexer
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/authzed/spicedb/pkg/schemadsl/input"
     7  )
     8  
     9  type lexerTest struct {
    10  	name   string
    11  	input  string
    12  	tokens []Lexeme
    13  }
    14  
    15  var (
    16  	tEOF        = Lexeme{TokenTypeEOF, 0, "", ""}
    17  	tWhitespace = Lexeme{TokenTypeWhitespace, 0, " ", ""}
    18  )
    19  
    20  var lexerTests = []lexerTest{
    21  	// Simple tests.
    22  	{"empty", "", []Lexeme{tEOF}},
    23  
    24  	{"single whitespace", " ", []Lexeme{tWhitespace, tEOF}},
    25  	{"single tab", "\t", []Lexeme{{TokenTypeWhitespace, 0, "\t", ""}, tEOF}},
    26  	{"multiple whitespace", "   ", []Lexeme{tWhitespace, tWhitespace, tWhitespace, tEOF}},
    27  
    28  	{"newline r", "\r", []Lexeme{{TokenTypeNewline, 0, "\r", ""}, tEOF}},
    29  	{"newline n", "\n", []Lexeme{{TokenTypeNewline, 0, "\n", ""}, tEOF}},
    30  	{"newline rn", "\r\n", []Lexeme{{TokenTypeNewline, 0, "\r", ""}, {TokenTypeNewline, 0, "\n", ""}, tEOF}},
    31  
    32  	{"comment", "// a comment", []Lexeme{{TokenTypeSinglelineComment, 0, "// a comment", ""}, tEOF}},
    33  	{"multiline comment", "/* a comment\n foo*/", []Lexeme{{TokenTypeMultilineComment, 0, "/* a comment\n foo*/", ""}, tEOF}},
    34  
    35  	{"left brace", "{", []Lexeme{{TokenTypeLeftBrace, 0, "{", ""}, tEOF}},
    36  	{"right brace", "}", []Lexeme{{TokenTypeRightBrace, 0, "}", ""}, tEOF}},
    37  
    38  	{"left paren", "(", []Lexeme{{TokenTypeLeftParen, 0, "(", ""}, tEOF}},
    39  	{"right paren", ")", []Lexeme{{TokenTypeRightParen, 0, ")", ""}, tEOF}},
    40  
    41  	{"semicolon", ";", []Lexeme{{TokenTypeSemicolon, 0, ";", ""}, tEOF}},
    42  	{"star", "*", []Lexeme{{TokenTypeStar, 0, "*", ""}, tEOF}},
    43  
    44  	{"right arrow", "->", []Lexeme{{TokenTypeRightArrow, 0, "->", ""}, tEOF}},
    45  
    46  	{"hash", "#", []Lexeme{{TokenTypeHash, 0, "#", ""}, tEOF}},
    47  	{"ellipsis", "...", []Lexeme{{TokenTypeEllipsis, 0, "...", ""}, tEOF}},
    48  
    49  	{"relation reference", "foo#...", []Lexeme{
    50  		{TokenTypeIdentifier, 0, "foo", ""},
    51  		{TokenTypeHash, 0, "#", ""},
    52  		{TokenTypeEllipsis, 0, "...", ""},
    53  		tEOF,
    54  	}},
    55  
    56  	{"plus", "+", []Lexeme{{TokenTypePlus, 0, "+", ""}, tEOF}},
    57  	{"minus", "-", []Lexeme{{TokenTypeMinus, 0, "-", ""}, tEOF}},
    58  
    59  	{"keyword", "definition", []Lexeme{{TokenTypeKeyword, 0, "definition", ""}, tEOF}},
    60  	{"keyword", "nil", []Lexeme{{TokenTypeKeyword, 0, "nil", ""}, tEOF}},
    61  	{"identifier", "define", []Lexeme{{TokenTypeIdentifier, 0, "define", ""}, tEOF}},
    62  	{"typepath", "foo/bar", []Lexeme{
    63  		{TokenTypeIdentifier, 0, "foo", ""},
    64  		{TokenTypeDiv, 0, "/", ""},
    65  		{TokenTypeIdentifier, 0, "bar", ""},
    66  		tEOF,
    67  	}},
    68  
    69  	{"multiple slash path", "foo/bar/baz/bang/zoom", []Lexeme{
    70  		{TokenTypeIdentifier, 0, "foo", ""},
    71  		{TokenTypeDiv, 0, "/", ""},
    72  		{TokenTypeIdentifier, 0, "bar", ""},
    73  		{TokenTypeDiv, 0, "/", ""},
    74  		{TokenTypeIdentifier, 0, "baz", ""},
    75  		{TokenTypeDiv, 0, "/", ""},
    76  		{TokenTypeIdentifier, 0, "bang", ""},
    77  		{TokenTypeDiv, 0, "/", ""},
    78  		{TokenTypeIdentifier, 0, "zoom", ""},
    79  		tEOF,
    80  	}},
    81  
    82  	{"type star", "foo:*", []Lexeme{
    83  		{TokenTypeIdentifier, 0, "foo", ""},
    84  		{TokenTypeColon, 0, ":", ""},
    85  		{TokenTypeStar, 0, "*", ""},
    86  		tEOF,
    87  	}},
    88  
    89  	{"expression", "foo->bar", []Lexeme{
    90  		{TokenTypeIdentifier, 0, "foo", ""},
    91  		{TokenTypeRightArrow, 0, "->", ""},
    92  		{TokenTypeIdentifier, 0, "bar", ""},
    93  		tEOF,
    94  	}},
    95  
    96  	{"relation", "/* foo */relation parent: namespace | organization\n", []Lexeme{
    97  		{TokenTypeMultilineComment, 0, "/* foo */", ""},
    98  		{TokenTypeKeyword, 0, "relation", ""},
    99  		tWhitespace,
   100  		{TokenTypeIdentifier, 0, "parent", ""},
   101  		{TokenTypeColon, 0, ":", ""},
   102  		tWhitespace,
   103  		{TokenTypeIdentifier, 0, "namespace", ""},
   104  		tWhitespace,
   105  		{TokenTypePipe, 0, "|", ""},
   106  		tWhitespace,
   107  		{TokenTypeIdentifier, 0, "organization", ""},
   108  		{TokenTypeSyntheticSemicolon, 0, "\n", ""},
   109  		tEOF,
   110  	}},
   111  
   112  	{"relation", "/* foo */relation parent: namespace | organization;", []Lexeme{
   113  		{TokenTypeMultilineComment, 0, "/* foo */", ""},
   114  		{TokenTypeKeyword, 0, "relation", ""},
   115  		tWhitespace,
   116  		{TokenTypeIdentifier, 0, "parent", ""},
   117  		{TokenTypeColon, 0, ":", ""},
   118  		tWhitespace,
   119  		{TokenTypeIdentifier, 0, "namespace", ""},
   120  		tWhitespace,
   121  		{TokenTypePipe, 0, "|", ""},
   122  		tWhitespace,
   123  		{TokenTypeIdentifier, 0, "organization", ""},
   124  		{TokenTypeSemicolon, 0, ";", ""},
   125  		tEOF,
   126  	}},
   127  
   128  	{"relation", "/* foo */relation parent: namespace:*\n", []Lexeme{
   129  		{TokenTypeMultilineComment, 0, "/* foo */", ""},
   130  		{TokenTypeKeyword, 0, "relation", ""},
   131  		tWhitespace,
   132  		{TokenTypeIdentifier, 0, "parent", ""},
   133  		{TokenTypeColon, 0, ":", ""},
   134  		tWhitespace,
   135  		{TokenTypeIdentifier, 0, "namespace", ""},
   136  		{TokenTypeColon, 0, ":", ""},
   137  		{TokenTypeStar, 0, "*", ""},
   138  		{TokenTypeSyntheticSemicolon, 0, "\n", ""},
   139  		tEOF,
   140  	}},
   141  
   142  	{"relation with caveat", "/* foo */relation viewer: user with somecaveat\n", []Lexeme{
   143  		{TokenTypeMultilineComment, 0, "/* foo */", ""},
   144  		{TokenTypeKeyword, 0, "relation", ""},
   145  		tWhitespace,
   146  		{TokenTypeIdentifier, 0, "viewer", ""},
   147  		{TokenTypeColon, 0, ":", ""},
   148  		tWhitespace,
   149  		{TokenTypeIdentifier, 0, "user", ""},
   150  		tWhitespace,
   151  		{TokenTypeKeyword, 0, "with", ""},
   152  		tWhitespace,
   153  		{TokenTypeIdentifier, 0, "somecaveat", ""},
   154  		{TokenTypeSyntheticSemicolon, 0, "\n", ""},
   155  		tEOF,
   156  	}},
   157  
   158  	{"relation with wildcard caveat", "/* foo */relation viewer: user:* with somecaveat\n", []Lexeme{
   159  		{TokenTypeMultilineComment, 0, "/* foo */", ""},
   160  		{TokenTypeKeyword, 0, "relation", ""},
   161  		tWhitespace,
   162  		{TokenTypeIdentifier, 0, "viewer", ""},
   163  		{TokenTypeColon, 0, ":", ""},
   164  		tWhitespace,
   165  		{TokenTypeIdentifier, 0, "user", ""},
   166  		{TokenTypeColon, 0, ":", ""},
   167  		{TokenTypeStar, 0, "*", ""},
   168  		tWhitespace,
   169  		{TokenTypeKeyword, 0, "with", ""},
   170  		tWhitespace,
   171  		{TokenTypeIdentifier, 0, "somecaveat", ""},
   172  		{TokenTypeSyntheticSemicolon, 0, "\n", ""},
   173  		tEOF,
   174  	}},
   175  
   176  	{"relation with invalid caveat", "/* foo */relation viewer: user with with\n", []Lexeme{
   177  		{TokenTypeMultilineComment, 0, "/* foo */", ""},
   178  		{TokenTypeKeyword, 0, "relation", ""},
   179  		tWhitespace,
   180  		{TokenTypeIdentifier, 0, "viewer", ""},
   181  		{TokenTypeColon, 0, ":", ""},
   182  		tWhitespace,
   183  		{TokenTypeIdentifier, 0, "user", ""},
   184  		tWhitespace,
   185  		{TokenTypeKeyword, 0, "with", ""},
   186  		tWhitespace,
   187  		{TokenTypeKeyword, 0, "with", ""},
   188  		{TokenTypeSyntheticSemicolon, 0, "\n", ""},
   189  		tEOF,
   190  	}},
   191  
   192  	{"expression with parens", "(foo->bar)\n", []Lexeme{
   193  		{TokenTypeLeftParen, 0, "(", ""},
   194  		{TokenTypeIdentifier, 0, "foo", ""},
   195  		{TokenTypeRightArrow, 0, "->", ""},
   196  		{TokenTypeIdentifier, 0, "bar", ""},
   197  		{TokenTypeRightParen, 0, ")", ""},
   198  		{TokenTypeSyntheticSemicolon, 0, "\n", ""},
   199  		tEOF,
   200  	}},
   201  	{
   202  		"cel lexemes", "[a<=b]",
   203  		[]Lexeme{
   204  			{TokenTypeLeftBracket, 0, "[", ""},
   205  			{TokenTypeIdentifier, 0, "a", ""},
   206  			{TokenTypeLessThanOrEqual, 0, "<=", ""},
   207  			{TokenTypeIdentifier, 0, "b", ""},
   208  			{TokenTypeRightBracket, 0, "]", ""},
   209  			tEOF,
   210  		},
   211  	},
   212  	{
   213  		"more cel lexemes", "[a>=b.?]",
   214  		[]Lexeme{
   215  			{TokenTypeLeftBracket, 0, "[", ""},
   216  			{TokenTypeIdentifier, 0, "a", ""},
   217  			{TokenTypeGreaterThanOrEqual, 0, ">=", ""},
   218  			{TokenTypeIdentifier, 0, "b", ""},
   219  			{TokenTypePeriod, 0, ".", ""},
   220  			{TokenTypeQuestionMark, 0, "?", ""},
   221  			{TokenTypeRightBracket, 0, "]", ""},
   222  			tEOF,
   223  		},
   224  	},
   225  	{
   226  		"cel string literal", `"hi there"`,
   227  		[]Lexeme{
   228  			{TokenTypeString, 0, `"hi there"`, ""},
   229  			tEOF,
   230  		},
   231  	},
   232  	{
   233  		"cel string literal with terminators", `"""hi "there" """`,
   234  		[]Lexeme{
   235  			{TokenTypeString, 0, `"""hi "there" """`, ""},
   236  			tEOF,
   237  		},
   238  	},
   239  	{
   240  		"unterminated cel string literal", "\"hi\nthere\"",
   241  		[]Lexeme{
   242  			{
   243  				Kind:     TokenTypeError,
   244  				Position: 0,
   245  				Value:    "\n",
   246  				Error:    "Unterminated string",
   247  			},
   248  		},
   249  	},
   250  }
   251  
   252  func TestLexer(t *testing.T) {
   253  	for _, test := range lexerTests {
   254  		test := test
   255  		t.Run(test.name, func(t *testing.T) {
   256  			test := test // Close over test and not the pointer that is reused.
   257  			tokens := performLex(&test)
   258  			if !equal(tokens, test.tokens) {
   259  				t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, tokens, test.tokens)
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func performLex(t *lexerTest) (tokens []Lexeme) {
   266  	l := Lex(input.Source(t.name), t.input)
   267  	for {
   268  		token := l.nextToken()
   269  		tokens = append(tokens, token)
   270  		if token.Kind == TokenTypeEOF || token.Kind == TokenTypeError {
   271  			break
   272  		}
   273  	}
   274  	return
   275  }
   276  
   277  func equal(found, expected []Lexeme) bool {
   278  	if len(found) != len(expected) {
   279  		return false
   280  	}
   281  	for k := range found {
   282  		if found[k].Kind != expected[k].Kind {
   283  			return false
   284  		}
   285  		if found[k].Value != expected[k].Value {
   286  			return false
   287  		}
   288  	}
   289  	return true
   290  }