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