1// +build go1.7 2 3package ini 4 5import ( 6 "bytes" 7 "fmt" 8 "io" 9 "reflect" 10 "testing" 11) 12 13func TestParser(t *testing.T) { 14 xID, _, _ := newLitToken([]rune("x = 1234")) 15 s3ID, _, _ := newLitToken([]rune("s3 = 1234")) 16 fooSlashes, _, _ := newLitToken([]rune("//foo")) 17 18 regionID, _, _ := newLitToken([]rune("region")) 19 regionLit, _, _ := newLitToken([]rune(`"us-west-2"`)) 20 regionNoQuotesLit, _, _ := newLitToken([]rune("us-west-2")) 21 22 credentialID, _, _ := newLitToken([]rune("credential_source")) 23 ec2MetadataLit, _, _ := newLitToken([]rune("Ec2InstanceMetadata")) 24 25 outputID, _, _ := newLitToken([]rune("output")) 26 outputLit, _, _ := newLitToken([]rune("json")) 27 28 sepInValueID, _, _ := newLitToken([]rune("sepInValue")) 29 sepInValueLit := newToken(TokenOp, []rune("=:[foo]]bar["), StringType) 30 31 equalOp, _, _ := newOpToken([]rune("= 1234")) 32 equalColonOp, _, _ := newOpToken([]rune(": 1234")) 33 numLit, _, _ := newLitToken([]rune("1234")) 34 defaultID, _, _ := newLitToken([]rune("default")) 35 assumeID, _, _ := newLitToken([]rune("assumerole")) 36 37 defaultProfileStmt := newSectionStatement(defaultID) 38 assumeProfileStmt := newSectionStatement(assumeID) 39 40 fooSlashesExpr := newExpression(fooSlashes) 41 42 xEQ1234 := newEqualExpr(newExpression(xID), equalOp) 43 xEQ1234.AppendChild(newExpression(numLit)) 44 xEQColon1234 := newEqualExpr(newExpression(xID), equalColonOp) 45 xEQColon1234.AppendChild(newExpression(numLit)) 46 47 regionEQRegion := newEqualExpr(newExpression(regionID), equalOp) 48 regionEQRegion.AppendChild(newExpression(regionLit)) 49 50 noQuotesRegionEQRegion := newEqualExpr(newExpression(regionID), equalOp) 51 noQuotesRegionEQRegion.AppendChild(newExpression(regionNoQuotesLit)) 52 53 credEQExpr := newEqualExpr(newExpression(credentialID), equalOp) 54 credEQExpr.AppendChild(newExpression(ec2MetadataLit)) 55 56 outputEQExpr := newEqualExpr(newExpression(outputID), equalOp) 57 outputEQExpr.AppendChild(newExpression(outputLit)) 58 59 sepInValueExpr := newEqualExpr(newExpression(sepInValueID), equalOp) 60 sepInValueExpr.AppendChild(newExpression(sepInValueLit)) 61 62 cases := []struct { 63 name string 64 r io.Reader 65 expectedStack []AST 66 expectedError bool 67 }{ 68 { 69 name: "semicolon comment", 70 r: bytes.NewBuffer([]byte(`;foo`)), 71 expectedStack: []AST{ 72 newCommentStatement(newToken(TokenComment, []rune(";foo"), NoneType)), 73 }, 74 }, 75 { 76 name: "0==0", 77 r: bytes.NewBuffer([]byte(`0==0`)), 78 expectedStack: []AST{ 79 func() AST { 80 equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalOp) 81 equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune("=0"), StringType))) 82 return newExprStatement(equalExpr) 83 }(), 84 }, 85 }, 86 { 87 name: "0=:0", 88 r: bytes.NewBuffer([]byte(`0=:0`)), 89 expectedStack: []AST{ 90 func() AST { 91 equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalOp) 92 equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune(":0"), StringType))) 93 return newExprStatement(equalExpr) 94 }(), 95 }, 96 }, 97 { 98 name: "0:=0", 99 r: bytes.NewBuffer([]byte(`0:=0`)), 100 expectedStack: []AST{ 101 func() AST { 102 equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalColonOp) 103 equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune("=0"), StringType))) 104 return newExprStatement(equalExpr) 105 }(), 106 }, 107 }, 108 { 109 name: "0::0", 110 r: bytes.NewBuffer([]byte(`0::0`)), 111 expectedStack: []AST{ 112 func() AST { 113 equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalColonOp) 114 equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune(":0"), StringType))) 115 return newExprStatement(equalExpr) 116 }(), 117 }, 118 }, 119 { 120 name: "section with variable", 121 r: bytes.NewBuffer([]byte(`[ default ]x`)), 122 expectedStack: []AST{ 123 newCompletedSectionStatement( 124 defaultProfileStmt, 125 ), 126 newExpression(xID), 127 }, 128 }, 129 { 130 name: "# comment", 131 r: bytes.NewBuffer([]byte(`# foo`)), 132 expectedStack: []AST{ 133 newCommentStatement(newToken(TokenComment, []rune("# foo"), NoneType)), 134 }, 135 }, 136 { 137 name: "// not a comment", 138 r: bytes.NewBuffer([]byte(`//foo`)), 139 expectedStack: []AST{ 140 fooSlashesExpr, 141 }, 142 }, 143 { 144 name: "multiple comments", 145 r: bytes.NewBuffer([]byte(`;foo 146 # baz 147 `)), 148 expectedStack: []AST{ 149 newCommentStatement(newToken(TokenComment, []rune(";foo"), NoneType)), 150 newCommentStatement(newToken(TokenComment, []rune("# baz"), NoneType)), 151 }, 152 }, 153 { 154 name: "comment followed by skip state", 155 r: bytes.NewBuffer([]byte(`;foo 156 //foo 157 # baz 158 `)), 159 expectedStack: []AST{ 160 newCommentStatement(newToken(TokenComment, []rune(";foo"), NoneType)), 161 }, 162 }, 163 { 164 name: "assignment", 165 r: bytes.NewBuffer([]byte(`x = 1234`)), 166 expectedStack: []AST{ 167 newExprStatement(xEQ1234), 168 }, 169 }, 170 { 171 name: "assignment spaceless", 172 r: bytes.NewBuffer([]byte(`x=1234`)), 173 expectedStack: []AST{ 174 newExprStatement(xEQ1234), 175 }, 176 }, 177 { 178 name: "assignment :", 179 r: bytes.NewBuffer([]byte(`x : 1234`)), 180 expectedStack: []AST{ 181 newExprStatement(xEQColon1234), 182 }, 183 }, 184 { 185 name: "assignment : no spaces", 186 r: bytes.NewBuffer([]byte(`x:1234`)), 187 expectedStack: []AST{ 188 newExprStatement(xEQColon1234), 189 }, 190 }, 191 { 192 name: "section expression", 193 r: bytes.NewBuffer([]byte(`[ default ]`)), 194 expectedStack: []AST{ 195 newCompletedSectionStatement( 196 defaultProfileStmt, 197 ), 198 }, 199 }, 200 { 201 name: "section expression no spaces", 202 r: bytes.NewBuffer([]byte(`[default]`)), 203 expectedStack: []AST{ 204 newCompletedSectionStatement( 205 defaultProfileStmt, 206 ), 207 }, 208 }, 209 { 210 name: "section statement", 211 r: bytes.NewBuffer([]byte(`[default] 212 region="us-west-2"`)), 213 expectedStack: []AST{ 214 newCompletedSectionStatement( 215 defaultProfileStmt, 216 ), 217 newExprStatement(regionEQRegion), 218 }, 219 }, 220 { 221 name: "complex section statement", 222 r: bytes.NewBuffer([]byte(`[default] 223 region = us-west-2 224 credential_source = Ec2InstanceMetadata 225 output = json 226 227 [assumerole] 228 output = json 229 region = us-west-2 230 `)), 231 expectedStack: []AST{ 232 newCompletedSectionStatement( 233 defaultProfileStmt, 234 ), 235 newExprStatement(noQuotesRegionEQRegion), 236 newExprStatement(credEQExpr), 237 newExprStatement(outputEQExpr), 238 newCompletedSectionStatement( 239 assumeProfileStmt, 240 ), 241 newExprStatement(outputEQExpr), 242 newExprStatement(noQuotesRegionEQRegion), 243 }, 244 }, 245 { 246 name: "complex section statement with nested params", 247 r: bytes.NewBuffer([]byte(`[default] 248s3 = 249 foo=bar 250 bar=baz 251region = us-west-2 252credential_source = Ec2InstanceMetadata 253output = json 254 255[assumerole] 256output = json 257region = us-west-2 258 `)), 259 expectedStack: []AST{ 260 newCompletedSectionStatement( 261 defaultProfileStmt, 262 ), 263 newSkipStatement(newEqualExpr(newExpression(s3ID), equalOp)), 264 newExprStatement(noQuotesRegionEQRegion), 265 newExprStatement(credEQExpr), 266 newExprStatement(outputEQExpr), 267 newCompletedSectionStatement( 268 assumeProfileStmt, 269 ), 270 newExprStatement(outputEQExpr), 271 newExprStatement(noQuotesRegionEQRegion), 272 }, 273 }, 274 { 275 name: "complex section statement", 276 r: bytes.NewBuffer([]byte(`[default] 277region = us-west-2 278credential_source = Ec2InstanceMetadata 279s3 = 280 foo=bar 281 bar=baz 282output = json 283 284[assumerole] 285output = json 286region = us-west-2 287 `)), 288 expectedStack: []AST{ 289 newCompletedSectionStatement( 290 defaultProfileStmt, 291 ), 292 newExprStatement(noQuotesRegionEQRegion), 293 newExprStatement(credEQExpr), 294 newSkipStatement(newEqualExpr(newExpression(s3ID), equalOp)), 295 newExprStatement(outputEQExpr), 296 newCompletedSectionStatement( 297 assumeProfileStmt, 298 ), 299 newExprStatement(outputEQExpr), 300 newExprStatement(noQuotesRegionEQRegion), 301 }, 302 }, 303 { 304 name: "missing section statement", 305 r: bytes.NewBuffer([]byte( 306 `[default] 307s3 = 308[assumerole] 309output = json 310 `)), 311 expectedStack: []AST{ 312 newCompletedSectionStatement( 313 defaultProfileStmt, 314 ), 315 newSkipStatement(newEqualExpr(newExpression(s3ID), equalOp)), 316 newCompletedSectionStatement( 317 assumeProfileStmt, 318 ), 319 newExprStatement(outputEQExpr), 320 }, 321 }, 322 { 323 name: "missing right hand expression in the last statement in the file", 324 r: bytes.NewBuffer([]byte( 325 `[default] 326region = us-west-2 327s3 =`)), 328 expectedStack: []AST{ 329 newCompletedSectionStatement( 330 defaultProfileStmt, 331 ), 332 newExprStatement(noQuotesRegionEQRegion), 333 }, 334 }, 335 { 336 name: "token seperators [ and ] in values", 337 r: bytes.NewBuffer([]byte( 338 `[default] 339sepInValue = =:[foo]]bar[ 340output = json 341[assumerole] 342sepInValue==:[foo]]bar[ 343output = json 344`)), 345 expectedStack: []AST{ 346 newCompletedSectionStatement(defaultProfileStmt), 347 newExprStatement(sepInValueExpr), 348 newExprStatement(outputEQExpr), 349 newCompletedSectionStatement(assumeProfileStmt), 350 newExprStatement(sepInValueExpr), 351 newExprStatement(outputEQExpr), 352 }, 353 }, 354 } 355 356 for i, c := range cases { 357 t.Run(c.name, func(t *testing.T) { 358 stack, err := ParseAST(c.r) 359 360 if e, a := c.expectedError, err != nil; e != a { 361 t.Errorf("%d: expected %t, but received %t with error %v", i, e, a, err) 362 } 363 364 if e, a := len(c.expectedStack), len(stack); e != a { 365 t.Errorf("expected same length %d, but received %d", e, a) 366 } 367 368 if e, a := c.expectedStack, stack; !reflect.DeepEqual(e, a) { 369 buf := bytes.Buffer{} 370 buf.WriteString("expected:\n") 371 for j := 0; j < len(e); j++ { 372 buf.WriteString(fmt.Sprintf("\t%d: %v\n", j, e[j])) 373 } 374 375 buf.WriteString("\nreceived:\n") 376 for j := 0; j < len(a); j++ { 377 buf.WriteString(fmt.Sprintf("\t%d: %v\n", j, a[j])) 378 } 379 380 t.Errorf("%s", buf.String()) 381 } 382 }) 383 } 384} 385