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