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 }