blob: da109124f6ad2d4ad5bbe0a66b718cde1e8260c6 [file] [log] [blame]
Colin Crossdde49cb2019-06-08 21:59:42 -07001// Copyright 2018 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 terminal
16
17import (
18 "bytes"
19 "fmt"
Colin Cross3dac80e2019-06-11 11:19:06 -070020 "os"
Colin Cross4355ee62019-06-11 23:01:36 -070021 "syscall"
Colin Crossdde49cb2019-06-08 21:59:42 -070022 "testing"
23
24 "android/soong/ui/status"
25)
26
27func TestStatusOutput(t *testing.T) {
Colin Cross323dc602020-09-18 14:25:31 -070028 t.Parallel()
Colin Crossdde49cb2019-06-08 21:59:42 -070029 tests := []struct {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000030 name string
31 calls func(stat status.StatusOutput)
32 smart string
33 simple string
Colin Crossdde49cb2019-06-08 21:59:42 -070034 }{
35 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000036 name: "two actions",
37 calls: twoActions,
38 smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
39 simple: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070040 },
41 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000042 name: "two parallel actions",
43 calls: twoParallelActions,
44 smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
45 simple: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070046 },
47 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000048 name: "action with output",
49 calls: actionsWithOutput,
50 smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\noutput1\noutput2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
51 simple: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070052 },
53 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000054 name: "action with output without newline",
55 calls: actionsWithOutputWithoutNewline,
56 smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\noutput1\noutput2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
57 simple: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070058 },
59 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000060 name: "action with error",
61 calls: actionsWithError,
62 smart: "\r\x1b[1m[ 0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
63 simple: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070064 },
65 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000066 name: "action with empty description",
67 calls: actionWithEmptyDescription,
68 smart: "\r\x1b[1m[ 0% 0/1] command1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] command1\x1b[0m\x1b[K\n",
69 simple: "[100% 1/1] command1\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070070 },
71 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000072 name: "messages",
73 calls: actionsWithMessages,
74 smart: "\r\x1b[1m[ 0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\nFAILED: error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
75 simple: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070076 },
77 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000078 name: "action with long description",
79 calls: actionWithLongDescription,
80 smart: "\r\x1b[1m[ 0% 0/2] action with very long descrip\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action with very long descrip\x1b[0m\x1b[K\n",
81 simple: "[ 50% 1/2] action with very long description to test eliding\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070082 },
83 {
Patrice Arrudaf445ba12020-07-28 17:49:01 +000084 name: "action with output with ansi codes",
85 calls: actionWithOuptutWithAnsiCodes,
86 smart: "\r\x1b[1m[ 0% 0/1] action1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] action1\x1b[0m\x1b[K\n\x1b[31mcolor\x1b[0m\n",
87 simple: "[100% 1/1] action1\ncolor\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070088 },
89 }
90
Colin Cross3dac80e2019-06-11 11:19:06 -070091 os.Setenv(tableHeightEnVar, "")
92
Colin Crossdde49cb2019-06-08 21:59:42 -070093 for _, tt := range tests {
94 t.Run(tt.name, func(t *testing.T) {
Colin Cross3dac80e2019-06-11 11:19:06 -070095
Colin Crossdde49cb2019-06-08 21:59:42 -070096 t.Run("smart", func(t *testing.T) {
97 smart := &fakeSmartTerminal{termWidth: 40}
Colin Crossc0b9f6b2019-09-23 12:44:54 -070098 stat := NewStatusOutput(smart, "", false, false)
Colin Crossdde49cb2019-06-08 21:59:42 -070099 tt.calls(stat)
100 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700101
102 if g, w := smart.String(), tt.smart; g != w {
103 t.Errorf("want:\n%q\ngot:\n%q", w, g)
104 }
105 })
106
Patrice Arrudaf445ba12020-07-28 17:49:01 +0000107 t.Run("simple", func(t *testing.T) {
108 simple := &bytes.Buffer{}
109 stat := NewStatusOutput(simple, "", false, false)
Colin Crossdde49cb2019-06-08 21:59:42 -0700110 tt.calls(stat)
111 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700112
Patrice Arrudaf445ba12020-07-28 17:49:01 +0000113 if g, w := simple.String(), tt.simple; g != w {
Colin Crossdde49cb2019-06-08 21:59:42 -0700114 t.Errorf("want:\n%q\ngot:\n%q", w, g)
115 }
116 })
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700117
Patrice Arrudaf445ba12020-07-28 17:49:01 +0000118 t.Run("force simple", func(t *testing.T) {
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700119 smart := &fakeSmartTerminal{termWidth: 40}
120 stat := NewStatusOutput(smart, "", true, false)
121 tt.calls(stat)
122 stat.Flush()
123
Patrice Arrudaf445ba12020-07-28 17:49:01 +0000124 if g, w := smart.String(), tt.simple; g != w {
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700125 t.Errorf("want:\n%q\ngot:\n%q", w, g)
126 }
127 })
Colin Crossdde49cb2019-06-08 21:59:42 -0700128 })
129 }
130}
131
132type runner struct {
133 counts status.Counts
134 stat status.StatusOutput
135}
136
137func newRunner(stat status.StatusOutput, totalActions int) *runner {
138 return &runner{
139 counts: status.Counts{TotalActions: totalActions},
140 stat: stat,
141 }
142}
143
144func (r *runner) startAction(action *status.Action) {
145 r.counts.StartedActions++
146 r.counts.RunningActions++
147 r.stat.StartAction(action, r.counts)
148}
149
150func (r *runner) finishAction(result status.ActionResult) {
151 r.counts.FinishedActions++
152 r.counts.RunningActions--
153 r.stat.FinishAction(result, r.counts)
154}
155
156func (r *runner) finishAndStartAction(result status.ActionResult, action *status.Action) {
157 r.counts.FinishedActions++
158 r.stat.FinishAction(result, r.counts)
159
160 r.counts.StartedActions++
161 r.stat.StartAction(action, r.counts)
162}
163
164var (
165 action1 = &status.Action{Description: "action1"}
166 result1 = status.ActionResult{Action: action1}
167 action2 = &status.Action{Description: "action2"}
168 result2 = status.ActionResult{Action: action2}
169 action3 = &status.Action{Description: "action3"}
170 result3 = status.ActionResult{Action: action3}
171)
172
173func twoActions(stat status.StatusOutput) {
174 runner := newRunner(stat, 2)
175 runner.startAction(action1)
176 runner.finishAction(result1)
177 runner.startAction(action2)
178 runner.finishAction(result2)
179}
180
181func twoParallelActions(stat status.StatusOutput) {
182 runner := newRunner(stat, 2)
183 runner.startAction(action1)
184 runner.startAction(action2)
185 runner.finishAction(result1)
186 runner.finishAction(result2)
187}
188
189func actionsWithOutput(stat status.StatusOutput) {
190 result2WithOutput := status.ActionResult{Action: action2, Output: "output1\noutput2\n"}
191
192 runner := newRunner(stat, 3)
193 runner.startAction(action1)
194 runner.finishAction(result1)
195 runner.startAction(action2)
196 runner.finishAction(result2WithOutput)
197 runner.startAction(action3)
198 runner.finishAction(result3)
199}
200
201func actionsWithOutputWithoutNewline(stat status.StatusOutput) {
202 result2WithOutputWithoutNewline := status.ActionResult{Action: action2, Output: "output1\noutput2"}
203
204 runner := newRunner(stat, 3)
205 runner.startAction(action1)
206 runner.finishAction(result1)
207 runner.startAction(action2)
208 runner.finishAction(result2WithOutputWithoutNewline)
209 runner.startAction(action3)
210 runner.finishAction(result3)
211}
212
213func actionsWithError(stat status.StatusOutput) {
214 action2WithError := &status.Action{Description: "action2", Outputs: []string{"f1", "f2"}, Command: "touch f1 f2"}
215 result2WithError := status.ActionResult{Action: action2WithError, Output: "error1\nerror2\n", Error: fmt.Errorf("error1")}
216
217 runner := newRunner(stat, 3)
218 runner.startAction(action1)
219 runner.finishAction(result1)
220 runner.startAction(action2WithError)
221 runner.finishAction(result2WithError)
222 runner.startAction(action3)
223 runner.finishAction(result3)
224}
225
226func actionWithEmptyDescription(stat status.StatusOutput) {
227 action1 := &status.Action{Command: "command1"}
228 result1 := status.ActionResult{Action: action1}
229
230 runner := newRunner(stat, 1)
231 runner.startAction(action1)
232 runner.finishAction(result1)
233}
234
235func actionsWithMessages(stat status.StatusOutput) {
236 runner := newRunner(stat, 2)
237
238 runner.startAction(action1)
239 runner.finishAction(result1)
240
241 stat.Message(status.VerboseLvl, "verbose")
242 stat.Message(status.StatusLvl, "status")
243 stat.Message(status.PrintLvl, "print")
244 stat.Message(status.ErrorLvl, "error")
245
246 runner.startAction(action2)
247 runner.finishAction(result2)
248}
249
250func actionWithLongDescription(stat status.StatusOutput) {
251 action1 := &status.Action{Description: "action with very long description to test eliding"}
252 result1 := status.ActionResult{Action: action1}
253
254 runner := newRunner(stat, 2)
255
256 runner.startAction(action1)
257
258 runner.finishAction(result1)
259}
260
261func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) {
262 result1WithOutputWithAnsiCodes := status.ActionResult{Action: action1, Output: "\x1b[31mcolor\x1b[0m"}
263
264 runner := newRunner(stat, 1)
265 runner.startAction(action1)
266 runner.finishAction(result1WithOutputWithAnsiCodes)
267}
268
269func TestSmartStatusOutputWidthChange(t *testing.T) {
Colin Cross323dc602020-09-18 14:25:31 -0700270 t.Parallel()
Colin Cross3dac80e2019-06-11 11:19:06 -0700271 os.Setenv(tableHeightEnVar, "")
272
Colin Crossdde49cb2019-06-08 21:59:42 -0700273 smart := &fakeSmartTerminal{termWidth: 40}
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700274 stat := NewStatusOutput(smart, "", false, false)
Colin Cross4355ee62019-06-11 23:01:36 -0700275 smartStat := stat.(*smartStatusOutput)
276 smartStat.sigwinchHandled = make(chan bool)
Colin Crossdde49cb2019-06-08 21:59:42 -0700277
278 runner := newRunner(stat, 2)
279
280 action := &status.Action{Description: "action with very long description to test eliding"}
281 result := status.ActionResult{Action: action}
282
283 runner.startAction(action)
284 smart.termWidth = 30
Colin Cross4355ee62019-06-11 23:01:36 -0700285 // Fake a SIGWINCH
286 smartStat.sigwinch <- syscall.SIGWINCH
287 <-smartStat.sigwinchHandled
Colin Crossdde49cb2019-06-08 21:59:42 -0700288 runner.finishAction(result)
289
290 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700291
Colin Cross00bdfd82019-06-11 11:16:23 -0700292 w := "\r\x1b[1m[ 0% 0/2] action with very long descrip\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action with very lo\x1b[0m\x1b[K\n"
Colin Crossdde49cb2019-06-08 21:59:42 -0700293
294 if g := smart.String(); g != w {
295 t.Errorf("want:\n%q\ngot:\n%q", w, g)
296 }
297}