blob: 81aa238b8f5f5fb7e30bed0ffde72d6bc86e0177 [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) {
28 tests := []struct {
29 name string
30 calls func(stat status.StatusOutput)
31 smart string
32 dumb string
33 }{
34 {
35 name: "two actions",
36 calls: twoActions,
Colin Cross00bdfd82019-06-11 11:16:23 -070037 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070038 dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
39 },
40 {
41 name: "two parallel actions",
42 calls: twoParallelActions,
Colin Cross00bdfd82019-06-11 11:16:23 -070043 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070044 dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
45 },
46 {
47 name: "action with output",
48 calls: actionsWithOutput,
Colin Cross00bdfd82019-06-11 11:16:23 -070049 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070050 dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
51 },
52 {
53 name: "action with output without newline",
54 calls: actionsWithOutputWithoutNewline,
Colin Cross00bdfd82019-06-11 11:16:23 -070055 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070056 dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
57 },
58 {
59 name: "action with error",
60 calls: actionsWithError,
Colin Cross00bdfd82019-06-11 11:16:23 -070061 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070062 dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
63 },
64 {
65 name: "action with empty description",
66 calls: actionWithEmptyDescription,
Colin Cross00bdfd82019-06-11 11:16:23 -070067 smart: "\r\x1b[1m[ 0% 0/1] command1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] command1\x1b[0m\x1b[K\n",
Colin Crossdde49cb2019-06-08 21:59:42 -070068 dumb: "[100% 1/1] command1\n",
69 },
70 {
71 name: "messages",
72 calls: actionsWithMessages,
Colin Cross00bdfd82019-06-11 11:16:23 -070073 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070074 dumb: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
75 },
76 {
77 name: "action with long description",
78 calls: actionWithLongDescription,
Colin Cross00bdfd82019-06-11 11:16:23 -070079 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070080 dumb: "[ 50% 1/2] action with very long description to test eliding\n",
81 },
82 {
83 name: "action with output with ansi codes",
84 calls: actionWithOuptutWithAnsiCodes,
Colin Cross00bdfd82019-06-11 11:16:23 -070085 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",
Colin Crossdde49cb2019-06-08 21:59:42 -070086 dumb: "[100% 1/1] action1\ncolor\n",
87 },
88 }
89
Colin Cross3dac80e2019-06-11 11:19:06 -070090 os.Setenv(tableHeightEnVar, "")
91
Colin Crossdde49cb2019-06-08 21:59:42 -070092 for _, tt := range tests {
93 t.Run(tt.name, func(t *testing.T) {
Colin Cross3dac80e2019-06-11 11:19:06 -070094
Colin Crossdde49cb2019-06-08 21:59:42 -070095 t.Run("smart", func(t *testing.T) {
96 smart := &fakeSmartTerminal{termWidth: 40}
Colin Cross097ed2a2019-06-08 21:48:58 -070097 stat := NewStatusOutput(smart, "", false)
Colin Crossdde49cb2019-06-08 21:59:42 -070098 tt.calls(stat)
99 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700100
101 if g, w := smart.String(), tt.smart; g != w {
102 t.Errorf("want:\n%q\ngot:\n%q", w, g)
103 }
104 })
105
106 t.Run("dumb", func(t *testing.T) {
107 dumb := &bytes.Buffer{}
Colin Cross097ed2a2019-06-08 21:48:58 -0700108 stat := NewStatusOutput(dumb, "", false)
Colin Crossdde49cb2019-06-08 21:59:42 -0700109 tt.calls(stat)
110 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700111
112 if g, w := dumb.String(), tt.dumb; g != w {
113 t.Errorf("want:\n%q\ngot:\n%q", w, g)
114 }
115 })
116 })
117 }
118}
119
120type runner struct {
121 counts status.Counts
122 stat status.StatusOutput
123}
124
125func newRunner(stat status.StatusOutput, totalActions int) *runner {
126 return &runner{
127 counts: status.Counts{TotalActions: totalActions},
128 stat: stat,
129 }
130}
131
132func (r *runner) startAction(action *status.Action) {
133 r.counts.StartedActions++
134 r.counts.RunningActions++
135 r.stat.StartAction(action, r.counts)
136}
137
138func (r *runner) finishAction(result status.ActionResult) {
139 r.counts.FinishedActions++
140 r.counts.RunningActions--
141 r.stat.FinishAction(result, r.counts)
142}
143
144func (r *runner) finishAndStartAction(result status.ActionResult, action *status.Action) {
145 r.counts.FinishedActions++
146 r.stat.FinishAction(result, r.counts)
147
148 r.counts.StartedActions++
149 r.stat.StartAction(action, r.counts)
150}
151
152var (
153 action1 = &status.Action{Description: "action1"}
154 result1 = status.ActionResult{Action: action1}
155 action2 = &status.Action{Description: "action2"}
156 result2 = status.ActionResult{Action: action2}
157 action3 = &status.Action{Description: "action3"}
158 result3 = status.ActionResult{Action: action3}
159)
160
161func twoActions(stat status.StatusOutput) {
162 runner := newRunner(stat, 2)
163 runner.startAction(action1)
164 runner.finishAction(result1)
165 runner.startAction(action2)
166 runner.finishAction(result2)
167}
168
169func twoParallelActions(stat status.StatusOutput) {
170 runner := newRunner(stat, 2)
171 runner.startAction(action1)
172 runner.startAction(action2)
173 runner.finishAction(result1)
174 runner.finishAction(result2)
175}
176
177func actionsWithOutput(stat status.StatusOutput) {
178 result2WithOutput := status.ActionResult{Action: action2, Output: "output1\noutput2\n"}
179
180 runner := newRunner(stat, 3)
181 runner.startAction(action1)
182 runner.finishAction(result1)
183 runner.startAction(action2)
184 runner.finishAction(result2WithOutput)
185 runner.startAction(action3)
186 runner.finishAction(result3)
187}
188
189func actionsWithOutputWithoutNewline(stat status.StatusOutput) {
190 result2WithOutputWithoutNewline := status.ActionResult{Action: action2, Output: "output1\noutput2"}
191
192 runner := newRunner(stat, 3)
193 runner.startAction(action1)
194 runner.finishAction(result1)
195 runner.startAction(action2)
196 runner.finishAction(result2WithOutputWithoutNewline)
197 runner.startAction(action3)
198 runner.finishAction(result3)
199}
200
201func actionsWithError(stat status.StatusOutput) {
202 action2WithError := &status.Action{Description: "action2", Outputs: []string{"f1", "f2"}, Command: "touch f1 f2"}
203 result2WithError := status.ActionResult{Action: action2WithError, Output: "error1\nerror2\n", Error: fmt.Errorf("error1")}
204
205 runner := newRunner(stat, 3)
206 runner.startAction(action1)
207 runner.finishAction(result1)
208 runner.startAction(action2WithError)
209 runner.finishAction(result2WithError)
210 runner.startAction(action3)
211 runner.finishAction(result3)
212}
213
214func actionWithEmptyDescription(stat status.StatusOutput) {
215 action1 := &status.Action{Command: "command1"}
216 result1 := status.ActionResult{Action: action1}
217
218 runner := newRunner(stat, 1)
219 runner.startAction(action1)
220 runner.finishAction(result1)
221}
222
223func actionsWithMessages(stat status.StatusOutput) {
224 runner := newRunner(stat, 2)
225
226 runner.startAction(action1)
227 runner.finishAction(result1)
228
229 stat.Message(status.VerboseLvl, "verbose")
230 stat.Message(status.StatusLvl, "status")
231 stat.Message(status.PrintLvl, "print")
232 stat.Message(status.ErrorLvl, "error")
233
234 runner.startAction(action2)
235 runner.finishAction(result2)
236}
237
238func actionWithLongDescription(stat status.StatusOutput) {
239 action1 := &status.Action{Description: "action with very long description to test eliding"}
240 result1 := status.ActionResult{Action: action1}
241
242 runner := newRunner(stat, 2)
243
244 runner.startAction(action1)
245
246 runner.finishAction(result1)
247}
248
249func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) {
250 result1WithOutputWithAnsiCodes := status.ActionResult{Action: action1, Output: "\x1b[31mcolor\x1b[0m"}
251
252 runner := newRunner(stat, 1)
253 runner.startAction(action1)
254 runner.finishAction(result1WithOutputWithAnsiCodes)
255}
256
257func TestSmartStatusOutputWidthChange(t *testing.T) {
Colin Cross3dac80e2019-06-11 11:19:06 -0700258 os.Setenv(tableHeightEnVar, "")
259
Colin Crossdde49cb2019-06-08 21:59:42 -0700260 smart := &fakeSmartTerminal{termWidth: 40}
Colin Cross097ed2a2019-06-08 21:48:58 -0700261 stat := NewStatusOutput(smart, "", false)
Colin Cross4355ee62019-06-11 23:01:36 -0700262 smartStat := stat.(*smartStatusOutput)
263 smartStat.sigwinchHandled = make(chan bool)
Colin Crossdde49cb2019-06-08 21:59:42 -0700264
265 runner := newRunner(stat, 2)
266
267 action := &status.Action{Description: "action with very long description to test eliding"}
268 result := status.ActionResult{Action: action}
269
270 runner.startAction(action)
271 smart.termWidth = 30
Colin Cross4355ee62019-06-11 23:01:36 -0700272 // Fake a SIGWINCH
273 smartStat.sigwinch <- syscall.SIGWINCH
274 <-smartStat.sigwinchHandled
Colin Crossdde49cb2019-06-08 21:59:42 -0700275 runner.finishAction(result)
276
277 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700278
Colin Cross00bdfd82019-06-11 11:16:23 -0700279 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 -0700280
281 if g := smart.String(); g != w {
282 t.Errorf("want:\n%q\ngot:\n%q", w, g)
283 }
284}