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