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