blob: 1fcded921a48811576b32500f3f5e18f48d5e50b [file] [log] [blame]
Patrice Arruda219eef32020-06-01 17:29:30 +00001// 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 (
Patrice Arruda7cc20742020-06-10 18:48:01 +000018 "errors"
Patrice Arruda219eef32020-06-01 17:29:30 +000019 "io/ioutil"
20 "os"
21 "path/filepath"
Patrice Arruda7d235cc2020-12-09 22:43:26 +000022 "reflect"
23 "sort"
Patrice Arruda219eef32020-06-01 17:29:30 +000024 "strconv"
25 "strings"
26 "testing"
27 "time"
28
29 "android/soong/ui/logger"
30)
31
MarkDacek00e31522023-01-06 22:15:24 +000032func 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
Patrice Arruda7d235cc2020-12-09 22:43:26 +000056func 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
Patrice Arruda219eef32020-06-01 17:29:30 +000099func TestUploadMetrics(t *testing.T) {
100 ctx := testContext()
101 tests := []struct {
102 description string
103 uploader string
104 createFiles bool
105 files []string
106 }{{
Yu Liu6e13b402021-07-27 14:29:06 -0700107 description: "no metrics uploader",
Patrice Arruda219eef32020-06-01 17:29:30 +0000108 }, {
109 description: "non-existent metrics files no upload",
Yu Liu6e13b402021-07-27 14:29:06 -0700110 uploader: "echo",
MarkDacek00e31522023-01-06 22:15:24 +0000111 files: []string{"metrics_file_1", "metrics_file_2", "metrics_file_3, bazel_metrics.pb"},
Patrice Arruda219eef32020-06-01 17:29:30 +0000112 }, {
113 description: "trigger upload",
114 uploader: "echo",
115 createFiles: true,
MarkDacek00e31522023-01-06 22:15:24 +0000116 files: []string{"metrics_file_1", "metrics_file_2, bazel_metrics.pb"},
Patrice Arruda219eef32020-06-01 17:29:30 +0000117 }}
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
Patrice Arruda92dc64f2020-11-16 16:29:16 -0800131 // Supply our own tmpDir to delete the temp dir once the test is done.
132 orgTmpDir := tmpDir
133 tmpDir = func(string, string) (string, error) {
Patrice Arruda7cc20742020-06-10 18:48:01 +0000134 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 }
Patrice Arruda92dc64f2020-11-16 16:29:16 -0800140 defer func() { tmpDir = orgTmpDir }()
Patrice Arruda7cc20742020-06-10 18:48:01 +0000141
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
Patrice Arruda219eef32020-06-01 17:29:30 +0000147 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 }
MarkDacek00e31522023-01-06 22:15:24 +0000157 if err := writeBazelProfileFile(outDir); err != nil {
158 t.Fatalf("failed to create bazel profile file in dir: %v", outDir)
159 }
Patrice Arruda219eef32020-06-01 17:29:30 +0000160
161 config := Config{&configImpl{
162 environ: &Environment{
163 "OUT_DIR=" + outDir,
Patrice Arruda219eef32020-06-01 17:29:30 +0000164 },
Yu Liu6e13b402021-07-27 14:29:06 -0700165 buildDateTime: strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
166 metricsUploader: tt.uploader,
Patrice Arruda219eef32020-06-01 17:29:30 +0000167 }}
168
MarkDacek733b77c2023-05-09 18:21:36 +0000169 UploadMetrics(ctx, config, false, time.Now(), metricsFiles...)
Patrice Arruda219eef32020-06-01 17:29:30 +0000170 })
171 }
172}
173
174func TestUploadMetricsErrors(t *testing.T) {
Patrice Arruda7cc20742020-06-10 18:48:01 +0000175 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 }}
Patrice Arruda219eef32020-06-01 17:29:30 +0000190
Patrice Arruda7cc20742020-06-10 18:48:01 +0000191 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
Patrice Arruda92dc64f2020-11-16 16:29:16 -0800206 orgTmpDir := tmpDir
207 tmpDir = func(string, string) (string, error) {
Patrice Arruda7cc20742020-06-10 18:48:01 +0000208 return tt.tmpDir, tt.tmpDirErr
209 }
Patrice Arruda92dc64f2020-11-16 16:29:16 -0800210 defer func() { tmpDir = orgTmpDir }()
Patrice Arruda7cc20742020-06-10 18:48:01 +0000211
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{
Patrice Arruda7cc20742020-06-10 18:48:01 +0000219 "OUT_DIR=/bad",
Yu Liu6e13b402021-07-27 14:29:06 -0700220 },
221 metricsUploader: "echo",
222 }}
Patrice Arruda7cc20742020-06-10 18:48:01 +0000223
MarkDacek733b77c2023-05-09 18:21:36 +0000224 UploadMetrics(ctx, config, true, time.Now(), metricsFile)
Patrice Arruda7cc20742020-06-10 18:48:01 +0000225 t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
226 })
Patrice Arruda219eef32020-06-01 17:29:30 +0000227 }
Patrice Arruda219eef32020-06-01 17:29:30 +0000228}
MarkDacek00e31522023-01-06 22:15:24 +0000229
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}