xref: /aosp_15_r20/build/blueprint/parser/parser_test.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package parser
16
17import (
18	"bytes"
19	"errors"
20	"reflect"
21	"strconv"
22	"strings"
23	"testing"
24	"text/scanner"
25)
26
27func mkpos(offset, line, column int) scanner.Position {
28	return scanner.Position{
29		Offset: offset,
30		Line:   line,
31		Column: column,
32	}
33}
34
35var validParseTestCases = []struct {
36	input    string
37	defs     []Definition
38	comments []*CommentGroup
39}{
40	{`
41		foo {}
42		`,
43		[]Definition{
44			&Module{
45				Type:    "foo",
46				TypePos: mkpos(3, 2, 3),
47				Map: Map{
48					LBracePos: mkpos(7, 2, 7),
49					RBracePos: mkpos(8, 2, 8),
50				},
51			},
52		},
53		nil,
54	},
55
56	{`
57		foo {
58			name: "abc",
59		}
60		`,
61		[]Definition{
62			&Module{
63				Type:    "foo",
64				TypePos: mkpos(3, 2, 3),
65				Map: Map{
66					LBracePos: mkpos(7, 2, 7),
67					RBracePos: mkpos(27, 4, 3),
68					Properties: []*Property{
69						{
70							Name:     "name",
71							NamePos:  mkpos(12, 3, 4),
72							ColonPos: mkpos(16, 3, 8),
73							Value: &String{
74								LiteralPos: mkpos(18, 3, 10),
75								Value:      "abc",
76							},
77						},
78					},
79				},
80			},
81		},
82		nil,
83	},
84
85	{`
86		foo {
87			isGood: true,
88		}
89		`,
90		[]Definition{
91			&Module{
92				Type:    "foo",
93				TypePos: mkpos(3, 2, 3),
94				Map: Map{
95					LBracePos: mkpos(7, 2, 7),
96					RBracePos: mkpos(28, 4, 3),
97					Properties: []*Property{
98						{
99							Name:     "isGood",
100							NamePos:  mkpos(12, 3, 4),
101							ColonPos: mkpos(18, 3, 10),
102							Value: &Bool{
103								LiteralPos: mkpos(20, 3, 12),
104								Value:      true,
105								Token:      "true",
106							},
107						},
108					},
109				},
110			},
111		},
112		nil,
113	},
114
115	{`
116		foo {
117			num: 4,
118		}
119		`,
120		[]Definition{
121			&Module{
122				Type:    "foo",
123				TypePos: mkpos(3, 2, 3),
124				Map: Map{
125					LBracePos: mkpos(7, 2, 7),
126					RBracePos: mkpos(22, 4, 3),
127					Properties: []*Property{
128						{
129							Name:     "num",
130							NamePos:  mkpos(12, 3, 4),
131							ColonPos: mkpos(15, 3, 7),
132							Value: &Int64{
133								LiteralPos: mkpos(17, 3, 9),
134								Value:      4,
135								Token:      "4",
136							},
137						},
138					},
139				},
140			},
141		},
142		nil,
143	},
144
145	{`
146		foo {
147			stuff: ["asdf", "jkl;", "qwert",
148				"uiop", ` + "`bnm,\n`" +
149		`]
150		}
151		`,
152		[]Definition{
153			&Module{
154				Type:    "foo",
155				TypePos: mkpos(3, 2, 3),
156				Map: Map{
157					LBracePos: mkpos(7, 2, 7),
158					RBracePos: mkpos(68, 6, 3),
159					Properties: []*Property{
160						{
161							Name:     "stuff",
162							NamePos:  mkpos(12, 3, 4),
163							ColonPos: mkpos(17, 3, 9),
164							Value: &List{
165								LBracePos: mkpos(19, 3, 11),
166								RBracePos: mkpos(64, 5, 2),
167								Values: []Expression{
168									&String{
169										LiteralPos: mkpos(20, 3, 12),
170										Value:      "asdf",
171									},
172									&String{
173										LiteralPos: mkpos(28, 3, 20),
174										Value:      "jkl;",
175									},
176									&String{
177										LiteralPos: mkpos(36, 3, 28),
178										Value:      "qwert",
179									},
180									&String{
181										LiteralPos: mkpos(49, 4, 5),
182										Value:      "uiop",
183									},
184									&String{
185										LiteralPos: mkpos(57, 4, 13),
186										Value:      "bnm,\n",
187									},
188								},
189							},
190						},
191					},
192				},
193			},
194		},
195		nil,
196	},
197
198	{
199		`
200		foo {
201			list_of_maps: [
202				{
203					var: true,
204					name: "a",
205				},
206				{
207					var: false,
208					name: "b",
209				},
210			],
211		}
212`,
213		[]Definition{
214			&Module{
215				Type:    "foo",
216				TypePos: mkpos(3, 2, 3),
217				Map: Map{
218					LBracePos: mkpos(7, 2, 7),
219					RBracePos: mkpos(127, 13, 3),
220					Properties: []*Property{
221						{
222							Name:     "list_of_maps",
223							NamePos:  mkpos(12, 3, 4),
224							ColonPos: mkpos(24, 3, 16),
225							Value: &List{
226								LBracePos: mkpos(26, 3, 18),
227								RBracePos: mkpos(122, 12, 4),
228								Values: []Expression{
229									&Map{
230										LBracePos: mkpos(32, 4, 5),
231										RBracePos: mkpos(70, 7, 5),
232										Properties: []*Property{
233											{
234												Name:     "var",
235												NamePos:  mkpos(39, 5, 6),
236												ColonPos: mkpos(42, 5, 9),
237												Value: &Bool{
238													LiteralPos: mkpos(44, 5, 11),
239													Value:      true,
240													Token:      "true",
241												},
242											},
243											{
244												Name:     "name",
245												NamePos:  mkpos(55, 6, 6),
246												ColonPos: mkpos(59, 6, 10),
247												Value: &String{
248													LiteralPos: mkpos(61, 6, 12),
249													Value:      "a",
250												},
251											},
252										},
253									},
254									&Map{
255										LBracePos: mkpos(77, 8, 5),
256										RBracePos: mkpos(116, 11, 5),
257										Properties: []*Property{
258											{
259												Name:     "var",
260												NamePos:  mkpos(84, 9, 6),
261												ColonPos: mkpos(87, 9, 9),
262												Value: &Bool{
263													LiteralPos: mkpos(89, 9, 11),
264													Value:      false,
265													Token:      "false",
266												},
267											},
268											{
269												Name:     "name",
270												NamePos:  mkpos(101, 10, 6),
271												ColonPos: mkpos(105, 10, 10),
272												Value: &String{
273													LiteralPos: mkpos(107, 10, 12),
274													Value:      "b",
275												},
276											},
277										},
278									},
279								},
280							},
281						},
282					},
283				},
284			},
285		},
286		nil,
287	},
288	{
289		`
290		foo {
291			list_of_lists: [
292				[ "a", "b" ],
293				[ "c", "d" ]
294			],
295		}
296`,
297		[]Definition{
298			&Module{
299				Type:    "foo",
300				TypePos: mkpos(3, 2, 3),
301				Map: Map{
302					LBracePos: mkpos(7, 2, 7),
303					RBracePos: mkpos(72, 7, 3),
304					Properties: []*Property{
305						{
306							Name:     "list_of_lists",
307							NamePos:  mkpos(12, 3, 4),
308							ColonPos: mkpos(25, 3, 17),
309							Value: &List{
310								LBracePos: mkpos(27, 3, 19),
311								RBracePos: mkpos(67, 6, 4),
312								Values: []Expression{
313									&List{
314										LBracePos: mkpos(33, 4, 5),
315										RBracePos: mkpos(44, 4, 16),
316										Values: []Expression{
317											&String{
318												LiteralPos: mkpos(35, 4, 7),
319												Value:      "a",
320											},
321											&String{
322												LiteralPos: mkpos(40, 4, 12),
323												Value:      "b",
324											},
325										},
326									},
327									&List{
328										LBracePos: mkpos(51, 5, 5),
329										RBracePos: mkpos(62, 5, 16),
330										Values: []Expression{
331											&String{
332												LiteralPos: mkpos(53, 5, 7),
333												Value:      "c",
334											},
335											&String{
336												LiteralPos: mkpos(58, 5, 12),
337												Value:      "d",
338											},
339										},
340									},
341								},
342							},
343						},
344					},
345				},
346			},
347		},
348		nil,
349	},
350	{`
351		foo {
352			stuff: {
353				isGood: true,
354				name: "bar",
355				num: 36,
356			}
357		}
358		`,
359		[]Definition{
360			&Module{
361				Type:    "foo",
362				TypePos: mkpos(3, 2, 3),
363				Map: Map{
364					LBracePos: mkpos(7, 2, 7),
365					RBracePos: mkpos(76, 8, 3),
366					Properties: []*Property{
367						{
368							Name:     "stuff",
369							NamePos:  mkpos(12, 3, 4),
370							ColonPos: mkpos(17, 3, 9),
371							Value: &Map{
372								LBracePos: mkpos(19, 3, 11),
373								RBracePos: mkpos(72, 7, 4),
374								Properties: []*Property{
375									{
376										Name:     "isGood",
377										NamePos:  mkpos(25, 4, 5),
378										ColonPos: mkpos(31, 4, 11),
379										Value: &Bool{
380											LiteralPos: mkpos(33, 4, 13),
381											Value:      true,
382											Token:      "true",
383										},
384									},
385									{
386										Name:     "name",
387										NamePos:  mkpos(43, 5, 5),
388										ColonPos: mkpos(47, 5, 9),
389										Value: &String{
390											LiteralPos: mkpos(49, 5, 11),
391											Value:      "bar",
392										},
393									},
394									{
395										Name:     "num",
396										NamePos:  mkpos(60, 6, 5),
397										ColonPos: mkpos(63, 6, 8),
398										Value: &Int64{
399											LiteralPos: mkpos(65, 6, 10),
400											Value:      36,
401											Token:      "36",
402										},
403									},
404								},
405							},
406						},
407					},
408				},
409			},
410		},
411		nil,
412	},
413
414	{`
415		// comment1
416		foo /* test */ {
417			// comment2
418			isGood: true,  // comment3
419		}
420		`,
421		[]Definition{
422			&Module{
423				Type:    "foo",
424				TypePos: mkpos(17, 3, 3),
425				Map: Map{
426					LBracePos: mkpos(32, 3, 18),
427					RBracePos: mkpos(81, 6, 3),
428					Properties: []*Property{
429						{
430							Name:     "isGood",
431							NamePos:  mkpos(52, 5, 4),
432							ColonPos: mkpos(58, 5, 10),
433							Value: &Bool{
434								LiteralPos: mkpos(60, 5, 12),
435								Value:      true,
436								Token:      "true",
437							},
438						},
439					},
440				},
441			},
442		},
443		[]*CommentGroup{
444			{
445				Comments: []*Comment{
446					&Comment{
447						Comment: []string{"// comment1"},
448						Slash:   mkpos(3, 2, 3),
449					},
450				},
451			},
452			{
453				Comments: []*Comment{
454					&Comment{
455						Comment: []string{"/* test */"},
456						Slash:   mkpos(21, 3, 7),
457					},
458				},
459			},
460			{
461				Comments: []*Comment{
462					&Comment{
463						Comment: []string{"// comment2"},
464						Slash:   mkpos(37, 4, 4),
465					},
466				},
467			},
468			{
469				Comments: []*Comment{
470					&Comment{
471						Comment: []string{"// comment3"},
472						Slash:   mkpos(67, 5, 19),
473					},
474				},
475			},
476		},
477	},
478
479	{`
480		foo {
481			name: "abc",
482			num: 4,
483		}
484
485		bar {
486			name: "def",
487			num: -5,
488		}
489		`,
490		[]Definition{
491			&Module{
492				Type:    "foo",
493				TypePos: mkpos(3, 2, 3),
494				Map: Map{
495					LBracePos: mkpos(7, 2, 7),
496					RBracePos: mkpos(38, 5, 3),
497					Properties: []*Property{
498						{
499							Name:     "name",
500							NamePos:  mkpos(12, 3, 4),
501							ColonPos: mkpos(16, 3, 8),
502							Value: &String{
503								LiteralPos: mkpos(18, 3, 10),
504								Value:      "abc",
505							},
506						},
507						{
508							Name:     "num",
509							NamePos:  mkpos(28, 4, 4),
510							ColonPos: mkpos(31, 4, 7),
511							Value: &Int64{
512								LiteralPos: mkpos(33, 4, 9),
513								Value:      4,
514								Token:      "4",
515							},
516						},
517					},
518				},
519			},
520			&Module{
521				Type:    "bar",
522				TypePos: mkpos(43, 7, 3),
523				Map: Map{
524					LBracePos: mkpos(47, 7, 7),
525					RBracePos: mkpos(79, 10, 3),
526					Properties: []*Property{
527						{
528							Name:     "name",
529							NamePos:  mkpos(52, 8, 4),
530							ColonPos: mkpos(56, 8, 8),
531							Value: &String{
532								LiteralPos: mkpos(58, 8, 10),
533								Value:      "def",
534							},
535						},
536						{
537							Name:     "num",
538							NamePos:  mkpos(68, 9, 4),
539							ColonPos: mkpos(71, 9, 7),
540							Value: &Int64{
541								LiteralPos: mkpos(73, 9, 9),
542								Value:      -5,
543								Token:      "-5",
544							},
545						},
546					},
547				},
548			},
549		},
550		nil,
551	},
552
553	{`
554		foo = "stuff"
555		bar = foo
556		baz = foo + bar
557		boo = baz
558		boo += foo
559		`,
560		[]Definition{
561			&Assignment{
562				Name:      "foo",
563				NamePos:   mkpos(3, 2, 3),
564				EqualsPos: mkpos(7, 2, 7),
565				Value: &String{
566					LiteralPos: mkpos(9, 2, 9),
567					Value:      "stuff",
568				},
569				Assigner: "=",
570			},
571			&Assignment{
572				Name:      "bar",
573				NamePos:   mkpos(19, 3, 3),
574				EqualsPos: mkpos(23, 3, 7),
575				Value: &Variable{
576					Name:    "foo",
577					NamePos: mkpos(25, 3, 9),
578				},
579				Assigner: "=",
580			},
581			&Assignment{
582				Name:      "baz",
583				NamePos:   mkpos(31, 4, 3),
584				EqualsPos: mkpos(35, 4, 7),
585				Value: &Operator{
586					OperatorPos: mkpos(41, 4, 13),
587					Operator:    '+',
588					Args: [2]Expression{
589						&Variable{
590							Name:    "foo",
591							NamePos: mkpos(37, 4, 9),
592						},
593						&Variable{
594							Name:    "bar",
595							NamePos: mkpos(43, 4, 15),
596						},
597					},
598				},
599				Assigner: "=",
600			},
601			&Assignment{
602				Name:      "boo",
603				NamePos:   mkpos(49, 5, 3),
604				EqualsPos: mkpos(53, 5, 7),
605				Value: &Variable{
606					Name:    "baz",
607					NamePos: mkpos(55, 5, 9),
608				},
609				Assigner: "=",
610			},
611			&Assignment{
612				Name:      "boo",
613				NamePos:   mkpos(61, 6, 3),
614				EqualsPos: mkpos(66, 6, 8),
615				Value: &Variable{
616					Name:    "foo",
617					NamePos: mkpos(68, 6, 10),
618				},
619				Assigner: "+=",
620			},
621		},
622		nil,
623	},
624
625	{`
626		baz = -4 + -5 + 6
627		`,
628		[]Definition{
629			&Assignment{
630				Name:      "baz",
631				NamePos:   mkpos(3, 2, 3),
632				EqualsPos: mkpos(7, 2, 7),
633				Value: &Operator{
634					OperatorPos: mkpos(12, 2, 12),
635					Operator:    '+',
636					Args: [2]Expression{
637						&Int64{
638							LiteralPos: mkpos(9, 2, 9),
639							Value:      -4,
640							Token:      "-4",
641						},
642						&Operator{
643							OperatorPos: mkpos(17, 2, 17),
644							Operator:    '+',
645							Args: [2]Expression{
646								&Int64{
647									LiteralPos: mkpos(14, 2, 14),
648									Value:      -5,
649									Token:      "-5",
650								},
651								&Int64{
652									LiteralPos: mkpos(19, 2, 19),
653									Value:      6,
654									Token:      "6",
655								},
656							},
657						},
658					},
659				},
660				Assigner: "=",
661			},
662		},
663		nil,
664	},
665
666	{`
667		foo = 1000000
668		bar = foo
669		baz = foo + bar
670		boo = baz
671		boo += foo
672		`,
673		[]Definition{
674			&Assignment{
675				Name:      "foo",
676				NamePos:   mkpos(3, 2, 3),
677				EqualsPos: mkpos(7, 2, 7),
678				Value: &Int64{
679					LiteralPos: mkpos(9, 2, 9),
680					Value:      1000000,
681					Token:      "1000000",
682				},
683				Assigner: "=",
684			},
685			&Assignment{
686				Name:      "bar",
687				NamePos:   mkpos(19, 3, 3),
688				EqualsPos: mkpos(23, 3, 7),
689				Value: &Variable{
690					Name:    "foo",
691					NamePos: mkpos(25, 3, 9),
692				},
693				Assigner: "=",
694			},
695			&Assignment{
696				Name:      "baz",
697				NamePos:   mkpos(31, 4, 3),
698				EqualsPos: mkpos(35, 4, 7),
699				Value: &Operator{
700					OperatorPos: mkpos(41, 4, 13),
701					Operator:    '+',
702					Args: [2]Expression{
703						&Variable{
704							Name:    "foo",
705							NamePos: mkpos(37, 4, 9),
706						},
707						&Variable{
708							Name:    "bar",
709							NamePos: mkpos(43, 4, 15),
710						},
711					},
712				},
713				Assigner: "=",
714			},
715			&Assignment{
716				Name:      "boo",
717				NamePos:   mkpos(49, 5, 3),
718				EqualsPos: mkpos(53, 5, 7),
719				Value: &Variable{
720					Name:    "baz",
721					NamePos: mkpos(55, 5, 9),
722				},
723				Assigner: "=",
724			},
725			&Assignment{
726				Name:      "boo",
727				NamePos:   mkpos(61, 6, 3),
728				EqualsPos: mkpos(66, 6, 8),
729				Value: &Variable{
730					Name:    "foo",
731					NamePos: mkpos(68, 6, 10),
732				},
733				Assigner: "+=",
734			},
735		},
736		nil,
737	},
738
739	{`
740		// comment1
741		// comment2
742
743		/* comment3
744		   comment4 */
745		// comment5
746
747		/* comment6 */ /* comment7 */ // comment8
748		`,
749		nil,
750		[]*CommentGroup{
751			{
752				Comments: []*Comment{
753					&Comment{
754						Comment: []string{"// comment1"},
755						Slash:   mkpos(3, 2, 3),
756					},
757					&Comment{
758						Comment: []string{"// comment2"},
759						Slash:   mkpos(17, 3, 3),
760					},
761				},
762			},
763			{
764				Comments: []*Comment{
765					&Comment{
766						Comment: []string{"/* comment3", "		   comment4 */"},
767						Slash:   mkpos(32, 5, 3),
768					},
769					&Comment{
770						Comment: []string{"// comment5"},
771						Slash:   mkpos(63, 7, 3),
772					},
773				},
774			},
775			{
776				Comments: []*Comment{
777					&Comment{
778						Comment: []string{"/* comment6 */"},
779						Slash:   mkpos(78, 9, 3),
780					},
781					&Comment{
782						Comment: []string{"/* comment7 */"},
783						Slash:   mkpos(93, 9, 18),
784					},
785					&Comment{
786						Comment: []string{"// comment8"},
787						Slash:   mkpos(108, 9, 33),
788					},
789				},
790			},
791		},
792	},
793}
794
795func TestParseValidInput(t *testing.T) {
796	for i, testCase := range validParseTestCases {
797		t.Run(strconv.Itoa(i), func(t *testing.T) {
798			r := bytes.NewBufferString(testCase.input)
799			file, errs := Parse("", r)
800			if len(errs) != 0 {
801				t.Errorf("test case: %s", testCase.input)
802				t.Errorf("unexpected errors:")
803				for _, err := range errs {
804					t.Errorf("  %s", err)
805				}
806				t.FailNow()
807			}
808
809			if len(file.Defs) == len(testCase.defs) {
810				for i := range file.Defs {
811					if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) {
812						t.Errorf("test case: %s", testCase.input)
813						t.Errorf("incorrect definition %d:", i)
814						t.Errorf("  expected: %s", testCase.defs[i])
815						t.Errorf("       got: %s", file.Defs[i])
816					}
817				}
818			} else {
819				t.Errorf("test case: %s", testCase.input)
820				t.Errorf("length mismatch, expected %d definitions, got %d",
821					len(testCase.defs), len(file.Defs))
822			}
823
824			if len(file.Comments) == len(testCase.comments) {
825				for i := range file.Comments {
826					if !reflect.DeepEqual(file.Comments[i], testCase.comments[i]) {
827						t.Errorf("test case: %s", testCase.input)
828						t.Errorf("incorrect comment %d:", i)
829						t.Errorf("  expected: %s", testCase.comments[i])
830						t.Errorf("       got: %s", file.Comments[i])
831					}
832				}
833			} else {
834				t.Errorf("test case: %s", testCase.input)
835				t.Errorf("length mismatch, expected %d comments, got %d",
836					len(testCase.comments), len(file.Comments))
837			}
838		})
839	}
840}
841
842func TestParseSelectWithoutTrailingComma(t *testing.T) {
843	r := bytes.NewBufferString(`
844	m {
845		foo: select(arch(), {
846			"arm64": true,
847			default: false
848		}),
849	}
850	`)
851	file, errs := ParseAndEval("", r, NewScope(nil))
852	if len(errs) != 0 {
853		t.Fatalf("%s", errors.Join(errs...).Error())
854	}
855	_, ok := file.Defs[0].(*Module).Properties[0].Value.(*Select)
856	if !ok {
857		t.Fatalf("did not parse to select")
858	}
859}
860
861func TestParserError(t *testing.T) {
862	testcases := []struct {
863		name  string
864		input string
865		err   string
866	}{
867		{
868			name:  "invalid first token",
869			input: "\x00",
870			err:   "invalid character NUL",
871		},
872		{
873			name: "select with duplicate condition",
874			input: `
875			m {
876				foo: select((arch(), arch()), {
877					(default, default): true,
878				}),
879			}
880			`,
881			err: "Duplicate select condition found: arch()",
882		},
883		{
884			name: "select with duplicate binding",
885			input: `
886			m {
887				foo: select((arch(), os()), {
888					(any @ bar, any @ bar): true,
889				}),
890			}
891			`,
892			err: "Found duplicate select pattern binding: bar",
893		},
894		// TODO: test more parser errors
895	}
896
897	for _, tt := range testcases {
898		t.Run(tt.name, func(t *testing.T) {
899			r := bytes.NewBufferString(tt.input)
900			_, errs := ParseAndEval("", r, NewScope(nil))
901			if len(errs) == 0 {
902				t.Fatalf("missing expected error")
903			}
904			if g, w := errs[0], tt.err; !strings.Contains(g.Error(), w) {
905				t.Errorf("expected error %q, got %q", w, g)
906			}
907			for _, err := range errs[1:] {
908				t.Errorf("got unexpected extra error %q", err)
909			}
910		})
911	}
912}
913
914func TestParserEndPos(t *testing.T) {
915	in := `
916		module {
917			string: "string",
918			stringexp: "string1" + "string2",
919			int: -1,
920			intexp: -1 + 2,
921			list: ["a", "b"],
922			listexp: ["c"] + ["d"],
923			multilinelist: [
924				"e",
925				"f",
926			],
927			map: {
928				prop: "abc",
929			},
930		}
931	`
932
933	// Strip each line to make it easier to compute the previous "," from each property
934	lines := strings.Split(in, "\n")
935	for i := range lines {
936		lines[i] = strings.TrimSpace(lines[i])
937	}
938	in = strings.Join(lines, "\n")
939
940	r := bytes.NewBufferString(in)
941
942	file, errs := Parse("", r)
943	if len(errs) != 0 {
944		t.Errorf("unexpected errors:")
945		for _, err := range errs {
946			t.Errorf("  %s", err)
947		}
948		t.FailNow()
949	}
950
951	mod := file.Defs[0].(*Module)
952	modEnd := mkpos(len(in)-1, len(lines)-1, 2)
953	if mod.End() != modEnd {
954		t.Errorf("expected mod.End() %s, got %s", modEnd, mod.End())
955	}
956
957	nextPos := make([]scanner.Position, len(mod.Properties))
958	for i := 0; i < len(mod.Properties)-1; i++ {
959		nextPos[i] = mod.Properties[i+1].Pos()
960	}
961	nextPos[len(mod.Properties)-1] = mod.RBracePos
962
963	for i, cur := range mod.Properties {
964		endOffset := nextPos[i].Offset - len(",\n")
965		endLine := nextPos[i].Line - 1
966		endColumn := len(lines[endLine-1]) // scanner.Position.Line is starts at 1
967		endPos := mkpos(endOffset, endLine, endColumn)
968		if cur.End() != endPos {
969			t.Errorf("expected property %s End() %s@%d, got %s@%d", cur.Name, endPos, endPos.Offset, cur.End(), cur.End().Offset)
970		}
971	}
972}
973
974func TestParserNotEvaluated(t *testing.T) {
975	// When parsing without evaluation, create variables correctly
976	input := "FOO=abc\n"
977	file, errs := Parse("", bytes.NewBufferString(input))
978	if errs != nil {
979		t.Errorf("unexpected errors:")
980		for _, err := range errs {
981			t.Errorf("  %s", err)
982		}
983		t.FailNow()
984	}
985	assignment, ok := file.Defs[0].(*Assignment)
986	if !ok || assignment.Name != "FOO" {
987		t.Fatalf("Expected to find FOO after parsing %s", input)
988	}
989	if assignment.Value.String() != "abc" {
990		t.Errorf("Attempt to print FOO returned %s", assignment.Value.String())
991	}
992}
993