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