xref: /aosp_15_r20/build/soong/ui/build/config_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2017 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 build
16
17import (
18	"bytes"
19	"context"
20	"fmt"
21	"io/ioutil"
22	"os"
23	"path/filepath"
24	"reflect"
25	"runtime"
26	"strings"
27	"testing"
28
29	"android/soong/ui/logger"
30	smpb "android/soong/ui/metrics/metrics_proto"
31	"android/soong/ui/status"
32
33	"google.golang.org/protobuf/encoding/prototext"
34
35	"google.golang.org/protobuf/proto"
36)
37
38func testContext() Context {
39	return Context{&ContextImpl{
40		Context: context.Background(),
41		Logger:  logger.New(&bytes.Buffer{}),
42		Writer:  &bytes.Buffer{},
43		Status:  &status.Status{},
44	}}
45}
46
47func TestConfigParseArgsJK(t *testing.T) {
48	ctx := testContext()
49
50	testCases := []struct {
51		args []string
52
53		parallel  int
54		keepGoing int
55		remaining []string
56	}{
57		{nil, -1, -1, nil},
58
59		{[]string{"-j"}, -1, -1, nil},
60		{[]string{"-j1"}, 1, -1, nil},
61		{[]string{"-j1234"}, 1234, -1, nil},
62
63		{[]string{"-j", "1"}, 1, -1, nil},
64		{[]string{"-j", "1234"}, 1234, -1, nil},
65		{[]string{"-j", "1234", "abc"}, 1234, -1, []string{"abc"}},
66		{[]string{"-j", "abc"}, -1, -1, []string{"abc"}},
67		{[]string{"-j", "1abc"}, -1, -1, []string{"1abc"}},
68
69		{[]string{"-k"}, -1, 0, nil},
70		{[]string{"-k0"}, -1, 0, nil},
71		{[]string{"-k1"}, -1, 1, nil},
72		{[]string{"-k1234"}, -1, 1234, nil},
73
74		{[]string{"-k", "0"}, -1, 0, nil},
75		{[]string{"-k", "1"}, -1, 1, nil},
76		{[]string{"-k", "1234"}, -1, 1234, nil},
77		{[]string{"-k", "1234", "abc"}, -1, 1234, []string{"abc"}},
78		{[]string{"-k", "abc"}, -1, 0, []string{"abc"}},
79		{[]string{"-k", "1abc"}, -1, 0, []string{"1abc"}},
80
81		// TODO: These are supported in Make, should we support them?
82		//{[]string{"-kj"}, -1, 0},
83		//{[]string{"-kj8"}, 8, 0},
84
85		// -jk is not valid in Make
86	}
87
88	for _, tc := range testCases {
89		t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
90			defer logger.Recover(func(err error) {
91				t.Fatal(err)
92			})
93
94			env := Environment([]string{})
95			c := &configImpl{
96				environ:   &env,
97				parallel:  -1,
98				keepGoing: -1,
99			}
100			c.parseArgs(ctx, tc.args)
101
102			if c.parallel != tc.parallel {
103				t.Errorf("for %q, parallel:\nwant: %d\n got: %d\n",
104					strings.Join(tc.args, " "),
105					tc.parallel, c.parallel)
106			}
107			if c.keepGoing != tc.keepGoing {
108				t.Errorf("for %q, keep going:\nwant: %d\n got: %d\n",
109					strings.Join(tc.args, " "),
110					tc.keepGoing, c.keepGoing)
111			}
112			if !reflect.DeepEqual(c.arguments, tc.remaining) {
113				t.Errorf("for %q, remaining arguments:\nwant: %q\n got: %q\n",
114					strings.Join(tc.args, " "),
115					tc.remaining, c.arguments)
116			}
117		})
118	}
119}
120
121func TestConfigParseArgsVars(t *testing.T) {
122	ctx := testContext()
123
124	testCases := []struct {
125		env  []string
126		args []string
127
128		expectedEnv []string
129		remaining   []string
130	}{
131		{},
132		{
133			env: []string{"A=bc"},
134
135			expectedEnv: []string{"A=bc"},
136		},
137		{
138			args: []string{"abc"},
139
140			remaining: []string{"abc"},
141		},
142
143		{
144			args: []string{"A=bc"},
145
146			expectedEnv: []string{"A=bc"},
147		},
148		{
149			env:  []string{"A=a"},
150			args: []string{"A=bc"},
151
152			expectedEnv: []string{"A=bc"},
153		},
154
155		{
156			env:  []string{"A=a"},
157			args: []string{"A=", "=b"},
158
159			expectedEnv: []string{"A="},
160			remaining:   []string{"=b"},
161		},
162	}
163
164	for _, tc := range testCases {
165		t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
166			defer logger.Recover(func(err error) {
167				t.Fatal(err)
168			})
169
170			e := Environment(tc.env)
171			c := &configImpl{
172				environ: &e,
173			}
174			c.parseArgs(ctx, tc.args)
175
176			if !reflect.DeepEqual([]string(*c.environ), tc.expectedEnv) {
177				t.Errorf("for env=%q args=%q, environment:\nwant: %q\n got: %q\n",
178					tc.env, tc.args,
179					tc.expectedEnv, []string(*c.environ))
180			}
181			if !reflect.DeepEqual(c.arguments, tc.remaining) {
182				t.Errorf("for env=%q args=%q, remaining arguments:\nwant: %q\n got: %q\n",
183					tc.env, tc.args,
184					tc.remaining, c.arguments)
185			}
186		})
187	}
188}
189
190func TestConfigCheckTopDir(t *testing.T) {
191	ctx := testContext()
192	buildRootDir := filepath.Dir(srcDirFileCheck)
193	expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
194
195	tests := []struct {
196		// ********* Setup *********
197		// Test description.
198		description string
199
200		// ********* Action *********
201		// If set to true, the build root file is created.
202		rootBuildFile bool
203
204		// The current path where Soong is being executed.
205		path string
206
207		// ********* Validation *********
208		// Expecting error and validate the error string against expectedErrStr.
209		wantErr bool
210	}{{
211		description:   "current directory is the root source tree",
212		rootBuildFile: true,
213		path:          ".",
214		wantErr:       false,
215	}, {
216		description:   "one level deep in the source tree",
217		rootBuildFile: true,
218		path:          "1",
219		wantErr:       true,
220	}, {
221		description:   "very deep in the source tree",
222		rootBuildFile: true,
223		path:          "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7",
224		wantErr:       true,
225	}, {
226		description:   "outside of source tree",
227		rootBuildFile: false,
228		path:          "1/2/3/4/5",
229		wantErr:       true,
230	}}
231
232	for _, tt := range tests {
233		t.Run(tt.description, func(t *testing.T) {
234			defer logger.Recover(func(err error) {
235				if !tt.wantErr {
236					t.Fatalf("Got unexpected error: %v", err)
237				}
238				if expectedErrStr != err.Error() {
239					t.Fatalf("expected %s, got %s", expectedErrStr, err.Error())
240				}
241			})
242
243			// Create the root source tree.
244			rootDir, err := ioutil.TempDir("", "")
245			if err != nil {
246				t.Fatal(err)
247			}
248			defer os.RemoveAll(rootDir)
249
250			// Create the build root file. This is to test if topDir returns an error if the build root
251			// file does not exist.
252			if tt.rootBuildFile {
253				dir := filepath.Join(rootDir, buildRootDir)
254				if err := os.MkdirAll(dir, 0755); err != nil {
255					t.Errorf("failed to create %s directory: %v", dir, err)
256				}
257				f := filepath.Join(rootDir, srcDirFileCheck)
258				if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil {
259					t.Errorf("failed to create file %s: %v", f, err)
260				}
261			}
262
263			// Next block of code is to set the current directory.
264			dir := rootDir
265			if tt.path != "" {
266				dir = filepath.Join(dir, tt.path)
267				if err := os.MkdirAll(dir, 0755); err != nil {
268					t.Errorf("failed to create %s directory: %v", dir, err)
269				}
270			}
271			curDir, err := os.Getwd()
272			if err != nil {
273				t.Fatalf("failed to get the current directory: %v", err)
274			}
275			defer func() { os.Chdir(curDir) }()
276
277			if err := os.Chdir(dir); err != nil {
278				t.Fatalf("failed to change directory to %s: %v", dir, err)
279			}
280
281			checkTopDir(ctx)
282		})
283	}
284}
285
286func TestConfigConvertToTarget(t *testing.T) {
287	tests := []struct {
288		// ********* Setup *********
289		// Test description.
290		description string
291
292		// ********* Action *********
293		// The current directory where Soong is being executed.
294		dir string
295
296		// The current prefix string to be pre-appended to the target.
297		prefix string
298
299		// ********* Validation *********
300		// The expected target to be invoked in ninja.
301		expectedTarget string
302	}{{
303		description:    "one level directory in source tree",
304		dir:            "test1",
305		prefix:         "MODULES-IN-",
306		expectedTarget: "MODULES-IN-test1",
307	}, {
308		description:    "multiple level directories in source tree",
309		dir:            "test1/test2/test3/test4",
310		prefix:         "GET-INSTALL-PATH-IN-",
311		expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4",
312	}}
313	for _, tt := range tests {
314		t.Run(tt.description, func(t *testing.T) {
315			target := convertToTarget(tt.dir, tt.prefix)
316			if target != tt.expectedTarget {
317				t.Errorf("expected %s, got %s for target", tt.expectedTarget, target)
318			}
319		})
320	}
321}
322
323func setTop(t *testing.T, dir string) func() {
324	curDir, err := os.Getwd()
325	if err != nil {
326		t.Fatalf("failed to get current directory: %v", err)
327	}
328	if err := os.Chdir(dir); err != nil {
329		t.Fatalf("failed to change directory to top dir %s: %v", dir, err)
330	}
331	return func() { os.Chdir(curDir) }
332}
333
334func createBuildFiles(t *testing.T, topDir string, buildFiles []string) {
335	for _, buildFile := range buildFiles {
336		buildFile = filepath.Join(topDir, buildFile)
337		if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil {
338			t.Errorf("failed to create file %s: %v", buildFile, err)
339		}
340	}
341}
342
343func createDirectories(t *testing.T, topDir string, dirs []string) {
344	for _, dir := range dirs {
345		dir = filepath.Join(topDir, dir)
346		if err := os.MkdirAll(dir, 0755); err != nil {
347			t.Errorf("failed to create %s directory: %v", dir, err)
348		}
349	}
350}
351
352func TestConfigGetTargets(t *testing.T) {
353	ctx := testContext()
354	tests := []struct {
355		// ********* Setup *********
356		// Test description.
357		description string
358
359		// Directories that exist in the source tree.
360		dirsInTrees []string
361
362		// Build files that exists in the source tree.
363		buildFiles []string
364
365		// ********* Action *********
366		// Directories passed in to soong_ui.
367		dirs []string
368
369		// Current directory that the user executed the build action command.
370		curDir string
371
372		// ********* Validation *********
373		// Expected targets from the function.
374		expectedTargets []string
375
376		// Expecting error from running test case.
377		errStr string
378	}{{
379		description:     "one target dir specified",
380		dirsInTrees:     []string{"0/1/2/3"},
381		buildFiles:      []string{"0/1/2/3/Android.bp"},
382		dirs:            []string{"1/2/3"},
383		curDir:          "0",
384		expectedTargets: []string{"MODULES-IN-0-1-2-3"},
385	}, {
386		description: "one target dir specified, build file does not exist",
387		dirsInTrees: []string{"0/1/2/3"},
388		buildFiles:  []string{},
389		dirs:        []string{"1/2/3"},
390		curDir:      "0",
391		errStr:      "Build file not found for 0/1/2/3 directory",
392	}, {
393		description: "one target dir specified, invalid targets specified",
394		dirsInTrees: []string{"0/1/2/3"},
395		buildFiles:  []string{},
396		dirs:        []string{"1/2/3:t1:t2"},
397		curDir:      "0",
398		errStr:      "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)",
399	}, {
400		description:     "one target dir specified, no targets specified but has colon",
401		dirsInTrees:     []string{"0/1/2/3"},
402		buildFiles:      []string{"0/1/2/3/Android.bp"},
403		dirs:            []string{"1/2/3:"},
404		curDir:          "0",
405		expectedTargets: []string{"MODULES-IN-0-1-2-3"},
406	}, {
407		description:     "one target dir specified, two targets specified",
408		dirsInTrees:     []string{"0/1/2/3"},
409		buildFiles:      []string{"0/1/2/3/Android.bp"},
410		dirs:            []string{"1/2/3:t1,t2"},
411		curDir:          "0",
412		expectedTargets: []string{"t1", "t2"},
413	}, {
414		description: "one target dir specified, no targets and has a comma",
415		dirsInTrees: []string{"0/1/2/3"},
416		buildFiles:  []string{"0/1/2/3/Android.bp"},
417		dirs:        []string{"1/2/3:,"},
418		curDir:      "0",
419		errStr:      "0/1/2/3 not in proper directory:target1,target2,... format",
420	}, {
421		description: "one target dir specified, improper targets defined",
422		dirsInTrees: []string{"0/1/2/3"},
423		buildFiles:  []string{"0/1/2/3/Android.bp"},
424		dirs:        []string{"1/2/3:,t1"},
425		curDir:      "0",
426		errStr:      "0/1/2/3 not in proper directory:target1,target2,... format",
427	}, {
428		description: "one target dir specified, blank target",
429		dirsInTrees: []string{"0/1/2/3"},
430		buildFiles:  []string{"0/1/2/3/Android.bp"},
431		dirs:        []string{"1/2/3:t1,"},
432		curDir:      "0",
433		errStr:      "0/1/2/3 not in proper directory:target1,target2,... format",
434	}, {
435		description:     "one target dir specified, many targets specified",
436		dirsInTrees:     []string{"0/1/2/3"},
437		buildFiles:      []string{"0/1/2/3/Android.bp"},
438		dirs:            []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"},
439		curDir:          "0",
440		expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"},
441	}, {
442		description: "one target dir specified, one target specified, build file does not exist",
443		dirsInTrees: []string{"0/1/2/3"},
444		buildFiles:  []string{},
445		dirs:        []string{"1/2/3:t1"},
446		curDir:      "0",
447		errStr:      "Couldn't locate a build file from 0/1/2/3 directory",
448	}, {
449		description: "one target dir specified, one target specified, build file not in target dir",
450		dirsInTrees: []string{"0/1/2/3"},
451		buildFiles:  []string{"0/1/2/Android.mk"},
452		dirs:        []string{"1/2/3:t1"},
453		curDir:      "0",
454		errStr:      "Couldn't locate a build file from 0/1/2/3 directory",
455	}, {
456		description:     "one target dir specified, build file not in target dir",
457		dirsInTrees:     []string{"0/1/2/3"},
458		buildFiles:      []string{"0/1/2/Android.mk"},
459		dirs:            []string{"1/2/3"},
460		curDir:          "0",
461		expectedTargets: []string{"MODULES-IN-0-1-2"},
462	}, {
463		description:     "multiple targets dir specified, targets specified",
464		dirsInTrees:     []string{"0/1/2/3", "0/3/4"},
465		buildFiles:      []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
466		dirs:            []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"},
467		curDir:          "0",
468		expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"},
469	}, {
470		description:     "multiple targets dir specified, one directory has targets specified",
471		dirsInTrees:     []string{"0/1/2/3", "0/3/4"},
472		buildFiles:      []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
473		dirs:            []string{"1/2/3:t1,t2", "3/4"},
474		curDir:          "0",
475		expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"},
476	}, {
477		description: "two dirs specified, only one dir exist",
478		dirsInTrees: []string{"0/1/2/3"},
479		buildFiles:  []string{"0/1/2/3/Android.mk"},
480		dirs:        []string{"1/2/3:t1", "3/4"},
481		curDir:      "0",
482		errStr:      "couldn't find directory 0/3/4",
483	}, {
484		description:     "multiple targets dirs specified at root source tree",
485		dirsInTrees:     []string{"0/1/2/3", "0/3/4"},
486		buildFiles:      []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
487		dirs:            []string{"0/1/2/3:t1,t2", "0/3/4"},
488		curDir:          ".",
489		expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"},
490	}, {
491		description: "no directories specified",
492		dirsInTrees: []string{"0/1/2/3", "0/3/4"},
493		buildFiles:  []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
494		dirs:        []string{},
495		curDir:      ".",
496	}}
497	for _, tt := range tests {
498		t.Run(tt.description, func(t *testing.T) {
499			defer logger.Recover(func(err error) {
500				if tt.errStr == "" {
501					t.Fatalf("Got unexpected error: %v", err)
502				}
503				if tt.errStr != err.Error() {
504					t.Errorf("expected %s, got %s", tt.errStr, err.Error())
505				}
506			})
507
508			// Create the root source tree.
509			topDir, err := ioutil.TempDir("", "")
510			if err != nil {
511				t.Fatalf("failed to create temp dir: %v", err)
512			}
513			defer os.RemoveAll(topDir)
514
515			createDirectories(t, topDir, tt.dirsInTrees)
516			createBuildFiles(t, topDir, tt.buildFiles)
517			r := setTop(t, topDir)
518			defer r()
519
520			targets := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-")
521			if !reflect.DeepEqual(targets, tt.expectedTargets) {
522				t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets)
523			}
524
525			// If the execution reached here and there was an expected error code, the unit test case failed.
526			if tt.errStr != "" {
527				t.Errorf("expecting error %s", tt.errStr)
528			}
529		})
530	}
531}
532
533func TestConfigFindBuildFile(t *testing.T) {
534	ctx := testContext()
535
536	tests := []struct {
537		// ********* Setup *********
538		// Test description.
539		description string
540
541		// Array of build files to create in dir.
542		buildFiles []string
543
544		// Directories that exist in the source tree.
545		dirsInTrees []string
546
547		// ********* Action *********
548		// The base directory is where findBuildFile is invoked.
549		dir string
550
551		// ********* Validation *********
552		// Expected build file path to find.
553		expectedBuildFile string
554	}{{
555		description:       "build file exists at leaf directory",
556		buildFiles:        []string{"1/2/3/Android.bp"},
557		dirsInTrees:       []string{"1/2/3"},
558		dir:               "1/2/3",
559		expectedBuildFile: "1/2/3/Android.mk",
560	}, {
561		description:       "build file exists in all directory paths",
562		buildFiles:        []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"},
563		dirsInTrees:       []string{"1/2/3"},
564		dir:               "1/2/3",
565		expectedBuildFile: "1/2/3/Android.mk",
566	}, {
567		description:       "build file does not exist in all directory paths",
568		buildFiles:        []string{},
569		dirsInTrees:       []string{"1/2/3"},
570		dir:               "1/2/3",
571		expectedBuildFile: "",
572	}, {
573		description:       "build file exists only at top directory",
574		buildFiles:        []string{"Android.bp"},
575		dirsInTrees:       []string{"1/2/3"},
576		dir:               "1/2/3",
577		expectedBuildFile: "",
578	}, {
579		description:       "build file exist in a subdirectory",
580		buildFiles:        []string{"1/2/Android.bp"},
581		dirsInTrees:       []string{"1/2/3"},
582		dir:               "1/2/3",
583		expectedBuildFile: "1/2/Android.mk",
584	}, {
585		description:       "build file exists in a subdirectory",
586		buildFiles:        []string{"1/Android.mk"},
587		dirsInTrees:       []string{"1/2/3"},
588		dir:               "1/2/3",
589		expectedBuildFile: "1/Android.mk",
590	}, {
591		description:       "top directory",
592		buildFiles:        []string{"Android.bp"},
593		dirsInTrees:       []string{},
594		dir:               ".",
595		expectedBuildFile: "",
596	}, {
597		description:       "build file exists in subdirectory",
598		buildFiles:        []string{"1/2/3/Android.bp", "1/2/4/Android.bp"},
599		dirsInTrees:       []string{"1/2/3", "1/2/4"},
600		dir:               "1/2",
601		expectedBuildFile: "1/2/Android.mk",
602	}, {
603		description:       "build file exists in parent subdirectory",
604		buildFiles:        []string{"1/5/Android.bp"},
605		dirsInTrees:       []string{"1/2/3", "1/2/4", "1/5"},
606		dir:               "1/2",
607		expectedBuildFile: "1/Android.mk",
608	}, {
609		description:       "build file exists in deep parent's subdirectory.",
610		buildFiles:        []string{"1/5/6/Android.bp"},
611		dirsInTrees:       []string{"1/2/3", "1/2/4", "1/5/6", "1/5/7"},
612		dir:               "1/2",
613		expectedBuildFile: "1/Android.mk",
614	}}
615
616	for _, tt := range tests {
617		t.Run(tt.description, func(t *testing.T) {
618			defer logger.Recover(func(err error) {
619				t.Fatalf("Got unexpected error: %v", err)
620			})
621
622			topDir, err := ioutil.TempDir("", "")
623			if err != nil {
624				t.Fatalf("failed to create temp dir: %v", err)
625			}
626			defer os.RemoveAll(topDir)
627
628			createDirectories(t, topDir, tt.dirsInTrees)
629			createBuildFiles(t, topDir, tt.buildFiles)
630
631			curDir, err := os.Getwd()
632			if err != nil {
633				t.Fatalf("Could not get working directory: %v", err)
634			}
635			defer func() { os.Chdir(curDir) }()
636			if err := os.Chdir(topDir); err != nil {
637				t.Fatalf("Could not change top dir to %s: %v", topDir, err)
638			}
639
640			buildFile := findBuildFile(ctx, tt.dir)
641			if buildFile != tt.expectedBuildFile {
642				t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile)
643			}
644		})
645	}
646}
647
648func TestConfigSplitArgs(t *testing.T) {
649	tests := []struct {
650		// ********* Setup *********
651		// Test description.
652		description string
653
654		// ********* Action *********
655		// Arguments passed in to soong_ui.
656		args []string
657
658		// ********* Validation *********
659		// Expected newArgs list after extracting the directories.
660		expectedNewArgs []string
661
662		// Expected directories
663		expectedDirs []string
664	}{{
665		description:     "flags but no directories specified",
666		args:            []string{"showcommands", "-j", "-k"},
667		expectedNewArgs: []string{"showcommands", "-j", "-k"},
668		expectedDirs:    []string{},
669	}, {
670		description:     "flags and one directory specified",
671		args:            []string{"snod", "-j", "dir:target1,target2"},
672		expectedNewArgs: []string{"snod", "-j"},
673		expectedDirs:    []string{"dir:target1,target2"},
674	}, {
675		description:     "flags and directories specified",
676		args:            []string{"dist", "-k", "dir1", "dir2:target1,target2"},
677		expectedNewArgs: []string{"dist", "-k"},
678		expectedDirs:    []string{"dir1", "dir2:target1,target2"},
679	}, {
680		description:     "only directories specified",
681		args:            []string{"dir1", "dir2", "dir3:target1,target2"},
682		expectedNewArgs: []string{},
683		expectedDirs:    []string{"dir1", "dir2", "dir3:target1,target2"},
684	}}
685	for _, tt := range tests {
686		t.Run(tt.description, func(t *testing.T) {
687			args, dirs := splitArgs(tt.args)
688			if !reflect.DeepEqual(tt.expectedNewArgs, args) {
689				t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args)
690			}
691			if !reflect.DeepEqual(tt.expectedDirs, dirs) {
692				t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs)
693			}
694		})
695	}
696}
697
698type envVar struct {
699	name  string
700	value string
701}
702
703type buildActionTestCase struct {
704	// ********* Setup *********
705	// Test description.
706	description string
707
708	// Directories that exist in the source tree.
709	dirsInTrees []string
710
711	// Build files that exists in the source tree.
712	buildFiles []string
713
714	// Create root symlink that points to topDir.
715	rootSymlink bool
716
717	// ********* Action *********
718	// Arguments passed in to soong_ui.
719	args []string
720
721	// Directory where the build action was invoked.
722	curDir string
723
724	// WITH_TIDY_ONLY environment variable specified.
725	tidyOnly string
726
727	// ********* Validation *********
728	// Expected arguments to be in Config instance.
729	expectedArgs []string
730
731	// Expecting error from running test case.
732	expectedErrStr string
733}
734
735func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction) {
736	ctx := testContext()
737
738	defer logger.Recover(func(err error) {
739		if tt.expectedErrStr == "" {
740			t.Fatalf("Got unexpected error: %v", err)
741		}
742		if tt.expectedErrStr != err.Error() {
743			t.Errorf("expected %s, got %s", tt.expectedErrStr, err.Error())
744		}
745	})
746
747	// Environment variables to set it to blank on every test case run.
748	resetEnvVars := []string{
749		"WITH_TIDY_ONLY",
750	}
751
752	for _, name := range resetEnvVars {
753		if err := os.Unsetenv(name); err != nil {
754			t.Fatalf("failed to unset environment variable %s: %v", name, err)
755		}
756	}
757	if tt.tidyOnly != "" {
758		if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil {
759			t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err)
760		}
761	}
762
763	// Create the root source tree.
764	topDir, err := ioutil.TempDir("", "")
765	if err != nil {
766		t.Fatalf("failed to create temp dir: %v", err)
767	}
768	defer os.RemoveAll(topDir)
769
770	createDirectories(t, topDir, tt.dirsInTrees)
771	createBuildFiles(t, topDir, tt.buildFiles)
772
773	if tt.rootSymlink {
774		// Create a secondary root source tree which points to the true root source tree.
775		symlinkTopDir, err := ioutil.TempDir("", "")
776		if err != nil {
777			t.Fatalf("failed to create symlink temp dir: %v", err)
778		}
779		defer os.RemoveAll(symlinkTopDir)
780
781		symlinkTopDir = filepath.Join(symlinkTopDir, "root")
782		err = os.Symlink(topDir, symlinkTopDir)
783		if err != nil {
784			t.Fatalf("failed to create symlink: %v", err)
785		}
786		topDir = symlinkTopDir
787	}
788
789	r := setTop(t, topDir)
790	defer r()
791
792	// The next block is to create the root build file.
793	rootBuildFileDir := filepath.Dir(srcDirFileCheck)
794	if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil {
795		t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err)
796	}
797
798	if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil {
799		t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err)
800	}
801
802	args := getConfigArgs(action, tt.curDir, ctx, tt.args)
803	if !reflect.DeepEqual(tt.expectedArgs, args) {
804		t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args)
805	}
806
807	// If the execution reached here and there was an expected error code, the unit test case failed.
808	if tt.expectedErrStr != "" {
809		t.Errorf("expecting error %s", tt.expectedErrStr)
810	}
811}
812
813func TestGetConfigArgsBuildModules(t *testing.T) {
814	tests := []buildActionTestCase{{
815		description:  "normal execution from the root source tree directory",
816		dirsInTrees:  []string{"0/1/2", "0/2", "0/3"},
817		buildFiles:   []string{"0/1/2/Android.mk", "0/2/Android.bp", "0/3/Android.mk"},
818		args:         []string{"-j", "fake_module", "fake_module2"},
819		curDir:       ".",
820		tidyOnly:     "",
821		expectedArgs: []string{"-j", "fake_module", "fake_module2"},
822	}, {
823		description:  "normal execution in deep directory",
824		dirsInTrees:  []string{"0/1/2", "0/2", "0/3", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6"},
825		buildFiles:   []string{"0/1/2/Android.mk", "0/2/Android.bp", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/Android.mk"},
826		args:         []string{"-j", "fake_module", "fake_module2", "-k"},
827		curDir:       "1/2/3/4/5/6/7/8/9",
828		tidyOnly:     "",
829		expectedArgs: []string{"-j", "fake_module", "fake_module2", "-k"},
830	}, {
831		description:  "normal execution in deep directory, no targets",
832		dirsInTrees:  []string{"0/1/2", "0/2", "0/3", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6"},
833		buildFiles:   []string{"0/1/2/Android.mk", "0/2/Android.bp", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/Android.mk"},
834		args:         []string{"-j", "-k"},
835		curDir:       "1/2/3/4/5/6/7/8/9",
836		tidyOnly:     "",
837		expectedArgs: []string{"-j", "-k"},
838	}, {
839		description:  "normal execution in root source tree, no args",
840		dirsInTrees:  []string{"0/1/2", "0/2", "0/3"},
841		buildFiles:   []string{"0/1/2/Android.mk", "0/2/Android.bp"},
842		args:         []string{},
843		curDir:       "0/2",
844		tidyOnly:     "",
845		expectedArgs: []string{},
846	}, {
847		description:  "normal execution in symlink root source tree, no args",
848		dirsInTrees:  []string{"0/1/2", "0/2", "0/3"},
849		buildFiles:   []string{"0/1/2/Android.mk", "0/2/Android.bp"},
850		rootSymlink:  true,
851		args:         []string{},
852		curDir:       "0/2",
853		tidyOnly:     "",
854		expectedArgs: []string{},
855	}}
856	for _, tt := range tests {
857		t.Run("build action BUILD_MODULES with dependencies, "+tt.description, func(t *testing.T) {
858			testGetConfigArgs(t, tt, BUILD_MODULES)
859		})
860	}
861}
862
863func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) {
864	tests := []buildActionTestCase{
865		{
866			description:  "normal execution in a directory",
867			dirsInTrees:  []string{"0/1/2"},
868			buildFiles:   []string{"0/1/2/Android.mk"},
869			args:         []string{"fake-module"},
870			curDir:       "0/1/2",
871			tidyOnly:     "",
872			expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"},
873		}, {
874			description:  "build file in parent directory",
875			dirsInTrees:  []string{"0/1/2"},
876			buildFiles:   []string{"0/1/Android.mk"},
877			args:         []string{},
878			curDir:       "0/1/2",
879			tidyOnly:     "",
880			expectedArgs: []string{"MODULES-IN-0-1"},
881		},
882		{
883			description:  "build file in parent directory, multiple module names passed in",
884			dirsInTrees:  []string{"0/1/2"},
885			buildFiles:   []string{"0/1/Android.mk"},
886			args:         []string{"fake-module1", "fake-module2", "fake-module3"},
887			curDir:       "0/1/2",
888			tidyOnly:     "",
889			expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"},
890		}, {
891			description:  "build file in 2nd level parent directory",
892			dirsInTrees:  []string{"0/1/2"},
893			buildFiles:   []string{"0/Android.bp"},
894			args:         []string{},
895			curDir:       "0/1/2",
896			tidyOnly:     "",
897			expectedArgs: []string{"MODULES-IN-0"},
898		}, {
899			description:  "build action executed at root directory",
900			dirsInTrees:  []string{},
901			buildFiles:   []string{},
902			rootSymlink:  false,
903			args:         []string{},
904			curDir:       ".",
905			tidyOnly:     "",
906			expectedArgs: []string{},
907		}, {
908			description:  "build action executed at root directory in symlink",
909			dirsInTrees:  []string{},
910			buildFiles:   []string{},
911			rootSymlink:  true,
912			args:         []string{},
913			curDir:       ".",
914			tidyOnly:     "",
915			expectedArgs: []string{},
916		}, {
917			description:    "build file not found",
918			dirsInTrees:    []string{"0/1/2"},
919			buildFiles:     []string{},
920			args:           []string{},
921			curDir:         "0/1/2",
922			tidyOnly:       "",
923			expectedArgs:   []string{"MODULES-IN-0-1-2"},
924			expectedErrStr: "Build file not found for 0/1/2 directory",
925		}, {
926			description:  "GET-INSTALL-PATH specified,",
927			dirsInTrees:  []string{"0/1/2"},
928			buildFiles:   []string{"0/1/Android.mk"},
929			args:         []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"},
930			curDir:       "0/1/2",
931			tidyOnly:     "",
932			expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"},
933		}, {
934			description:  "tidy only environment variable specified,",
935			dirsInTrees:  []string{"0/1/2"},
936			buildFiles:   []string{"0/1/Android.mk"},
937			args:         []string{"GET-INSTALL-PATH"},
938			curDir:       "0/1/2",
939			tidyOnly:     "true",
940			expectedArgs: []string{"tidy_only"},
941		}, {
942			description:  "normal execution in root directory with args",
943			dirsInTrees:  []string{},
944			buildFiles:   []string{},
945			args:         []string{"-j", "-k", "fake_module"},
946			curDir:       "",
947			tidyOnly:     "",
948			expectedArgs: []string{"-j", "-k", "fake_module"},
949		},
950	}
951	for _, tt := range tests {
952		t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) {
953			testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY)
954		})
955	}
956}
957
958func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) {
959	tests := []buildActionTestCase{{
960		description:  "normal execution in a directory",
961		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
962		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
963		args:         []string{"3.1/", "3.2/", "3.3/"},
964		curDir:       "0/1/2",
965		tidyOnly:     "",
966		expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"},
967	}, {
968		description:  "GET-INSTALL-PATH specified",
969		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"},
970		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"},
971		args:         []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"},
972		curDir:       "0/1",
973		tidyOnly:     "",
974		expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "GET-INSTALL-PATH-IN-0-1"},
975	}, {
976		description:  "tidy only environment variable specified",
977		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
978		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
979		args:         []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"},
980		curDir:       "0/1/2",
981		tidyOnly:     "1",
982		expectedArgs: []string{"tidy_only"},
983	}, {
984		description:  "normal execution from top dir directory",
985		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
986		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"},
987		rootSymlink:  false,
988		args:         []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
989		curDir:       ".",
990		tidyOnly:     "",
991		expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"},
992	}, {
993		description:  "normal execution from top dir directory in symlink",
994		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
995		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"},
996		rootSymlink:  true,
997		args:         []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
998		curDir:       ".",
999		tidyOnly:     "",
1000		expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"},
1001	}}
1002	for _, tt := range tests {
1003		t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) {
1004			testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES)
1005		})
1006	}
1007}
1008
1009func TestBuildConfig(t *testing.T) {
1010	tests := []struct {
1011		name                string
1012		environ             Environment
1013		arguments           []string
1014		expectedBuildConfig *smpb.BuildConfig
1015	}{
1016		{
1017			name:    "none set",
1018			environ: Environment{},
1019			expectedBuildConfig: &smpb.BuildConfig{
1020				ForceUseGoma:          proto.Bool(false),
1021				UseGoma:               proto.Bool(false),
1022				UseRbe:                proto.Bool(false),
1023				NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
1024			},
1025		},
1026		{
1027			name:    "force use goma",
1028			environ: Environment{"FORCE_USE_GOMA=1"},
1029			expectedBuildConfig: &smpb.BuildConfig{
1030				ForceUseGoma:          proto.Bool(true),
1031				UseGoma:               proto.Bool(false),
1032				UseRbe:                proto.Bool(false),
1033				NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
1034			},
1035		},
1036		{
1037			name:    "use goma",
1038			environ: Environment{"USE_GOMA=1"},
1039			expectedBuildConfig: &smpb.BuildConfig{
1040				ForceUseGoma:          proto.Bool(false),
1041				UseGoma:               proto.Bool(true),
1042				UseRbe:                proto.Bool(false),
1043				NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
1044			},
1045		},
1046		{
1047			// RBE is only supported on linux.
1048			name:    "use rbe",
1049			environ: Environment{"USE_RBE=1"},
1050			expectedBuildConfig: &smpb.BuildConfig{
1051				ForceUseGoma:          proto.Bool(false),
1052				UseGoma:               proto.Bool(false),
1053				UseRbe:                proto.Bool(runtime.GOOS == "linux"),
1054				NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
1055			},
1056		},
1057	}
1058
1059	for _, tc := range tests {
1060		t.Run(tc.name, func(t *testing.T) {
1061			c := &configImpl{
1062				environ:   &tc.environ,
1063				arguments: tc.arguments,
1064			}
1065			config := Config{c}
1066			actualBuildConfig := buildConfig(config)
1067			if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) {
1068				t.Errorf("Build config mismatch.\n"+
1069					"Expected build config: %#v\n"+
1070					"Actual build config: %#v", prototext.Format(expected), prototext.Format(actualBuildConfig))
1071			}
1072		})
1073	}
1074}
1075
1076func TestGetMetricsUploaderApp(t *testing.T) {
1077
1078	metricsUploaderDir := "metrics_uploader_dir"
1079	metricsUploaderBinary := "metrics_uploader_binary"
1080	metricsUploaderPath := filepath.Join(metricsUploaderDir, metricsUploaderBinary)
1081	tests := []struct {
1082		description string
1083		environ     Environment
1084		createFiles bool
1085		expected    string
1086	}{{
1087		description: "Uploader binary exist",
1088		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
1089		createFiles: true,
1090		expected:    metricsUploaderPath,
1091	}, {
1092		description: "Uploader binary not exist",
1093		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
1094		createFiles: false,
1095		expected:    "",
1096	}, {
1097		description: "Uploader binary variable not set",
1098		createFiles: true,
1099		expected:    "",
1100	}}
1101
1102	for _, tt := range tests {
1103		t.Run(tt.description, func(t *testing.T) {
1104			defer logger.Recover(func(err error) {
1105				t.Fatalf("got unexpected error: %v", err)
1106			})
1107
1108			// Create the root source tree.
1109			topDir, err := ioutil.TempDir("", "")
1110			if err != nil {
1111				t.Fatalf("failed to create temp dir: %v", err)
1112			}
1113			defer os.RemoveAll(topDir)
1114
1115			expected := tt.expected
1116			if len(expected) > 0 {
1117				expected = filepath.Join(topDir, expected)
1118			}
1119
1120			if tt.createFiles {
1121				if err := os.MkdirAll(filepath.Join(topDir, metricsUploaderDir), 0755); err != nil {
1122					t.Errorf("failed to create %s directory: %v", metricsUploaderDir, err)
1123				}
1124				if err := ioutil.WriteFile(filepath.Join(topDir, metricsUploaderPath), []byte{}, 0644); err != nil {
1125					t.Errorf("failed to create file %s: %v", expected, err)
1126				}
1127			}
1128
1129			actual := GetMetricsUploader(topDir, &tt.environ)
1130
1131			if actual != expected {
1132				t.Errorf("expecting: %s, actual: %s", expected, actual)
1133			}
1134		})
1135	}
1136}
1137