//go:build go1.7 // +build go1.7 package ini import ( "bytes" "fmt" "io" "reflect" "testing" ) func TestParser(t *testing.T) { xID, _, _ := newLitToken([]rune("x = 1234")) s3ID, _, _ := newLitToken([]rune("s3 = 1234")) fooSlashes, _, _ := newLitToken([]rune("//foo")) regionID, _, _ := newLitToken([]rune("region")) regionLit, _, _ := newLitToken([]rune(`"us-west-2"`)) regionNoQuotesLit, _, _ := newLitToken([]rune("us-west-2")) credentialID, _, _ := newLitToken([]rune("credential_source")) ec2MetadataLit, _, _ := newLitToken([]rune("Ec2InstanceMetadata")) outputID, _, _ := newLitToken([]rune("output")) outputLit, _, _ := newLitToken([]rune("json")) sepInValueID, _, _ := newLitToken([]rune("sepInValue")) sepInValueLit := newToken(TokenOp, []rune("=:[foo]]bar["), StringType) equalOp, _, _ := newOpToken([]rune("= 1234")) equalColonOp, _, _ := newOpToken([]rune(": 1234")) numLit, _, _ := newLitToken([]rune("1234")) defaultID, _, _ := newLitToken([]rune("default")) assumeID, _, _ := newLitToken([]rune("assumerole")) defaultProfileStmt := newSectionStatement(defaultID) assumeProfileStmt := newSectionStatement(assumeID) fooSlashesExpr := newExpression(fooSlashes) xEQ1234 := newEqualExpr(newExpression(xID), equalOp) xEQ1234.AppendChild(newExpression(numLit)) xEQColon1234 := newEqualExpr(newExpression(xID), equalColonOp) xEQColon1234.AppendChild(newExpression(numLit)) regionEQRegion := newEqualExpr(newExpression(regionID), equalOp) regionEQRegion.AppendChild(newExpression(regionLit)) noQuotesRegionEQRegion := newEqualExpr(newExpression(regionID), equalOp) noQuotesRegionEQRegion.AppendChild(newExpression(regionNoQuotesLit)) credEQExpr := newEqualExpr(newExpression(credentialID), equalOp) credEQExpr.AppendChild(newExpression(ec2MetadataLit)) outputEQExpr := newEqualExpr(newExpression(outputID), equalOp) outputEQExpr.AppendChild(newExpression(outputLit)) sepInValueExpr := newEqualExpr(newExpression(sepInValueID), equalOp) sepInValueExpr.AppendChild(newExpression(sepInValueLit)) cases := []struct { name string r io.Reader expectedStack []AST expectedError bool }{ { name: "semicolon comment", r: bytes.NewBuffer([]byte(`;foo`)), expectedStack: []AST{ newCommentStatement(newToken(TokenComment, []rune(";foo"), NoneType)), }, }, { name: "0==0", r: bytes.NewBuffer([]byte(`0==0`)), expectedStack: []AST{ func() AST { equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalOp) equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune("=0"), StringType))) return newExprStatement(equalExpr) }(), }, }, { name: "0=:0", r: bytes.NewBuffer([]byte(`0=:0`)), expectedStack: []AST{ func() AST { equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalOp) equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune(":0"), StringType))) return newExprStatement(equalExpr) }(), }, }, { name: "0:=0", r: bytes.NewBuffer([]byte(`0:=0`)), expectedStack: []AST{ func() AST { equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalColonOp) equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune("=0"), StringType))) return newExprStatement(equalExpr) }(), }, }, { name: "0::0", r: bytes.NewBuffer([]byte(`0::0`)), expectedStack: []AST{ func() AST { equalExpr := newEqualExpr(newExpression(newToken(TokenLit, []rune("0"), StringType)), equalColonOp) equalExpr.AppendChild(newExpression(newToken(TokenOp, []rune(":0"), StringType))) return newExprStatement(equalExpr) }(), }, }, { name: "section with variable", r: bytes.NewBuffer([]byte(`[ default ]x`)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newExpression(xID), }, }, { name: "# comment", r: bytes.NewBuffer([]byte(`# foo`)), expectedStack: []AST{ newCommentStatement(newToken(TokenComment, []rune("# foo"), NoneType)), }, }, { name: "// not a comment", r: bytes.NewBuffer([]byte(`//foo`)), expectedStack: []AST{ fooSlashesExpr, }, }, { name: "multiple comments", r: bytes.NewBuffer([]byte(`;foo # baz `)), expectedStack: []AST{ newCommentStatement(newToken(TokenComment, []rune(";foo"), NoneType)), newCommentStatement(newToken(TokenComment, []rune("# baz"), NoneType)), }, }, { name: "comment followed by skip state", r: bytes.NewBuffer([]byte(`;foo //foo # baz `)), expectedStack: []AST{ newCommentStatement(newToken(TokenComment, []rune(";foo"), NoneType)), }, }, { name: "assignment", r: bytes.NewBuffer([]byte(`x = 1234`)), expectedStack: []AST{ newExprStatement(xEQ1234), }, }, { name: "assignment spaceless", r: bytes.NewBuffer([]byte(`x=1234`)), expectedStack: []AST{ newExprStatement(xEQ1234), }, }, { name: "assignment :", r: bytes.NewBuffer([]byte(`x : 1234`)), expectedStack: []AST{ newExprStatement(xEQColon1234), }, }, { name: "assignment : no spaces", r: bytes.NewBuffer([]byte(`x:1234`)), expectedStack: []AST{ newExprStatement(xEQColon1234), }, }, { name: "section expression", r: bytes.NewBuffer([]byte(`[ default ]`)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), }, }, { name: "section expression no spaces", r: bytes.NewBuffer([]byte(`[default]`)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), }, }, { name: "section statement", r: bytes.NewBuffer([]byte(`[default] region="us-west-2"`)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newExprStatement(regionEQRegion), }, }, { name: "complex section statement", r: bytes.NewBuffer([]byte(`[default] region = us-west-2 credential_source = Ec2InstanceMetadata output = json [assumerole] output = json region = us-west-2 `)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newExprStatement(noQuotesRegionEQRegion), newExprStatement(credEQExpr), newExprStatement(outputEQExpr), newCompletedSectionStatement( assumeProfileStmt, ), newExprStatement(outputEQExpr), newExprStatement(noQuotesRegionEQRegion), }, }, { name: "complex section statement with nested params", r: bytes.NewBuffer([]byte(`[default] s3 = foo=bar bar=baz region = us-west-2 credential_source = Ec2InstanceMetadata output = json [assumerole] output = json region = us-west-2 `)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newSkipStatement(newEqualExpr(newExpression(s3ID), equalOp)), newExprStatement(noQuotesRegionEQRegion), newExprStatement(credEQExpr), newExprStatement(outputEQExpr), newCompletedSectionStatement( assumeProfileStmt, ), newExprStatement(outputEQExpr), newExprStatement(noQuotesRegionEQRegion), }, }, { name: "complex section statement", r: bytes.NewBuffer([]byte(`[default] region = us-west-2 credential_source = Ec2InstanceMetadata s3 = foo=bar bar=baz output = json [assumerole] output = json region = us-west-2 `)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newExprStatement(noQuotesRegionEQRegion), newExprStatement(credEQExpr), newSkipStatement(newEqualExpr(newExpression(s3ID), equalOp)), newExprStatement(outputEQExpr), newCompletedSectionStatement( assumeProfileStmt, ), newExprStatement(outputEQExpr), newExprStatement(noQuotesRegionEQRegion), }, }, { name: "missing section statement", r: bytes.NewBuffer([]byte( `[default] s3 = [assumerole] output = json `)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newSkipStatement(newEqualExpr(newExpression(s3ID), equalOp)), newCompletedSectionStatement( assumeProfileStmt, ), newExprStatement(outputEQExpr), }, }, { name: "missing right hand expression in the last statement in the file", r: bytes.NewBuffer([]byte( `[default] region = us-west-2 s3 =`)), expectedStack: []AST{ newCompletedSectionStatement( defaultProfileStmt, ), newExprStatement(noQuotesRegionEQRegion), }, }, { name: "token seperators [ and ] in values", r: bytes.NewBuffer([]byte( `[default] sepInValue = =:[foo]]bar[ output = json [assumerole] sepInValue==:[foo]]bar[ output = json `)), expectedStack: []AST{ newCompletedSectionStatement(defaultProfileStmt), newExprStatement(sepInValueExpr), newExprStatement(outputEQExpr), newCompletedSectionStatement(assumeProfileStmt), newExprStatement(sepInValueExpr), newExprStatement(outputEQExpr), }, }, } for i, c := range cases { t.Run(c.name, func(t *testing.T) { stack, err := ParseAST(c.r) if e, a := c.expectedError, err != nil; e != a { t.Errorf("%d: expected %t, but received %t with error %v", i, e, a, err) } if e, a := len(c.expectedStack), len(stack); e != a { t.Errorf("expected same length %d, but received %d", e, a) } if e, a := c.expectedStack, stack; !reflect.DeepEqual(e, a) { buf := bytes.Buffer{} buf.WriteString("expected:\n") for j := 0; j < len(e); j++ { buf.WriteString(fmt.Sprintf("\t%d: %v\n", j, e[j])) } buf.WriteString("\nreceived:\n") for j := 0; j < len(a); j++ { buf.WriteString(fmt.Sprintf("\t%d: %v\n", j, a[j])) } t.Errorf("%s", buf.String()) } }) } }