• 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/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		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:  "multitree build action executed at root directory",
907			dirsInTrees:  []string{},
908			buildFiles:   []string{},
909			rootSymlink:  false,
910			args:         []string{"--multitree-build"},
911			curDir:       ".",
912			tidyOnly:     "",
913			expectedArgs: []string{"--multitree-build"},
914		}, {
915			description:  "build action executed at root directory in symlink",
916			dirsInTrees:  []string{},
917			buildFiles:   []string{},
918			rootSymlink:  true,
919			args:         []string{},
920			curDir:       ".",
921			tidyOnly:     "",
922			expectedArgs: []string{},
923		}, {
924			description:    "build file not found",
925			dirsInTrees:    []string{"0/1/2"},
926			buildFiles:     []string{},
927			args:           []string{},
928			curDir:         "0/1/2",
929			tidyOnly:       "",
930			expectedArgs:   []string{"MODULES-IN-0-1-2"},
931			expectedErrStr: "Build file not found for 0/1/2 directory",
932		}, {
933			description:  "GET-INSTALL-PATH specified,",
934			dirsInTrees:  []string{"0/1/2"},
935			buildFiles:   []string{"0/1/Android.mk"},
936			args:         []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"},
937			curDir:       "0/1/2",
938			tidyOnly:     "",
939			expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"},
940		}, {
941			description:  "tidy only environment variable specified,",
942			dirsInTrees:  []string{"0/1/2"},
943			buildFiles:   []string{"0/1/Android.mk"},
944			args:         []string{"GET-INSTALL-PATH"},
945			curDir:       "0/1/2",
946			tidyOnly:     "true",
947			expectedArgs: []string{"tidy_only"},
948		}, {
949			description:  "normal execution in root directory with args",
950			dirsInTrees:  []string{},
951			buildFiles:   []string{},
952			args:         []string{"-j", "-k", "fake_module"},
953			curDir:       "",
954			tidyOnly:     "",
955			expectedArgs: []string{"-j", "-k", "fake_module"},
956		}}
957	for _, tt := range tests {
958		t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) {
959			testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY)
960		})
961	}
962}
963
964func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) {
965	tests := []buildActionTestCase{{
966		description:  "normal execution in a directory",
967		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
968		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
969		args:         []string{"3.1/", "3.2/", "3.3/"},
970		curDir:       "0/1/2",
971		tidyOnly:     "",
972		expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"},
973	}, {
974		description:  "GET-INSTALL-PATH specified",
975		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"},
976		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"},
977		args:         []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"},
978		curDir:       "0/1",
979		tidyOnly:     "",
980		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"},
981	}, {
982		description:  "tidy only environment variable specified",
983		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
984		buildFiles:   []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
985		args:         []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"},
986		curDir:       "0/1/2",
987		tidyOnly:     "1",
988		expectedArgs: []string{"tidy_only"},
989	}, {
990		description:  "normal execution from top dir directory",
991		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
992		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"},
993		rootSymlink:  false,
994		args:         []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
995		curDir:       ".",
996		tidyOnly:     "",
997		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"},
998	}, {
999		description:  "normal execution from top dir directory in symlink",
1000		dirsInTrees:  []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
1001		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"},
1002		rootSymlink:  true,
1003		args:         []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
1004		curDir:       ".",
1005		tidyOnly:     "",
1006		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"},
1007	}}
1008	for _, tt := range tests {
1009		t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) {
1010			testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES)
1011		})
1012	}
1013}
1014
1015func TestBuildConfig(t *testing.T) {
1016	tests := []struct {
1017		name                string
1018		environ             Environment
1019		arguments           []string
1020		useBazel            bool
1021		bazelDevMode        bool
1022		bazelProdMode       bool
1023		bazelStagingMode    bool
1024		expectedBuildConfig *smpb.BuildConfig
1025	}{
1026		{
1027			name:    "none set",
1028			environ: Environment{},
1029			expectedBuildConfig: &smpb.BuildConfig{
1030				ForceUseGoma:                proto.Bool(false),
1031				UseGoma:                     proto.Bool(false),
1032				UseRbe:                      proto.Bool(false),
1033				BazelMixedBuild:             proto.Bool(false),
1034				ForceDisableBazelMixedBuild: proto.Bool(false),
1035				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1036			},
1037		},
1038		{
1039			name:    "force use goma",
1040			environ: Environment{"FORCE_USE_GOMA=1"},
1041			expectedBuildConfig: &smpb.BuildConfig{
1042				ForceUseGoma:                proto.Bool(true),
1043				UseGoma:                     proto.Bool(false),
1044				UseRbe:                      proto.Bool(false),
1045				BazelMixedBuild:             proto.Bool(false),
1046				ForceDisableBazelMixedBuild: proto.Bool(false),
1047				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1048			},
1049		},
1050		{
1051			name:    "use goma",
1052			environ: Environment{"USE_GOMA=1"},
1053			expectedBuildConfig: &smpb.BuildConfig{
1054				ForceUseGoma:                proto.Bool(false),
1055				UseGoma:                     proto.Bool(true),
1056				UseRbe:                      proto.Bool(false),
1057				BazelMixedBuild:             proto.Bool(false),
1058				ForceDisableBazelMixedBuild: proto.Bool(false),
1059				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1060			},
1061		},
1062		{
1063			name:    "use rbe",
1064			environ: Environment{"USE_RBE=1"},
1065			expectedBuildConfig: &smpb.BuildConfig{
1066				ForceUseGoma:                proto.Bool(false),
1067				UseGoma:                     proto.Bool(false),
1068				UseRbe:                      proto.Bool(true),
1069				BazelMixedBuild:             proto.Bool(false),
1070				ForceDisableBazelMixedBuild: proto.Bool(false),
1071				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1072			},
1073		},
1074		{
1075			name:    "disable mixed builds",
1076			environ: Environment{"BUILD_BROKEN_DISABLE_BAZEL=1"},
1077			expectedBuildConfig: &smpb.BuildConfig{
1078				ForceUseGoma:                proto.Bool(false),
1079				UseGoma:                     proto.Bool(false),
1080				UseRbe:                      proto.Bool(false),
1081				BazelMixedBuild:             proto.Bool(false),
1082				ForceDisableBazelMixedBuild: proto.Bool(true),
1083				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1084			},
1085		},
1086		{
1087			name:     "use bazel as ninja",
1088			environ:  Environment{},
1089			useBazel: true,
1090			expectedBuildConfig: &smpb.BuildConfig{
1091				ForceUseGoma:                proto.Bool(false),
1092				UseGoma:                     proto.Bool(false),
1093				UseRbe:                      proto.Bool(false),
1094				BazelMixedBuild:             proto.Bool(false),
1095				ForceDisableBazelMixedBuild: proto.Bool(false),
1096				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1097			},
1098		},
1099		{
1100			name:         "bazel mixed build from dev mode",
1101			environ:      Environment{},
1102			bazelDevMode: true,
1103			expectedBuildConfig: &smpb.BuildConfig{
1104				ForceUseGoma:                proto.Bool(false),
1105				UseGoma:                     proto.Bool(false),
1106				UseRbe:                      proto.Bool(false),
1107				BazelMixedBuild:             proto.Bool(true),
1108				ForceDisableBazelMixedBuild: proto.Bool(false),
1109				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1110			},
1111		},
1112		{
1113			name:          "bazel mixed build from prod mode",
1114			environ:       Environment{},
1115			bazelProdMode: true,
1116			expectedBuildConfig: &smpb.BuildConfig{
1117				ForceUseGoma:                proto.Bool(false),
1118				UseGoma:                     proto.Bool(false),
1119				UseRbe:                      proto.Bool(false),
1120				BazelMixedBuild:             proto.Bool(true),
1121				ForceDisableBazelMixedBuild: proto.Bool(false),
1122				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1123			},
1124		},
1125		{
1126			name:             "bazel mixed build from staging mode",
1127			environ:          Environment{},
1128			bazelStagingMode: true,
1129			expectedBuildConfig: &smpb.BuildConfig{
1130				ForceUseGoma:                proto.Bool(false),
1131				UseGoma:                     proto.Bool(false),
1132				UseRbe:                      proto.Bool(false),
1133				BazelMixedBuild:             proto.Bool(true),
1134				ForceDisableBazelMixedBuild: proto.Bool(false),
1135				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1136			},
1137		},
1138		{
1139			name:      "specified targets",
1140			environ:   Environment{},
1141			useBazel:  true,
1142			arguments: []string{"droid", "dist"},
1143			expectedBuildConfig: &smpb.BuildConfig{
1144				ForceUseGoma:                proto.Bool(false),
1145				UseGoma:                     proto.Bool(false),
1146				UseRbe:                      proto.Bool(false),
1147				BazelMixedBuild:             proto.Bool(false),
1148				Targets:                     []string{"droid", "dist"},
1149				ForceDisableBazelMixedBuild: proto.Bool(false),
1150				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1151			},
1152		},
1153		{
1154			name: "all set",
1155			environ: Environment{
1156				"FORCE_USE_GOMA=1",
1157				"USE_GOMA=1",
1158				"USE_RBE=1",
1159				"BUILD_BROKEN_DISABLE_BAZEL=1",
1160			},
1161			useBazel:     true,
1162			bazelDevMode: true,
1163			expectedBuildConfig: &smpb.BuildConfig{
1164				ForceUseGoma:                proto.Bool(true),
1165				UseGoma:                     proto.Bool(true),
1166				UseRbe:                      proto.Bool(true),
1167				BazelMixedBuild:             proto.Bool(true),
1168				ForceDisableBazelMixedBuild: proto.Bool(true),
1169				NinjaWeightListSource:       smpb.BuildConfig_NOT_USED.Enum(),
1170			},
1171		},
1172	}
1173
1174	ctx := testContext()
1175	for _, tc := range tests {
1176		t.Run(tc.name, func(t *testing.T) {
1177			c := &configImpl{
1178				environ:          &tc.environ,
1179				bazelDevMode:     tc.bazelDevMode,
1180				bazelProdMode:    tc.bazelProdMode,
1181				bazelStagingMode: tc.bazelStagingMode,
1182				arguments:        tc.arguments,
1183			}
1184			config := Config{c}
1185			checkBazelMode(ctx, config)
1186			actualBuildConfig := buildConfig(config)
1187			if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) {
1188				t.Errorf("Build config mismatch.\n"+
1189					"Expected build config: %#v\n"+
1190					"Actual build config: %#v", prototext.Format(expected), prototext.Format(actualBuildConfig))
1191			}
1192		})
1193	}
1194}
1195
1196func TestGetMetricsUploaderApp(t *testing.T) {
1197
1198	metricsUploaderDir := "metrics_uploader_dir"
1199	metricsUploaderBinary := "metrics_uploader_binary"
1200	metricsUploaderPath := filepath.Join(metricsUploaderDir, metricsUploaderBinary)
1201	tests := []struct {
1202		description string
1203		environ     Environment
1204		createFiles bool
1205		expected    string
1206	}{{
1207		description: "Uploader binary exist",
1208		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
1209		createFiles: true,
1210		expected:    metricsUploaderPath,
1211	}, {
1212		description: "Uploader binary not exist",
1213		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
1214		createFiles: false,
1215		expected:    "",
1216	}, {
1217		description: "Uploader binary variable not set",
1218		createFiles: true,
1219		expected:    "",
1220	}}
1221
1222	for _, tt := range tests {
1223		t.Run(tt.description, func(t *testing.T) {
1224			defer logger.Recover(func(err error) {
1225				t.Fatalf("got unexpected error: %v", err)
1226			})
1227
1228			// Create the root source tree.
1229			topDir, err := ioutil.TempDir("", "")
1230			if err != nil {
1231				t.Fatalf("failed to create temp dir: %v", err)
1232			}
1233			defer os.RemoveAll(topDir)
1234
1235			expected := tt.expected
1236			if len(expected) > 0 {
1237				expected = filepath.Join(topDir, expected)
1238			}
1239
1240			if tt.createFiles {
1241				if err := os.MkdirAll(filepath.Join(topDir, metricsUploaderDir), 0755); err != nil {
1242					t.Errorf("failed to create %s directory: %v", metricsUploaderDir, err)
1243				}
1244				if err := ioutil.WriteFile(filepath.Join(topDir, metricsUploaderPath), []byte{}, 0644); err != nil {
1245					t.Errorf("failed to create file %s: %v", expected, err)
1246				}
1247			}
1248
1249			actual := GetMetricsUploader(topDir, &tt.environ)
1250
1251			if actual != expected {
1252				t.Errorf("expecting: %s, actual: %s", expected, actual)
1253			}
1254		})
1255	}
1256}
1257