• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 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 android
16
17import (
18	"errors"
19	"fmt"
20	"reflect"
21	"strconv"
22	"strings"
23	"testing"
24
25	"github.com/google/blueprint/proptools"
26)
27
28type strsTestCase struct {
29	in  []string
30	out string
31	err []error
32}
33
34var commonValidatePathTestCases = []strsTestCase{
35	{
36		in:  []string{""},
37		out: "",
38	},
39	{
40		in:  []string{"a/b"},
41		out: "a/b",
42	},
43	{
44		in:  []string{"a/b", "c"},
45		out: "a/b/c",
46	},
47	{
48		in:  []string{"a/.."},
49		out: ".",
50	},
51	{
52		in:  []string{"."},
53		out: ".",
54	},
55	{
56		in:  []string{".."},
57		out: "",
58		err: []error{errors.New("Path is outside directory: ..")},
59	},
60	{
61		in:  []string{"../a"},
62		out: "",
63		err: []error{errors.New("Path is outside directory: ../a")},
64	},
65	{
66		in:  []string{"b/../../a"},
67		out: "",
68		err: []error{errors.New("Path is outside directory: ../a")},
69	},
70	{
71		in:  []string{"/a"},
72		out: "",
73		err: []error{errors.New("Path is outside directory: /a")},
74	},
75	{
76		in:  []string{"a", "../b"},
77		out: "",
78		err: []error{errors.New("Path is outside directory: ../b")},
79	},
80	{
81		in:  []string{"a", "b/../../c"},
82		out: "",
83		err: []error{errors.New("Path is outside directory: ../c")},
84	},
85	{
86		in:  []string{"a", "./.."},
87		out: "",
88		err: []error{errors.New("Path is outside directory: ..")},
89	},
90}
91
92var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
93	{
94		in:  []string{"$host/../$a"},
95		out: "$a",
96	},
97}...)
98
99var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
100	{
101		in:  []string{"$host/../$a"},
102		out: "",
103		err: []error{errors.New("Path contains invalid character($): $host/../$a")},
104	},
105	{
106		in:  []string{"$host/.."},
107		out: "",
108		err: []error{errors.New("Path contains invalid character($): $host/..")},
109	},
110}...)
111
112func TestValidateSafePath(t *testing.T) {
113	for _, testCase := range validateSafePathTestCases {
114		t.Run(strings.Join(testCase.in, ","), func(t *testing.T) {
115			ctx := &configErrorWrapper{}
116			out, err := validateSafePath(testCase.in...)
117			if err != nil {
118				reportPathError(ctx, err)
119			}
120			check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
121		})
122	}
123}
124
125func TestValidatePath(t *testing.T) {
126	for _, testCase := range validatePathTestCases {
127		t.Run(strings.Join(testCase.in, ","), func(t *testing.T) {
128			ctx := &configErrorWrapper{}
129			out, err := validatePath(testCase.in...)
130			if err != nil {
131				reportPathError(ctx, err)
132			}
133			check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
134		})
135	}
136}
137
138func TestOptionalPath(t *testing.T) {
139	var path OptionalPath
140	checkInvalidOptionalPath(t, path, "unknown")
141
142	path = OptionalPathForPath(nil)
143	checkInvalidOptionalPath(t, path, "unknown")
144
145	path = InvalidOptionalPath("foo")
146	checkInvalidOptionalPath(t, path, "foo")
147
148	path = InvalidOptionalPath("")
149	checkInvalidOptionalPath(t, path, "unknown")
150
151	path = OptionalPathForPath(PathForTesting("path"))
152	checkValidOptionalPath(t, path, "path")
153}
154
155func checkInvalidOptionalPath(t *testing.T, path OptionalPath, expectedInvalidReason string) {
156	t.Helper()
157	if path.Valid() {
158		t.Errorf("Invalid OptionalPath should not be valid")
159	}
160	if path.InvalidReason() != expectedInvalidReason {
161		t.Errorf("Wrong invalid reason: expected %q, got %q", expectedInvalidReason, path.InvalidReason())
162	}
163	if path.String() != "" {
164		t.Errorf("Invalid OptionalPath String() should return \"\", not %q", path.String())
165	}
166	paths := path.AsPaths()
167	if len(paths) != 0 {
168		t.Errorf("Invalid OptionalPath AsPaths() should return empty Paths, not %q", paths)
169	}
170	defer func() {
171		if r := recover(); r == nil {
172			t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath")
173		}
174	}()
175	path.Path()
176}
177
178func checkValidOptionalPath(t *testing.T, path OptionalPath, expectedString string) {
179	t.Helper()
180	if !path.Valid() {
181		t.Errorf("Initialized OptionalPath should not be invalid")
182	}
183	if path.InvalidReason() != "" {
184		t.Errorf("Initialized OptionalPath should not have an invalid reason, got: %q", path.InvalidReason())
185	}
186	if path.String() != expectedString {
187		t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String())
188	}
189	paths := path.AsPaths()
190	if len(paths) != 1 {
191		t.Errorf("Initialized OptionalPath AsPaths() should return Paths with length 1, not %q", paths)
192	}
193	path.Path()
194}
195
196func check(t *testing.T, testType, testString string,
197	got interface{}, err []error,
198	expected interface{}, expectedErr []error) {
199	t.Helper()
200
201	printedTestCase := false
202	e := func(s string, expected, got interface{}) {
203		t.Helper()
204		if !printedTestCase {
205			t.Errorf("test case %s: %s", testType, testString)
206			printedTestCase = true
207		}
208		t.Errorf("incorrect %s", s)
209		t.Errorf("  expected: %s", p(expected))
210		t.Errorf("       got: %s", p(got))
211	}
212
213	if !reflect.DeepEqual(expectedErr, err) {
214		e("errors:", expectedErr, err)
215	}
216
217	if !reflect.DeepEqual(expected, got) {
218		e("output:", expected, got)
219	}
220}
221
222func p(in interface{}) string {
223	if v, ok := in.([]interface{}); ok {
224		s := make([]string, len(v))
225		for i := range v {
226			s[i] = fmt.Sprintf("%#v", v[i])
227		}
228		return "[" + strings.Join(s, ", ") + "]"
229	} else {
230		return fmt.Sprintf("%#v", in)
231	}
232}
233
234func pathTestConfig(buildDir string) Config {
235	return TestConfig(buildDir, nil, "", nil)
236}
237
238func TestPathForModuleInstall(t *testing.T) {
239	testConfig := pathTestConfig("")
240
241	hostTarget := Target{Os: Linux, Arch: Arch{ArchType: X86}}
242	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
243
244	testCases := []struct {
245		name         string
246		ctx          *testModuleInstallPathContext
247		in           []string
248		out          string
249		partitionDir string
250	}{
251		{
252			name: "host binary",
253			ctx: &testModuleInstallPathContext{
254				baseModuleContext: baseModuleContext{
255					os:     hostTarget.Os,
256					target: hostTarget,
257				},
258			},
259			in:           []string{"bin", "my_test"},
260			out:          "host/linux-x86/bin/my_test",
261			partitionDir: "host/linux-x86",
262		},
263
264		{
265			name: "system binary",
266			ctx: &testModuleInstallPathContext{
267				baseModuleContext: baseModuleContext{
268					os:     deviceTarget.Os,
269					target: deviceTarget,
270				},
271			},
272			in:           []string{"bin", "my_test"},
273			out:          "target/product/test_device/system/bin/my_test",
274			partitionDir: "target/product/test_device/system",
275		},
276		{
277			name: "vendor binary",
278			ctx: &testModuleInstallPathContext{
279				baseModuleContext: baseModuleContext{
280					os:     deviceTarget.Os,
281					target: deviceTarget,
282					earlyModuleContext: earlyModuleContext{
283						kind: socSpecificModule,
284					},
285				},
286			},
287			in:           []string{"bin", "my_test"},
288			out:          "target/product/test_device/vendor/bin/my_test",
289			partitionDir: "target/product/test_device/vendor",
290		},
291		{
292			name: "odm binary",
293			ctx: &testModuleInstallPathContext{
294				baseModuleContext: baseModuleContext{
295					os:     deviceTarget.Os,
296					target: deviceTarget,
297					earlyModuleContext: earlyModuleContext{
298						kind: deviceSpecificModule,
299					},
300				},
301			},
302			in:           []string{"bin", "my_test"},
303			out:          "target/product/test_device/odm/bin/my_test",
304			partitionDir: "target/product/test_device/odm",
305		},
306		{
307			name: "product binary",
308			ctx: &testModuleInstallPathContext{
309				baseModuleContext: baseModuleContext{
310					os:     deviceTarget.Os,
311					target: deviceTarget,
312					earlyModuleContext: earlyModuleContext{
313						kind: productSpecificModule,
314					},
315				},
316			},
317			in:           []string{"bin", "my_test"},
318			out:          "target/product/test_device/product/bin/my_test",
319			partitionDir: "target/product/test_device/product",
320		},
321		{
322			name: "system_ext binary",
323			ctx: &testModuleInstallPathContext{
324				baseModuleContext: baseModuleContext{
325					os:     deviceTarget.Os,
326					target: deviceTarget,
327					earlyModuleContext: earlyModuleContext{
328						kind: systemExtSpecificModule,
329					},
330				},
331			},
332			in:           []string{"bin", "my_test"},
333			out:          "target/product/test_device/system_ext/bin/my_test",
334			partitionDir: "target/product/test_device/system_ext",
335		},
336		{
337			name: "root binary",
338			ctx: &testModuleInstallPathContext{
339				baseModuleContext: baseModuleContext{
340					os:     deviceTarget.Os,
341					target: deviceTarget,
342				},
343				inRoot: true,
344			},
345			in:           []string{"my_test"},
346			out:          "target/product/test_device/root/my_test",
347			partitionDir: "target/product/test_device/root",
348		},
349		{
350			name: "recovery binary",
351			ctx: &testModuleInstallPathContext{
352				baseModuleContext: baseModuleContext{
353					os:     deviceTarget.Os,
354					target: deviceTarget,
355				},
356				inRecovery: true,
357			},
358			in:           []string{"bin/my_test"},
359			out:          "target/product/test_device/recovery/root/system/bin/my_test",
360			partitionDir: "target/product/test_device/recovery/root/system",
361		},
362		{
363			name: "recovery root binary",
364			ctx: &testModuleInstallPathContext{
365				baseModuleContext: baseModuleContext{
366					os:     deviceTarget.Os,
367					target: deviceTarget,
368				},
369				inRecovery: true,
370				inRoot:     true,
371			},
372			in:           []string{"my_test"},
373			out:          "target/product/test_device/recovery/root/my_test",
374			partitionDir: "target/product/test_device/recovery/root",
375		},
376
377		{
378			name: "ramdisk binary",
379			ctx: &testModuleInstallPathContext{
380				baseModuleContext: baseModuleContext{
381					os:     deviceTarget.Os,
382					target: deviceTarget,
383				},
384				inRamdisk: true,
385			},
386			in:           []string{"my_test"},
387			out:          "target/product/test_device/ramdisk/system/my_test",
388			partitionDir: "target/product/test_device/ramdisk/system",
389		},
390		{
391			name: "ramdisk root binary",
392			ctx: &testModuleInstallPathContext{
393				baseModuleContext: baseModuleContext{
394					os:     deviceTarget.Os,
395					target: deviceTarget,
396				},
397				inRamdisk: true,
398				inRoot:    true,
399			},
400			in:           []string{"my_test"},
401			out:          "target/product/test_device/ramdisk/my_test",
402			partitionDir: "target/product/test_device/ramdisk",
403		},
404		{
405			name: "vendor_ramdisk binary",
406			ctx: &testModuleInstallPathContext{
407				baseModuleContext: baseModuleContext{
408					os:     deviceTarget.Os,
409					target: deviceTarget,
410				},
411				inVendorRamdisk: true,
412			},
413			in:           []string{"my_test"},
414			out:          "target/product/test_device/vendor_ramdisk/system/my_test",
415			partitionDir: "target/product/test_device/vendor_ramdisk/system",
416		},
417		{
418			name: "vendor_ramdisk root binary",
419			ctx: &testModuleInstallPathContext{
420				baseModuleContext: baseModuleContext{
421					os:     deviceTarget.Os,
422					target: deviceTarget,
423				},
424				inVendorRamdisk: true,
425				inRoot:          true,
426			},
427			in:           []string{"my_test"},
428			out:          "target/product/test_device/vendor_ramdisk/my_test",
429			partitionDir: "target/product/test_device/vendor_ramdisk",
430		},
431		{
432			name: "debug_ramdisk binary",
433			ctx: &testModuleInstallPathContext{
434				baseModuleContext: baseModuleContext{
435					os:     deviceTarget.Os,
436					target: deviceTarget,
437				},
438				inDebugRamdisk: true,
439			},
440			in:           []string{"my_test"},
441			out:          "target/product/test_device/debug_ramdisk/my_test",
442			partitionDir: "target/product/test_device/debug_ramdisk",
443		},
444		{
445			name: "system native test binary",
446			ctx: &testModuleInstallPathContext{
447				baseModuleContext: baseModuleContext{
448					os:     deviceTarget.Os,
449					target: deviceTarget,
450				},
451				inData: true,
452			},
453			in:           []string{"nativetest", "my_test"},
454			out:          "target/product/test_device/data/nativetest/my_test",
455			partitionDir: "target/product/test_device/data",
456		},
457		{
458			name: "vendor native test binary",
459			ctx: &testModuleInstallPathContext{
460				baseModuleContext: baseModuleContext{
461					os:     deviceTarget.Os,
462					target: deviceTarget,
463					earlyModuleContext: earlyModuleContext{
464						kind: socSpecificModule,
465					},
466				},
467				inData: true,
468			},
469			in:           []string{"nativetest", "my_test"},
470			out:          "target/product/test_device/data/nativetest/my_test",
471			partitionDir: "target/product/test_device/data",
472		},
473		{
474			name: "odm native test binary",
475			ctx: &testModuleInstallPathContext{
476				baseModuleContext: baseModuleContext{
477					os:     deviceTarget.Os,
478					target: deviceTarget,
479					earlyModuleContext: earlyModuleContext{
480						kind: deviceSpecificModule,
481					},
482				},
483				inData: true,
484			},
485			in:           []string{"nativetest", "my_test"},
486			out:          "target/product/test_device/data/nativetest/my_test",
487			partitionDir: "target/product/test_device/data",
488		},
489		{
490			name: "product native test binary",
491			ctx: &testModuleInstallPathContext{
492				baseModuleContext: baseModuleContext{
493					os:     deviceTarget.Os,
494					target: deviceTarget,
495					earlyModuleContext: earlyModuleContext{
496						kind: productSpecificModule,
497					},
498				},
499				inData: true,
500			},
501			in:           []string{"nativetest", "my_test"},
502			out:          "target/product/test_device/data/nativetest/my_test",
503			partitionDir: "target/product/test_device/data",
504		},
505
506		{
507			name: "system_ext native test binary",
508			ctx: &testModuleInstallPathContext{
509				baseModuleContext: baseModuleContext{
510					os:     deviceTarget.Os,
511					target: deviceTarget,
512					earlyModuleContext: earlyModuleContext{
513						kind: systemExtSpecificModule,
514					},
515				},
516				inData: true,
517			},
518			in:           []string{"nativetest", "my_test"},
519			out:          "target/product/test_device/data/nativetest/my_test",
520			partitionDir: "target/product/test_device/data",
521		},
522
523		{
524			name: "sanitized system binary",
525			ctx: &testModuleInstallPathContext{
526				baseModuleContext: baseModuleContext{
527					os:     deviceTarget.Os,
528					target: deviceTarget,
529				},
530				inSanitizerDir: true,
531			},
532			in:           []string{"bin", "my_test"},
533			out:          "target/product/test_device/data/asan/system/bin/my_test",
534			partitionDir: "target/product/test_device/data/asan/system",
535		},
536		{
537			name: "sanitized vendor binary",
538			ctx: &testModuleInstallPathContext{
539				baseModuleContext: baseModuleContext{
540					os:     deviceTarget.Os,
541					target: deviceTarget,
542					earlyModuleContext: earlyModuleContext{
543						kind: socSpecificModule,
544					},
545				},
546				inSanitizerDir: true,
547			},
548			in:           []string{"bin", "my_test"},
549			out:          "target/product/test_device/data/asan/vendor/bin/my_test",
550			partitionDir: "target/product/test_device/data/asan/vendor",
551		},
552		{
553			name: "sanitized odm binary",
554			ctx: &testModuleInstallPathContext{
555				baseModuleContext: baseModuleContext{
556					os:     deviceTarget.Os,
557					target: deviceTarget,
558					earlyModuleContext: earlyModuleContext{
559						kind: deviceSpecificModule,
560					},
561				},
562				inSanitizerDir: true,
563			},
564			in:           []string{"bin", "my_test"},
565			out:          "target/product/test_device/data/asan/odm/bin/my_test",
566			partitionDir: "target/product/test_device/data/asan/odm",
567		},
568		{
569			name: "sanitized product binary",
570			ctx: &testModuleInstallPathContext{
571				baseModuleContext: baseModuleContext{
572					os:     deviceTarget.Os,
573					target: deviceTarget,
574					earlyModuleContext: earlyModuleContext{
575						kind: productSpecificModule,
576					},
577				},
578				inSanitizerDir: true,
579			},
580			in:           []string{"bin", "my_test"},
581			out:          "target/product/test_device/data/asan/product/bin/my_test",
582			partitionDir: "target/product/test_device/data/asan/product",
583		},
584
585		{
586			name: "sanitized system_ext binary",
587			ctx: &testModuleInstallPathContext{
588				baseModuleContext: baseModuleContext{
589					os:     deviceTarget.Os,
590					target: deviceTarget,
591					earlyModuleContext: earlyModuleContext{
592						kind: systemExtSpecificModule,
593					},
594				},
595				inSanitizerDir: true,
596			},
597			in:           []string{"bin", "my_test"},
598			out:          "target/product/test_device/data/asan/system_ext/bin/my_test",
599			partitionDir: "target/product/test_device/data/asan/system_ext",
600		},
601
602		{
603			name: "sanitized system native test binary",
604			ctx: &testModuleInstallPathContext{
605				baseModuleContext: baseModuleContext{
606					os:     deviceTarget.Os,
607					target: deviceTarget,
608				},
609				inData:         true,
610				inSanitizerDir: true,
611			},
612			in:           []string{"nativetest", "my_test"},
613			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
614			partitionDir: "target/product/test_device/data/asan/data",
615		},
616		{
617			name: "sanitized vendor native test binary",
618			ctx: &testModuleInstallPathContext{
619				baseModuleContext: baseModuleContext{
620					os:     deviceTarget.Os,
621					target: deviceTarget,
622					earlyModuleContext: earlyModuleContext{
623						kind: socSpecificModule,
624					},
625				},
626				inData:         true,
627				inSanitizerDir: true,
628			},
629			in:           []string{"nativetest", "my_test"},
630			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
631			partitionDir: "target/product/test_device/data/asan/data",
632		},
633		{
634			name: "sanitized odm native test binary",
635			ctx: &testModuleInstallPathContext{
636				baseModuleContext: baseModuleContext{
637					os:     deviceTarget.Os,
638					target: deviceTarget,
639					earlyModuleContext: earlyModuleContext{
640						kind: deviceSpecificModule,
641					},
642				},
643				inData:         true,
644				inSanitizerDir: true,
645			},
646			in:           []string{"nativetest", "my_test"},
647			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
648			partitionDir: "target/product/test_device/data/asan/data",
649		},
650		{
651			name: "sanitized product native test binary",
652			ctx: &testModuleInstallPathContext{
653				baseModuleContext: baseModuleContext{
654					os:     deviceTarget.Os,
655					target: deviceTarget,
656					earlyModuleContext: earlyModuleContext{
657						kind: productSpecificModule,
658					},
659				},
660				inData:         true,
661				inSanitizerDir: true,
662			},
663			in:           []string{"nativetest", "my_test"},
664			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
665			partitionDir: "target/product/test_device/data/asan/data",
666		},
667		{
668			name: "sanitized system_ext native test binary",
669			ctx: &testModuleInstallPathContext{
670				baseModuleContext: baseModuleContext{
671					os:     deviceTarget.Os,
672					target: deviceTarget,
673					earlyModuleContext: earlyModuleContext{
674						kind: systemExtSpecificModule,
675					},
676				},
677				inData:         true,
678				inSanitizerDir: true,
679			},
680			in:           []string{"nativetest", "my_test"},
681			out:          "target/product/test_device/data/asan/data/nativetest/my_test",
682			partitionDir: "target/product/test_device/data/asan/data",
683		}, {
684			name: "device testcases",
685			ctx: &testModuleInstallPathContext{
686				baseModuleContext: baseModuleContext{
687					os:     deviceTarget.Os,
688					target: deviceTarget,
689				},
690				inTestcases: true,
691			},
692			in:           []string{"my_test", "my_test_bin"},
693			out:          "target/product/test_device/testcases/my_test/my_test_bin",
694			partitionDir: "target/product/test_device/testcases",
695		}, {
696			name: "host testcases",
697			ctx: &testModuleInstallPathContext{
698				baseModuleContext: baseModuleContext{
699					os:     hostTarget.Os,
700					target: hostTarget,
701				},
702				inTestcases: true,
703			},
704			in:           []string{"my_test", "my_test_bin"},
705			out:          "host/linux-x86/testcases/my_test/my_test_bin",
706			partitionDir: "host/linux-x86/testcases",
707		}, {
708			name: "forced host testcases",
709			ctx: &testModuleInstallPathContext{
710				baseModuleContext: baseModuleContext{
711					os:     deviceTarget.Os,
712					target: deviceTarget,
713				},
714				inTestcases: true,
715				forceOS:     &Linux,
716				forceArch:   &X86,
717			},
718			in:           []string{"my_test", "my_test_bin"},
719			out:          "host/linux-x86/testcases/my_test/my_test_bin",
720			partitionDir: "host/linux-x86/testcases",
721		},
722	}
723
724	for _, tc := range testCases {
725		t.Run(tc.name, func(t *testing.T) {
726			tc.ctx.baseModuleContext.config = testConfig
727			output := PathForModuleInstall(tc.ctx, tc.in...)
728			if output.basePath.path != tc.out {
729				t.Errorf("unexpected path:\n got: %q\nwant: %q\n",
730					output.basePath.path,
731					tc.out)
732			}
733			if output.partitionDir != tc.partitionDir {
734				t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n",
735					output.partitionDir, tc.partitionDir)
736			}
737		})
738	}
739}
740
741func TestPathForModuleInstallRecoveryAsBoot(t *testing.T) {
742	testConfig := pathTestConfig("")
743	testConfig.TestProductVariables.BoardUsesRecoveryAsBoot = proptools.BoolPtr(true)
744	testConfig.TestProductVariables.BoardMoveRecoveryResourcesToVendorBoot = proptools.BoolPtr(true)
745	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
746
747	testCases := []struct {
748		name         string
749		ctx          *testModuleInstallPathContext
750		in           []string
751		out          string
752		partitionDir string
753	}{
754		{
755			name: "ramdisk binary",
756			ctx: &testModuleInstallPathContext{
757				baseModuleContext: baseModuleContext{
758					os:     deviceTarget.Os,
759					target: deviceTarget,
760				},
761				inRamdisk: true,
762				inRoot:    true,
763			},
764			in:           []string{"my_test"},
765			out:          "target/product/test_device/recovery/root/first_stage_ramdisk/my_test",
766			partitionDir: "target/product/test_device/recovery/root/first_stage_ramdisk",
767		},
768
769		{
770			name: "vendor_ramdisk binary",
771			ctx: &testModuleInstallPathContext{
772				baseModuleContext: baseModuleContext{
773					os:     deviceTarget.Os,
774					target: deviceTarget,
775				},
776				inVendorRamdisk: true,
777				inRoot:          true,
778			},
779			in:           []string{"my_test"},
780			out:          "target/product/test_device/vendor_ramdisk/first_stage_ramdisk/my_test",
781			partitionDir: "target/product/test_device/vendor_ramdisk/first_stage_ramdisk",
782		},
783	}
784
785	for _, tc := range testCases {
786		t.Run(tc.name, func(t *testing.T) {
787			tc.ctx.baseModuleContext.config = testConfig
788			output := PathForModuleInstall(tc.ctx, tc.in...)
789			if output.basePath.path != tc.out {
790				t.Errorf("unexpected path:\n got: %q\nwant: %q\n",
791					output.basePath.path,
792					tc.out)
793			}
794			if output.partitionDir != tc.partitionDir {
795				t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n",
796					output.partitionDir, tc.partitionDir)
797			}
798		})
799	}
800}
801
802func TestBaseDirForInstallPath(t *testing.T) {
803	testConfig := pathTestConfig("")
804	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
805
806	ctx := &testModuleInstallPathContext{
807		baseModuleContext: baseModuleContext{
808			os:     deviceTarget.Os,
809			target: deviceTarget,
810		},
811	}
812	ctx.baseModuleContext.config = testConfig
813
814	actual := PathForModuleInstall(ctx, "foo", "bar")
815	expectedBaseDir := "target/product/test_device/system"
816	if actual.partitionDir != expectedBaseDir {
817		t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", actual.partitionDir, expectedBaseDir)
818	}
819	expectedRelPath := "foo/bar"
820	if actual.Rel() != expectedRelPath {
821		t.Errorf("unexpected Rel():\n got: %q\nwant: %q\n", actual.Rel(), expectedRelPath)
822	}
823
824	actualAfterJoin := actual.Join(ctx, "baz")
825	// partitionDir is preserved even after joining
826	if actualAfterJoin.partitionDir != expectedBaseDir {
827		t.Errorf("unexpected partitionDir after joining:\n got: %q\nwant: %q\n", actualAfterJoin.partitionDir, expectedBaseDir)
828	}
829	// Rel() is updated though
830	expectedRelAfterJoin := "baz"
831	if actualAfterJoin.Rel() != expectedRelAfterJoin {
832		t.Errorf("unexpected Rel() after joining:\n got: %q\nwant: %q\n", actualAfterJoin.Rel(), expectedRelAfterJoin)
833	}
834}
835
836func TestDirectorySortedPaths(t *testing.T) {
837	config := TestConfig("out", nil, "", map[string][]byte{
838		"Android.bp": nil,
839		"a.txt":      nil,
840		"a/txt":      nil,
841		"a/b/c":      nil,
842		"a/b/d":      nil,
843		"b":          nil,
844		"b/b.txt":    nil,
845		"a/a.txt":    nil,
846	})
847
848	ctx := PathContextForTesting(config)
849
850	makePaths := func() Paths {
851		return Paths{
852			PathForSource(ctx, "a.txt"),
853			PathForSource(ctx, "a/txt"),
854			PathForSource(ctx, "a/b/c"),
855			PathForSource(ctx, "a/b/d"),
856			PathForSource(ctx, "b"),
857			PathForSource(ctx, "b/b.txt"),
858			PathForSource(ctx, "a/a.txt"),
859		}
860	}
861
862	expected := []string{
863		"a.txt",
864		"a/a.txt",
865		"a/b/c",
866		"a/b/d",
867		"a/txt",
868		"b",
869		"b/b.txt",
870	}
871
872	paths := makePaths()
873	reversePaths := ReversePaths(paths)
874
875	sortedPaths := PathsToDirectorySortedPaths(paths)
876	reverseSortedPaths := PathsToDirectorySortedPaths(reversePaths)
877
878	if !reflect.DeepEqual(Paths(sortedPaths).Strings(), expected) {
879		t.Fatalf("sorted paths:\n %#v\n != \n %#v", paths.Strings(), expected)
880	}
881
882	if !reflect.DeepEqual(Paths(reverseSortedPaths).Strings(), expected) {
883		t.Fatalf("sorted reversed paths:\n %#v\n !=\n %#v", reversePaths.Strings(), expected)
884	}
885
886	expectedA := []string{
887		"a/a.txt",
888		"a/b/c",
889		"a/b/d",
890		"a/txt",
891	}
892
893	inA := sortedPaths.PathsInDirectory("a")
894	if !reflect.DeepEqual(inA.Strings(), expectedA) {
895		t.Errorf("FilesInDirectory(a):\n %#v\n != \n %#v", inA.Strings(), expectedA)
896	}
897
898	expectedA_B := []string{
899		"a/b/c",
900		"a/b/d",
901	}
902
903	inA_B := sortedPaths.PathsInDirectory("a/b")
904	if !reflect.DeepEqual(inA_B.Strings(), expectedA_B) {
905		t.Errorf("FilesInDirectory(a/b):\n %#v\n != \n %#v", inA_B.Strings(), expectedA_B)
906	}
907
908	expectedB := []string{
909		"b/b.txt",
910	}
911
912	inB := sortedPaths.PathsInDirectory("b")
913	if !reflect.DeepEqual(inB.Strings(), expectedB) {
914		t.Errorf("FilesInDirectory(b):\n %#v\n != \n %#v", inA.Strings(), expectedA)
915	}
916}
917
918func TestMaybeRel(t *testing.T) {
919	testCases := []struct {
920		name   string
921		base   string
922		target string
923		out    string
924		isRel  bool
925	}{
926		{
927			name:   "normal",
928			base:   "a/b/c",
929			target: "a/b/c/d",
930			out:    "d",
931			isRel:  true,
932		},
933		{
934			name:   "parent",
935			base:   "a/b/c/d",
936			target: "a/b/c",
937			isRel:  false,
938		},
939		{
940			name:   "not relative",
941			base:   "a/b",
942			target: "c/d",
943			isRel:  false,
944		},
945		{
946			name:   "abs1",
947			base:   "/a",
948			target: "a",
949			isRel:  false,
950		},
951		{
952			name:   "abs2",
953			base:   "a",
954			target: "/a",
955			isRel:  false,
956		},
957	}
958
959	for _, testCase := range testCases {
960		t.Run(testCase.name, func(t *testing.T) {
961			ctx := &configErrorWrapper{}
962			out, isRel := MaybeRel(ctx, testCase.base, testCase.target)
963			if len(ctx.errors) > 0 {
964				t.Errorf("MaybeRel(..., %s, %s) reported unexpected errors %v",
965					testCase.base, testCase.target, ctx.errors)
966			}
967			if isRel != testCase.isRel || out != testCase.out {
968				t.Errorf("MaybeRel(..., %s, %s) want %v, %v got %v, %v",
969					testCase.base, testCase.target, testCase.out, testCase.isRel, out, isRel)
970			}
971		})
972	}
973}
974
975func TestPathForSource(t *testing.T) {
976	testCases := []struct {
977		name     string
978		buildDir string
979		src      string
980		err      string
981	}{
982		{
983			name:     "normal",
984			buildDir: "out",
985			src:      "a/b/c",
986		},
987		{
988			name:     "abs",
989			buildDir: "out",
990			src:      "/a/b/c",
991			err:      "is outside directory",
992		},
993		{
994			name:     "in out dir",
995			buildDir: "out",
996			src:      "out/soong/a/b/c",
997			err:      "is in output",
998		},
999	}
1000
1001	funcs := []struct {
1002		name string
1003		f    func(ctx PathContext, pathComponents ...string) (SourcePath, error)
1004	}{
1005		{"pathForSource", pathForSource},
1006		{"safePathForSource", safePathForSource},
1007	}
1008
1009	for _, f := range funcs {
1010		t.Run(f.name, func(t *testing.T) {
1011			for _, test := range testCases {
1012				t.Run(test.name, func(t *testing.T) {
1013					testConfig := pathTestConfig(test.buildDir)
1014					ctx := &configErrorWrapper{config: testConfig}
1015					_, err := f.f(ctx, test.src)
1016					if len(ctx.errors) > 0 {
1017						t.Fatalf("unexpected errors %v", ctx.errors)
1018					}
1019					if err != nil {
1020						if test.err == "" {
1021							t.Fatalf("unexpected error %q", err.Error())
1022						} else if !strings.Contains(err.Error(), test.err) {
1023							t.Fatalf("incorrect error, want substring %q got %q", test.err, err.Error())
1024						}
1025					} else {
1026						if test.err != "" {
1027							t.Fatalf("missing error %q", test.err)
1028						}
1029					}
1030				})
1031			}
1032		})
1033	}
1034}
1035
1036type pathForModuleSrcTestModule struct {
1037	ModuleBase
1038	props struct {
1039		Srcs         []string `android:"path"`
1040		Exclude_srcs []string `android:"path"`
1041
1042		Src *string `android:"path"`
1043
1044		Module_handles_missing_deps bool
1045	}
1046
1047	src string
1048	rel string
1049
1050	srcs []string
1051	rels []string
1052
1053	missingDeps []string
1054}
1055
1056func pathForModuleSrcTestModuleFactory() Module {
1057	module := &pathForModuleSrcTestModule{}
1058	module.AddProperties(&module.props)
1059	InitAndroidModule(module)
1060	return module
1061}
1062
1063func (p *pathForModuleSrcTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
1064	var srcs Paths
1065	if p.props.Module_handles_missing_deps {
1066		srcs, p.missingDeps = PathsAndMissingDepsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs)
1067	} else {
1068		srcs = PathsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs)
1069	}
1070	p.srcs = srcs.Strings()
1071
1072	for _, src := range srcs {
1073		p.rels = append(p.rels, src.Rel())
1074	}
1075
1076	if p.props.Src != nil {
1077		src := PathForModuleSrc(ctx, *p.props.Src)
1078		if src != nil {
1079			p.src = src.String()
1080			p.rel = src.Rel()
1081		}
1082	}
1083
1084	if !p.props.Module_handles_missing_deps {
1085		p.missingDeps = ctx.GetMissingDependencies()
1086	}
1087
1088	ctx.Build(pctx, BuildParams{
1089		Rule:   Touch,
1090		Output: PathForModuleOut(ctx, "output"),
1091	})
1092}
1093
1094type pathForModuleSrcOutputFileProviderModule struct {
1095	ModuleBase
1096	props struct {
1097		Outs   []string
1098		Tagged []string
1099	}
1100
1101	outs   Paths
1102	tagged Paths
1103}
1104
1105func pathForModuleSrcOutputFileProviderModuleFactory() Module {
1106	module := &pathForModuleSrcOutputFileProviderModule{}
1107	module.AddProperties(&module.props)
1108	InitAndroidModule(module)
1109	return module
1110}
1111
1112func (p *pathForModuleSrcOutputFileProviderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
1113	for _, out := range p.props.Outs {
1114		p.outs = append(p.outs, PathForModuleOut(ctx, out))
1115	}
1116
1117	for _, tagged := range p.props.Tagged {
1118		p.tagged = append(p.tagged, PathForModuleOut(ctx, tagged))
1119	}
1120}
1121
1122func (p *pathForModuleSrcOutputFileProviderModule) OutputFiles(tag string) (Paths, error) {
1123	switch tag {
1124	case "":
1125		return p.outs, nil
1126	case ".tagged":
1127		return p.tagged, nil
1128	default:
1129		return nil, fmt.Errorf("unsupported tag %q", tag)
1130	}
1131}
1132
1133type pathForModuleSrcTestCase struct {
1134	name string
1135	bp   string
1136	srcs []string
1137	rels []string
1138	src  string
1139	rel  string
1140
1141	// Make test specific preparations to the test fixture.
1142	preparer FixturePreparer
1143
1144	// A test specific error handler.
1145	errorHandler FixtureErrorHandler
1146}
1147
1148func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) {
1149	for _, test := range tests {
1150		t.Run(test.name, func(t *testing.T) {
1151			fgBp := `
1152				filegroup {
1153					name: "a",
1154					srcs: ["src/a"],
1155				}
1156			`
1157
1158			ofpBp := `
1159				output_file_provider {
1160					name: "b",
1161					outs: ["gen/b"],
1162					tagged: ["gen/c"],
1163				}
1164			`
1165
1166			mockFS := MockFS{
1167				"fg/Android.bp":     []byte(fgBp),
1168				"foo/Android.bp":    []byte(test.bp),
1169				"ofp/Android.bp":    []byte(ofpBp),
1170				"fg/src/a":          nil,
1171				"foo/src/b":         nil,
1172				"foo/src/c":         nil,
1173				"foo/src/d":         nil,
1174				"foo/src/e/e":       nil,
1175				"foo/src_special/$": nil,
1176			}
1177
1178			errorHandler := test.errorHandler
1179			if errorHandler == nil {
1180				errorHandler = FixtureExpectsNoErrors
1181			}
1182
1183			result := GroupFixturePreparers(
1184				FixtureRegisterWithContext(func(ctx RegistrationContext) {
1185					ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
1186					ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
1187				}),
1188				PrepareForTestWithFilegroup,
1189				PrepareForTestWithNamespace,
1190				mockFS.AddToFixture(),
1191				OptionalFixturePreparer(test.preparer),
1192			).
1193				ExtendWithErrorHandler(errorHandler).
1194				RunTest(t)
1195
1196			m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
1197
1198			AssertStringPathsRelativeToTopEquals(t, "srcs", result.Config, test.srcs, m.srcs)
1199			AssertStringPathsRelativeToTopEquals(t, "rels", result.Config, test.rels, m.rels)
1200			AssertStringPathRelativeToTopEquals(t, "src", result.Config, test.src, m.src)
1201			AssertStringPathRelativeToTopEquals(t, "rel", result.Config, test.rel, m.rel)
1202		})
1203	}
1204}
1205
1206func TestPathsForModuleSrc(t *testing.T) {
1207	tests := []pathForModuleSrcTestCase{
1208		{
1209			name: "path",
1210			bp: `
1211			test {
1212				name: "foo",
1213				srcs: ["src/b"],
1214			}`,
1215			srcs: []string{"foo/src/b"},
1216			rels: []string{"src/b"},
1217		},
1218		{
1219			name: "glob",
1220			bp: `
1221			test {
1222				name: "foo",
1223				srcs: [
1224					"src/*",
1225					"src/e/*",
1226				],
1227			}`,
1228			srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"},
1229			rels: []string{"src/b", "src/c", "src/d", "src/e/e"},
1230		},
1231		{
1232			name: "recursive glob",
1233			bp: `
1234			test {
1235				name: "foo",
1236				srcs: ["src/**/*"],
1237			}`,
1238			srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"},
1239			rels: []string{"src/b", "src/c", "src/d", "src/e/e"},
1240		},
1241		{
1242			name: "filegroup",
1243			bp: `
1244			test {
1245				name: "foo",
1246				srcs: [":a"],
1247			}`,
1248			srcs: []string{"fg/src/a"},
1249			rels: []string{"src/a"},
1250		},
1251		{
1252			name: "output file provider",
1253			bp: `
1254			test {
1255				name: "foo",
1256				srcs: [":b"],
1257			}`,
1258			srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"},
1259			rels: []string{"gen/b"},
1260		},
1261		{
1262			name: "output file provider tagged",
1263			bp: `
1264			test {
1265				name: "foo",
1266				srcs: [":b{.tagged}"],
1267			}`,
1268			srcs: []string{"out/soong/.intermediates/ofp/b/gen/c"},
1269			rels: []string{"gen/c"},
1270		},
1271		{
1272			name: "output file provider with exclude",
1273			bp: `
1274			test {
1275				name: "foo",
1276				srcs: [":b", ":c"],
1277				exclude_srcs: [":c"]
1278			}
1279			output_file_provider {
1280				name: "c",
1281				outs: ["gen/c"],
1282			}`,
1283			srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"},
1284			rels: []string{"gen/b"},
1285		},
1286		{
1287			name: "special characters glob",
1288			bp: `
1289			test {
1290				name: "foo",
1291				srcs: ["src_special/*"],
1292			}`,
1293			srcs: []string{"foo/src_special/$"},
1294			rels: []string{"src_special/$"},
1295		},
1296	}
1297
1298	testPathForModuleSrc(t, tests)
1299}
1300
1301func TestPathForModuleSrc(t *testing.T) {
1302	tests := []pathForModuleSrcTestCase{
1303		{
1304			name: "path",
1305			bp: `
1306			test {
1307				name: "foo",
1308				src: "src/b",
1309			}`,
1310			src: "foo/src/b",
1311			rel: "src/b",
1312		},
1313		{
1314			name: "glob",
1315			bp: `
1316			test {
1317				name: "foo",
1318				src: "src/e/*",
1319			}`,
1320			src: "foo/src/e/e",
1321			rel: "src/e/e",
1322		},
1323		{
1324			name: "filegroup",
1325			bp: `
1326			test {
1327				name: "foo",
1328				src: ":a",
1329			}`,
1330			src: "fg/src/a",
1331			rel: "src/a",
1332		},
1333		{
1334			name: "output file provider",
1335			bp: `
1336			test {
1337				name: "foo",
1338				src: ":b",
1339			}`,
1340			src: "out/soong/.intermediates/ofp/b/gen/b",
1341			rel: "gen/b",
1342		},
1343		{
1344			name: "output file provider tagged",
1345			bp: `
1346			test {
1347				name: "foo",
1348				src: ":b{.tagged}",
1349			}`,
1350			src: "out/soong/.intermediates/ofp/b/gen/c",
1351			rel: "gen/c",
1352		},
1353		{
1354			name: "special characters glob",
1355			bp: `
1356			test {
1357				name: "foo",
1358				src: "src_special/*",
1359			}`,
1360			src: "foo/src_special/$",
1361			rel: "src_special/$",
1362		},
1363		{
1364			// This test makes sure that an unqualified module name cannot contain characters that make
1365			// it appear as a qualified module name.
1366			name: "output file provider, invalid fully qualified name",
1367			bp: `
1368			test {
1369				name: "foo",
1370				src: "://other:b",
1371				srcs: ["://other:c"],
1372			}`,
1373			preparer: FixtureAddTextFile("other/Android.bp", `
1374				soong_namespace {}
1375
1376				output_file_provider {
1377					name: "b",
1378					outs: ["gen/b"],
1379				}
1380
1381				output_file_provider {
1382					name: "c",
1383					outs: ["gen/c"],
1384				}
1385			`),
1386			src:  "foo/:/other:b",
1387			rel:  ":/other:b",
1388			srcs: []string{"foo/:/other:c"},
1389			rels: []string{":/other:c"},
1390		},
1391		{
1392			name: "output file provider, missing fully qualified name",
1393			bp: `
1394			test {
1395				name: "foo",
1396				src: "//other:b",
1397				srcs: ["//other:c"],
1398			}`,
1399			errorHandler: FixtureExpectsAllErrorsToMatchAPattern([]string{
1400				`"foo" depends on undefined module "//other:b"`,
1401				`"foo" depends on undefined module "//other:c"`,
1402			}),
1403		},
1404		{
1405			name: "output file provider, fully qualified name",
1406			bp: `
1407			test {
1408				name: "foo",
1409				src: "//other:b",
1410				srcs: ["//other:c"],
1411			}`,
1412			src:  "out/soong/.intermediates/other/b/gen/b",
1413			rel:  "gen/b",
1414			srcs: []string{"out/soong/.intermediates/other/c/gen/c"},
1415			rels: []string{"gen/c"},
1416			preparer: FixtureAddTextFile("other/Android.bp", `
1417				soong_namespace {}
1418
1419				output_file_provider {
1420					name: "b",
1421					outs: ["gen/b"],
1422				}
1423
1424				output_file_provider {
1425					name: "c",
1426					outs: ["gen/c"],
1427				}
1428			`),
1429		},
1430	}
1431
1432	testPathForModuleSrc(t, tests)
1433}
1434
1435func TestPathsForModuleSrc_AllowMissingDependencies(t *testing.T) {
1436	bp := `
1437		test {
1438			name: "foo",
1439			srcs: [":a"],
1440			exclude_srcs: [":b"],
1441			src: ":c",
1442		}
1443
1444		test {
1445			name: "bar",
1446			srcs: [":d"],
1447			exclude_srcs: [":e"],
1448			module_handles_missing_deps: true,
1449		}
1450	`
1451
1452	result := GroupFixturePreparers(
1453		PrepareForTestWithAllowMissingDependencies,
1454		FixtureRegisterWithContext(func(ctx RegistrationContext) {
1455			ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
1456		}),
1457		FixtureWithRootAndroidBp(bp),
1458	).RunTest(t)
1459
1460	foo := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
1461
1462	AssertArrayString(t, "foo missing deps", []string{"a", "b", "c"}, foo.missingDeps)
1463	AssertArrayString(t, "foo srcs", []string{}, foo.srcs)
1464	AssertStringEquals(t, "foo src", "", foo.src)
1465
1466	bar := result.ModuleForTests("bar", "").Module().(*pathForModuleSrcTestModule)
1467
1468	AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps)
1469	AssertArrayString(t, "bar srcs", []string{}, bar.srcs)
1470}
1471
1472func TestPathRelativeToTop(t *testing.T) {
1473	testConfig := pathTestConfig("/tmp/build/top")
1474	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
1475
1476	ctx := &testModuleInstallPathContext{
1477		baseModuleContext: baseModuleContext{
1478			os:     deviceTarget.Os,
1479			target: deviceTarget,
1480		},
1481	}
1482	ctx.baseModuleContext.config = testConfig
1483
1484	t.Run("install for soong", func(t *testing.T) {
1485		p := PathForModuleInstall(ctx, "install/path")
1486		AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p)
1487	})
1488	t.Run("install for make", func(t *testing.T) {
1489		p := PathForModuleInstall(ctx, "install/path")
1490		p.makePath = true
1491		AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p)
1492	})
1493	t.Run("output", func(t *testing.T) {
1494		p := PathForOutput(ctx, "output/path")
1495		AssertPathRelativeToTopEquals(t, "output path", "out/soong/output/path", p)
1496	})
1497	t.Run("source", func(t *testing.T) {
1498		p := PathForSource(ctx, "source/path")
1499		AssertPathRelativeToTopEquals(t, "source path", "source/path", p)
1500	})
1501	t.Run("mixture", func(t *testing.T) {
1502		paths := Paths{
1503			PathForModuleInstall(ctx, "install/path"),
1504			PathForOutput(ctx, "output/path"),
1505			PathForSource(ctx, "source/path"),
1506		}
1507
1508		expected := []string{
1509			"out/soong/target/product/test_device/system/install/path",
1510			"out/soong/output/path",
1511			"source/path",
1512		}
1513		AssertPathsRelativeToTopEquals(t, "mixture", expected, paths)
1514	})
1515}
1516
1517func ExampleOutputPath_ReplaceExtension() {
1518	ctx := &configErrorWrapper{
1519		config: TestConfig("out", nil, "", nil),
1520	}
1521	p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art")
1522	p2 := p.ReplaceExtension(ctx, "oat")
1523	fmt.Println(p, p2)
1524	fmt.Println(p.Rel(), p2.Rel())
1525
1526	// Output:
1527	// out/soong/system/framework/boot.art out/soong/system/framework/boot.oat
1528	// boot.art boot.oat
1529}
1530
1531func ExampleOutputPath_InSameDir() {
1532	ctx := &configErrorWrapper{
1533		config: TestConfig("out", nil, "", nil),
1534	}
1535	p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art")
1536	p2 := p.InSameDir(ctx, "oat", "arm", "boot.vdex")
1537	fmt.Println(p, p2)
1538	fmt.Println(p.Rel(), p2.Rel())
1539
1540	// Output:
1541	// out/soong/system/framework/boot.art out/soong/system/framework/oat/arm/boot.vdex
1542	// boot.art oat/arm/boot.vdex
1543}
1544
1545func BenchmarkFirstUniquePaths(b *testing.B) {
1546	implementations := []struct {
1547		name string
1548		f    func(Paths) Paths
1549	}{
1550		{
1551			name: "list",
1552			f:    firstUniquePathsList,
1553		},
1554		{
1555			name: "map",
1556			f:    firstUniquePathsMap,
1557		},
1558	}
1559	const maxSize = 1024
1560	uniquePaths := make(Paths, maxSize)
1561	for i := range uniquePaths {
1562		uniquePaths[i] = PathForTesting(strconv.Itoa(i))
1563	}
1564	samePath := make(Paths, maxSize)
1565	for i := range samePath {
1566		samePath[i] = uniquePaths[0]
1567	}
1568
1569	f := func(b *testing.B, imp func(Paths) Paths, paths Paths) {
1570		for i := 0; i < b.N; i++ {
1571			b.ReportAllocs()
1572			paths = append(Paths(nil), paths...)
1573			imp(paths)
1574		}
1575	}
1576
1577	for n := 1; n <= maxSize; n <<= 1 {
1578		b.Run(strconv.Itoa(n), func(b *testing.B) {
1579			for _, implementation := range implementations {
1580				b.Run(implementation.name, func(b *testing.B) {
1581					b.Run("same", func(b *testing.B) {
1582						f(b, implementation.f, samePath[:n])
1583					})
1584					b.Run("unique", func(b *testing.B) {
1585						f(b, implementation.f, uniquePaths[:n])
1586					})
1587				})
1588			}
1589		})
1590	}
1591}
1592