• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 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	"errors"
19	"io/ioutil"
20	"os"
21	"path/filepath"
22	"reflect"
23	"sort"
24	"strconv"
25	"strings"
26	"testing"
27	"time"
28
29	"android/soong/ui/logger"
30)
31
32func writeBazelProfileFile(dir string) error {
33	contents := `
34
35=== PHASE SUMMARY INFORMATION ===
36
37Total launch phase time                              1.193 s   15.77%
38Total init phase time                                1.092 s   14.44%
39Total target pattern evaluation phase time           0.580 s    7.67%
40Total interleaved loading-and-analysis phase time    3.646 s   48.21%
41Total preparation phase time                         0.022 s    0.30%
42Total execution phase time                           0.993 s   13.13%
43Total finish phase time                              0.036 s    0.48%
44---------------------------------------------------------------------
45Total run time                                       7.563 s  100.00%
46
47Critical path (178 ms):
48       Time Percentage   Description
49     178 ms  100.00%   action 'BazelWorkspaceStatusAction stable-status.txt'
50
51`
52	file := filepath.Join(dir, "bazel_metrics.txt")
53	return os.WriteFile(file, []byte(contents), 0666)
54}
55
56func TestPruneMetricsFiles(t *testing.T) {
57	rootDir := t.TempDir()
58
59	dirs := []string{
60		filepath.Join(rootDir, "d1"),
61		filepath.Join(rootDir, "d1", "d2"),
62		filepath.Join(rootDir, "d1", "d2", "d3"),
63	}
64
65	files := []string{
66		filepath.Join(rootDir, "d1", "f1"),
67		filepath.Join(rootDir, "d1", "d2", "f1"),
68		filepath.Join(rootDir, "d1", "d2", "d3", "f1"),
69	}
70
71	for _, d := range dirs {
72		if err := os.MkdirAll(d, 0777); err != nil {
73			t.Fatalf("got %v, expecting nil error for making directory %q", err, d)
74		}
75	}
76
77	for _, f := range files {
78		if err := ioutil.WriteFile(f, []byte{}, 0777); err != nil {
79			t.Fatalf("got %v, expecting nil error on writing file %q", err, f)
80		}
81	}
82
83	want := []string{
84		filepath.Join(rootDir, "d1", "f1"),
85		filepath.Join(rootDir, "d1", "d2", "f1"),
86		filepath.Join(rootDir, "d1", "d2", "d3", "f1"),
87	}
88
89	got := pruneMetricsFiles([]string{rootDir})
90
91	sort.Strings(got)
92	sort.Strings(want)
93
94	if !reflect.DeepEqual(got, want) {
95		t.Errorf("got %q, want %q after pruning metrics files", got, want)
96	}
97}
98
99func TestUploadMetrics(t *testing.T) {
100	ctx := testContext()
101	tests := []struct {
102		description string
103		uploader    string
104		createFiles bool
105		files       []string
106	}{{
107		description: "no metrics uploader",
108	}, {
109		description: "non-existent metrics files no upload",
110		uploader:    "echo",
111		files:       []string{"metrics_file_1", "metrics_file_2", "metrics_file_3, bazel_metrics.pb"},
112	}, {
113		description: "trigger upload",
114		uploader:    "echo",
115		createFiles: true,
116		files:       []string{"metrics_file_1", "metrics_file_2, bazel_metrics.pb"},
117	}}
118
119	for _, tt := range tests {
120		t.Run(tt.description, func(t *testing.T) {
121			defer logger.Recover(func(err error) {
122				t.Fatalf("got unexpected error: %v", err)
123			})
124
125			outDir, err := ioutil.TempDir("", "")
126			if err != nil {
127				t.Fatalf("failed to create out directory: %v", outDir)
128			}
129			defer os.RemoveAll(outDir)
130
131			// Supply our own tmpDir to delete the temp dir once the test is done.
132			orgTmpDir := tmpDir
133			tmpDir = func(string, string) (string, error) {
134				retDir := filepath.Join(outDir, "tmp_upload_dir")
135				if err := os.Mkdir(retDir, 0755); err != nil {
136					t.Fatalf("failed to create temporary directory %q: %v", retDir, err)
137				}
138				return retDir, nil
139			}
140			defer func() { tmpDir = orgTmpDir }()
141
142			metricsUploadDir := filepath.Join(outDir, ".metrics_uploader")
143			if err := os.Mkdir(metricsUploadDir, 0755); err != nil {
144				t.Fatalf("failed to create %q directory for oauth valid check: %v", metricsUploadDir, err)
145			}
146
147			var metricsFiles []string
148			if tt.createFiles {
149				for _, f := range tt.files {
150					filename := filepath.Join(outDir, f)
151					metricsFiles = append(metricsFiles, filename)
152					if err := ioutil.WriteFile(filename, []byte("test file"), 0644); err != nil {
153						t.Fatalf("failed to create a fake metrics file %q for uploading: %v", filename, err)
154					}
155				}
156			}
157			if err := writeBazelProfileFile(outDir); err != nil {
158				t.Fatalf("failed to create bazel profile file in dir: %v", outDir)
159			}
160
161			config := Config{&configImpl{
162				environ: &Environment{
163					"OUT_DIR=" + outDir,
164				},
165				buildDateTime:   strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
166				metricsUploader: tt.uploader,
167			}}
168
169			UploadMetrics(ctx, config, false, time.Now(), "out/bazel_metrics.txt", "out/bazel_metrics.pb", metricsFiles...)
170		})
171	}
172}
173
174func TestUploadMetricsErrors(t *testing.T) {
175	ctx := testContext()
176	tests := []struct {
177		description string
178		tmpDir      string
179		tmpDirErr   error
180		expectedErr string
181	}{{
182		description: "getTmpDir returned error",
183		tmpDirErr:   errors.New("getTmpDir failed"),
184		expectedErr: "getTmpDir failed",
185	}, {
186		description: "copyFile operation error",
187		tmpDir:      "/fake_dir",
188		expectedErr: "failed to copy",
189	}}
190
191	for _, tt := range tests {
192		t.Run(tt.description, func(t *testing.T) {
193			defer logger.Recover(func(err error) {
194				got := err.Error()
195				if !strings.Contains(got, tt.expectedErr) {
196					t.Errorf("got %q, want %q to be contained in error", got, tt.expectedErr)
197				}
198			})
199
200			outDir, err := ioutil.TempDir("", "")
201			if err != nil {
202				t.Fatalf("failed to create out directory: %v", outDir)
203			}
204			defer os.RemoveAll(outDir)
205
206			orgTmpDir := tmpDir
207			tmpDir = func(string, string) (string, error) {
208				return tt.tmpDir, tt.tmpDirErr
209			}
210			defer func() { tmpDir = orgTmpDir }()
211
212			metricsFile := filepath.Join(outDir, "metrics_file_1")
213			if err := ioutil.WriteFile(metricsFile, []byte("test file"), 0644); err != nil {
214				t.Fatalf("failed to create a fake metrics file %q for uploading: %v", metricsFile, err)
215			}
216
217			config := Config{&configImpl{
218				environ: &Environment{
219					"OUT_DIR=/bad",
220				},
221				metricsUploader: "echo",
222			}}
223
224			UploadMetrics(ctx, config, true, time.Now(), "", "", metricsFile)
225			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
226		})
227	}
228}
229
230func TestParsePercentageToTenThousandths(t *testing.T) {
231	// 2.59% should be returned as 259 - representing 259/10000 of the build
232	percentage := parsePercentageToTenThousandths("2.59%")
233	if percentage != 259 {
234		t.Errorf("Expected percentage to be returned as ten-thousandths. Expected 259, have %d\n", percentage)
235	}
236
237	// Test without a leading digit
238	percentage = parsePercentageToTenThousandths(".52%")
239	if percentage != 52 {
240		t.Errorf("Expected percentage to be returned as ten-thousandths. Expected 52, have %d\n", percentage)
241	}
242}
243
244func TestParseTimingToNanos(t *testing.T) {
245	// This parses from seconds (with millis precision) and returns nanos
246	timingNanos := parseTimingToNanos("0.111")
247	if timingNanos != 111000000 {
248		t.Errorf("Error parsing timing. Expected 111000, have %d\n", timingNanos)
249	}
250
251	// Test without a leading digit
252	timingNanos = parseTimingToNanos(".112")
253	if timingNanos != 112000000 {
254		t.Errorf("Error parsing timing. Expected 112000, have %d\n", timingNanos)
255	}
256}
257
258func TestParsePhaseTiming(t *testing.T) {
259	// Sample lines include:
260	// Total launch phase time   0.011 s    2.59%
261	// Total target pattern evaluation phase time  0.012 s    4.59%
262
263	line1 := "Total launch phase time   0.011 s    2.59%"
264	timing := parsePhaseTiming(line1)
265
266	if timing.GetPhaseName() != "launch" {
267		t.Errorf("Failed to parse phase name. Expected launch, have %s\n", timing.GetPhaseName())
268	} else if timing.GetDurationNanos() != 11000000 {
269		t.Errorf("Failed to parse duration nanos. Expected 11000000, have %d\n", timing.GetDurationNanos())
270	} else if timing.GetPortionOfBuildTime() != 259 {
271		t.Errorf("Failed to parse portion of build time. Expected 259, have %d\n", timing.GetPortionOfBuildTime())
272	}
273
274	// Test with a multiword phase name
275	line2 := "Total target pattern evaluation phase  time  0.012 s    4.59%"
276
277	timing = parsePhaseTiming(line2)
278	if timing.GetPhaseName() != "target pattern evaluation" {
279		t.Errorf("Failed to parse phase name. Expected target pattern evaluation, have %s\n", timing.GetPhaseName())
280	} else if timing.GetDurationNanos() != 12000000 {
281		t.Errorf("Failed to parse duration nanos. Expected 12000000, have %d\n", timing.GetDurationNanos())
282	} else if timing.GetPortionOfBuildTime() != 459 {
283		t.Errorf("Failed to parse portion of build time. Expected 459, have %d\n", timing.GetPortionOfBuildTime())
284	}
285}
286
287func TestParseTotal(t *testing.T) {
288	// Total line is in the form of:
289	// Total run time                                       7.563 s  100.00%
290
291	line := "Total run time                                       7.563 s  100.00%"
292
293	total := parseTotal(line)
294
295	// Only the seconds field is parsed, as nanos
296	if total != 7563000000 {
297		t.Errorf("Failed to parse total build time. Expected 7563000000, have %d\n", total)
298	}
299}
300