xref: /aosp_15_r20/build/blueprint/bpmodify/bpmodify_test.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1// Copyright 2020 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 bpmodify
16
17import (
18	"strings"
19	"testing"
20)
21
22func must(err error) {
23	if err != nil {
24		panic(err)
25	}
26}
27
28func must2[T any](v T, err error) T {
29	if err != nil {
30		panic(err)
31	}
32	return v
33}
34
35func simplifyModuleDefinition(def string) string {
36	var result string
37	for _, line := range strings.Split(def, "\n") {
38		result += strings.TrimSpace(line)
39	}
40	return result
41}
42func TestBpModify(t *testing.T) {
43	var testCases = []struct {
44		name     string
45		input    string
46		output   string
47		err      string
48		modified bool
49		f        func(bp *Blueprint)
50	}{
51		{
52			name: "add",
53			input: `
54			cc_foo {
55				name: "foo",
56			}
57			`,
58			output: `
59			cc_foo {
60				name: "foo",
61				deps: ["bar"],
62			}
63			`,
64			modified: true,
65			f: func(bp *Blueprint) {
66				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "deps"))
67				must(props.AddStringToList("bar"))
68			},
69		},
70		{
71			name: "remove",
72			input: `
73			cc_foo {
74				name: "foo",
75				deps: ["bar"],
76			}
77			`,
78			output: `
79			cc_foo {
80				name: "foo",
81				deps: [],
82			}
83			`,
84			modified: true,
85			f: func(bp *Blueprint) {
86				props := must2(bp.ModulesByName("foo").GetProperty("deps"))
87				must(props.RemoveStringFromList("bar"))
88			},
89		},
90		{
91			name: "nested add",
92			input: `
93			cc_foo {
94				name: "foo",
95			}
96			`,
97			output: `
98			cc_foo {
99				name: "foo",
100				arch: {
101					arm: {
102						deps: [
103							"dep2",
104							"nested_dep",],
105					},
106				},
107			}
108			`,
109			modified: true,
110			f: func(bp *Blueprint) {
111				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "arch.arm.deps"))
112				must(props.AddStringToList("nested_dep", "dep2"))
113			},
114		},
115		{
116			name: "nested remove",
117			input: `
118			cc_foo {
119				name: "foo",
120				arch: {
121					arm: {
122						deps: [
123							"dep2",
124							"nested_dep",
125						],
126					},
127				},
128			}
129			`,
130			output: `
131			cc_foo {
132				name: "foo",
133				arch: {
134					arm: {
135						deps: [
136						],
137					},
138				},
139			}
140			`,
141			modified: true,
142			f: func(bp *Blueprint) {
143				props := must2(bp.ModulesByName("foo").GetProperty("arch.arm.deps"))
144				must(props.RemoveStringFromList("nested_dep", "dep2"))
145			},
146		},
147		{
148			name: "add existing",
149			input: `
150			cc_foo {
151				name: "foo",
152				arch: {
153					arm: {
154						deps: [
155							"nested_dep",
156							"dep2",
157						],
158					},
159				},
160			}
161			`,
162			output: `
163			cc_foo {
164				name: "foo",
165				arch: {
166					arm: {
167						deps: [
168							"nested_dep",
169							"dep2",
170						],
171					},
172				},
173			}
174			`,
175			modified: false,
176			f: func(bp *Blueprint) {
177				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "arch.arm.deps"))
178				must(props.AddStringToList("dep2", "dep2"))
179			},
180		},
181		{
182			name: "remove missing",
183			input: `
184			cc_foo {
185				name: "foo",
186				arch: {
187					arm: {
188						deps: [
189							"nested_dep",
190							"dep2",
191						],
192					},
193				},
194			}
195			`,
196			output: `
197			cc_foo {
198				name: "foo",
199				arch: {
200					arm: {
201						deps: [
202							"nested_dep",
203							"dep2",
204						],
205					},
206				},
207			}
208			`,
209			modified: false,
210			f: func(bp *Blueprint) {
211				props := must2(bp.ModulesByName("foo").GetProperty("arch.arm.deps"))
212				must(props.RemoveStringFromList("dep3", "dep4"))
213			},
214		},
215		{
216			name: "remove non existent",
217			input: `
218			cc_foo {
219				name: "foo",
220			}
221			`,
222			output: `
223			cc_foo {
224				name: "foo",
225			}
226			`,
227			modified: false,
228			f: func(bp *Blueprint) {
229				props := must2(bp.ModulesByName("foo").GetProperty("deps"))
230				must(props.RemoveStringFromList("bar"))
231			},
232		},
233		{
234			name: "remove non existent nested",
235			input: `
236			cc_foo {
237				name: "foo",
238				arch: {},
239			}
240			`,
241			output: `
242			cc_foo {
243				name: "foo",
244				arch: {},
245			}
246			`,
247			modified: false,
248			f: func(bp *Blueprint) {
249				props := must2(bp.ModulesByName("foo").GetProperty("arch.arm.deps"))
250				must(props.RemoveStringFromList("bar"))
251			},
252		},
253		{
254			name: "add numeric sorted",
255			input: `
256			cc_foo {
257				name: "foo",
258				versions: ["1", "2", "20"],
259			}
260			`,
261			output: `
262			cc_foo {
263				name: "foo",
264				versions: [
265					"1",
266					"2",
267					"10",
268					"20",
269				],
270			}
271			`,
272			modified: true,
273			f: func(bp *Blueprint) {
274				props := must2(bp.ModulesByName("foo").GetProperty("versions"))
275				must(props.AddStringToList("10"))
276			},
277		},
278		{
279			name: "add mixed sorted",
280			input: `
281			cc_foo {
282				name: "foo",
283				deps: ["bar-v1-bar", "bar-v2-bar"],
284			}
285			`,
286			output: `
287			cc_foo {
288				name: "foo",
289				deps: [
290					"bar-v1-bar",
291					"bar-v2-bar",
292					"bar-v10-bar",
293				],
294			}
295			`,
296			modified: true,
297			f: func(bp *Blueprint) {
298				props := must2(bp.ModulesByName("foo").GetProperty("deps"))
299				must(props.AddStringToList("bar-v10-bar"))
300			},
301		},
302		{
303			name:  "add a struct with literal",
304			input: `cc_foo {name: "foo"}`,
305			output: `cc_foo {
306				name: "foo",
307				structs: [
308					{
309						version: "1",
310
311						imports: [
312							"bar1",
313							"bar2",
314						],
315					},
316				],
317			}
318			`,
319			modified: true,
320			f: func(bp *Blueprint) {
321				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "structs"))
322				must(props.AddLiteral(`{version: "1", imports: ["bar1", "bar2"]}`))
323			},
324		},
325		{
326			name: "set string",
327			input: `
328			cc_foo {
329				name: "foo",
330			}
331			`,
332			output: `
333			cc_foo {
334				name: "foo",
335				foo: "bar",
336			}
337			`,
338			modified: true,
339			f: func(bp *Blueprint) {
340				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(String, "foo"))
341				must(props.SetString("bar"))
342			},
343		},
344		{
345			name: "set existing string",
346			input: `
347			cc_foo {
348				name: "foo",
349				foo: "baz",
350			}
351			`,
352			output: `
353			cc_foo {
354				name: "foo",
355				foo: "bar",
356			}
357			`,
358			modified: true,
359			f: func(bp *Blueprint) {
360				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(String, "foo"))
361				must(props.SetString("bar"))
362			},
363		},
364		{
365			name: "set bool",
366			input: `
367			cc_foo {
368				name: "foo",
369			}
370			`,
371			output: `
372			cc_foo {
373				name: "foo",
374				foo: true,
375			}
376			`,
377			modified: true,
378			f: func(bp *Blueprint) {
379				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(Bool, "foo"))
380				must(props.SetBool(true))
381			},
382		},
383		{
384			name: "set existing bool",
385			input: `
386			cc_foo {
387				name: "foo",
388				foo: true,
389			}
390			`,
391			output: `
392			cc_foo {
393				name: "foo",
394				foo: false,
395			}
396			`,
397			modified: true,
398			f: func(bp *Blueprint) {
399				props := must2(bp.ModulesByName("foo").GetOrCreateProperty(Bool, "foo"))
400				must(props.SetBool(false))
401			},
402		},
403		{
404			name: "remove existing property",
405			input: `
406			cc_foo {
407				name: "foo",
408				foo: "baz",
409			}
410			`,
411			output: `
412			cc_foo {
413				name: "foo",
414			}
415			`,
416			modified: true,
417			f: func(bp *Blueprint) {
418				must(bp.ModulesByName("foo").RemoveProperty("foo"))
419			},
420		}, {
421			name: "remove nested property",
422			input: `
423			cc_foo {
424				name: "foo",
425				foo: {
426					bar: "baz",
427				},
428			}
429			`,
430			output: `
431			cc_foo {
432				name: "foo",
433				foo: {},
434			}
435			`,
436			modified: true,
437			f: func(bp *Blueprint) {
438				must(bp.ModulesByName("foo").RemoveProperty("foo.bar"))
439			},
440		}, {
441			name: "remove non-existing property",
442			input: `
443			cc_foo {
444				name: "foo",
445				foo: "baz",
446			}
447			`,
448			output: `
449			cc_foo {
450				name: "foo",
451				foo: "baz",
452			}
453			`,
454			modified: false,
455			f: func(bp *Blueprint) {
456				must(bp.ModulesByName("foo").RemoveProperty("bar"))
457			},
458		}, {
459			name: "replace property",
460			input: `
461			cc_foo {
462				name: "foo",
463				deps: ["baz", "unchanged"],
464			}
465			`,
466			output: `
467			cc_foo {
468				name: "foo",
469				deps: [
470                "baz_lib",
471                "unchanged",
472				],
473			}
474			`,
475			modified: true,
476			f: func(bp *Blueprint) {
477				props := must2(bp.ModulesByName("foo").GetProperty("deps"))
478				must(props.ReplaceStrings(map[string]string{"baz": "baz_lib", "foobar": "foobar_lib"}))
479			},
480		}, {
481			name: "replace property multiple modules",
482			input: `
483			cc_foo {
484				name: "foo",
485				deps: ["baz", "unchanged"],
486				unchanged: ["baz"],
487				required: ["foobar"],
488			}
489			`,
490			output: `
491			cc_foo {
492				name: "foo",
493				deps: [
494								"baz_lib",
495								"unchanged",
496				],
497				unchanged: ["baz"],
498				required: ["foobar_lib"],
499			}
500			`,
501			modified: true,
502			f: func(bp *Blueprint) {
503				props := must2(bp.ModulesByName("foo").GetProperty("deps", "required"))
504				must(props.ReplaceStrings(map[string]string{"baz": "baz_lib", "foobar": "foobar_lib"}))
505			},
506		}, {
507			name: "replace property string value",
508			input: `
509			cc_foo {
510				name: "foo",
511				deps: ["baz"],
512				unchanged: ["baz"],
513				required: ["foobar"],
514			}
515			`,
516			output: `
517			cc_foo {
518				name: "foo_lib",
519				deps: ["baz"],
520				unchanged: ["baz"],
521				required: ["foobar"],
522			}
523			`,
524			modified: true,
525			f: func(bp *Blueprint) {
526				props := must2(bp.ModulesByName("foo").GetProperty("name"))
527				must(props.ReplaceStrings(map[string]string{"foo": "foo_lib"}))
528			},
529		}, {
530			name: "replace property string and list values",
531			input: `
532			cc_foo {
533				name: "foo",
534				deps: ["baz"],
535				unchanged: ["baz"],
536				required: ["foobar"],
537			}
538			`,
539			output: `
540			cc_foo {
541				name: "foo_lib",
542				deps: ["baz_lib"],
543				unchanged: ["baz"],
544				required: ["foobar"],
545			}
546			`,
547			modified: true,
548			f: func(bp *Blueprint) {
549				props := must2(bp.ModulesByName("foo").GetProperty("name", "deps"))
550				must(props.ReplaceStrings(map[string]string{"foo": "foo_lib", "baz": "baz_lib"}))
551			},
552		}, {
553			name: "move contents of property into non-existing property",
554			input: `
555			cc_foo {
556				name: "foo",
557				bar: ["barContents"],
558				}
559`,
560			output: `
561			cc_foo {
562				name: "foo",
563				baz: ["barContents"],
564			}
565			`,
566			modified: true,
567			f: func(bp *Blueprint) {
568				must(bp.ModulesByName("foo").MoveProperty("baz", "bar"))
569			},
570		}, {
571			name: "move contents of property into existing property",
572			input: `
573			cc_foo {
574				name: "foo",
575				baz: ["bazContents"],
576				bar: ["barContents"],
577			}
578			`,
579			output: `
580			cc_foo {
581				name: "foo",
582				baz: [
583					"bazContents",
584					"barContents",
585				],
586
587			}
588			`,
589			modified: true,
590			f: func(bp *Blueprint) {
591				must(bp.ModulesByName("foo").MoveProperty("baz", "bar"))
592			},
593		}, {
594			name: "replace nested",
595			input: `
596			cc_foo {
597				name: "foo",
598				foo: {
599					bar: "baz",
600				},
601			}
602			`,
603			output: `
604			cc_foo {
605				name: "foo",
606				foo: {
607					bar: "baz2",
608				},
609			}
610			`,
611			modified: true,
612			f: func(bp *Blueprint) {
613				props := must2(bp.ModulesByName("foo").GetProperty("foo.bar"))
614				must(props.ReplaceStrings(map[string]string{"baz": "baz2"}))
615			},
616		},
617	}
618
619	for i, testCase := range testCases {
620		t.Run(testCase.name, func(t *testing.T) {
621			bp, err := NewBlueprint("", []byte(testCase.input))
622			if err != nil {
623				t.Fatalf("error creating Blueprint: %s", err)
624			}
625			err = nil
626			func() {
627				defer func() {
628					if r := recover(); r != nil {
629						if recoveredErr, ok := r.(error); ok {
630							err = recoveredErr
631						} else {
632							t.Fatalf("unexpected panic: %q", r)
633						}
634					}
635				}()
636				testCase.f(bp)
637			}()
638			if err != nil {
639				if testCase.err != "" {
640					if g, w := err.Error(), testCase.err; !strings.Contains(w, g) {
641						t.Errorf("unexpected error, want %q, got %q", g, w)
642					}
643				} else {
644					t.Errorf("unexpected error %q", err.Error())
645				}
646			} else {
647				if testCase.err != "" {
648					t.Errorf("missing error, expected %q", testCase.err)
649				}
650			}
651
652			if g, w := bp.Modified(), testCase.modified; g != w {
653				t.Errorf("incorrect bp.Modified() value, want %v, got %v", w, g)
654			}
655
656			inModuleString := bp.String()
657			if simplifyModuleDefinition(inModuleString) != simplifyModuleDefinition(testCase.output) {
658				t.Errorf("test case %d:", i)
659				t.Errorf("expected module definition:")
660				t.Errorf("  %s", testCase.output)
661				t.Errorf("actual module definition:")
662				t.Errorf("  %s", inModuleString)
663			}
664		})
665	}
666}
667