blob: 106d6515f294c755dc215dd4ef175da0df296bb0 [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 Cross4355ee62019-06-11 23:01:36 -070020 "syscall"
Colin Crossdde49cb2019-06-08 21:59:42 -070021 "testing"
22
23 "android/soong/ui/status"
24)
25
26func TestStatusOutput(t *testing.T) {
27 tests := []struct {
28 name string
29 calls func(stat status.StatusOutput)
30 smart string
31 dumb string
32 }{
33 {
34 name: "two actions",
35 calls: twoActions,
Colin Cross00bdfd82019-06-11 11:16:23 -070036 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 -070037 dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
38 },
39 {
40 name: "two parallel actions",
41 calls: twoParallelActions,
Colin Cross00bdfd82019-06-11 11:16:23 -070042 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 -070043 dumb: "[ 50% 1/2] action1\n[100% 2/2] action2\n",
44 },
45 {
46 name: "action with output",
47 calls: actionsWithOutput,
Colin Cross00bdfd82019-06-11 11:16:23 -070048 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 -070049 dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
50 },
51 {
52 name: "action with output without newline",
53 calls: actionsWithOutputWithoutNewline,
Colin Cross00bdfd82019-06-11 11:16:23 -070054 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 -070055 dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\noutput1\noutput2\n[100% 3/3] action3\n",
56 },
57 {
58 name: "action with error",
59 calls: actionsWithError,
Colin Cross00bdfd82019-06-11 11:16:23 -070060 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 -070061 dumb: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
62 },
63 {
64 name: "action with empty description",
65 calls: actionWithEmptyDescription,
Colin Cross00bdfd82019-06-11 11:16:23 -070066 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 -070067 dumb: "[100% 1/1] command1\n",
68 },
69 {
70 name: "messages",
71 calls: actionsWithMessages,
Colin Cross00bdfd82019-06-11 11:16:23 -070072 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 -070073 dumb: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
74 },
75 {
76 name: "action with long description",
77 calls: actionWithLongDescription,
Colin Cross00bdfd82019-06-11 11:16:23 -070078 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 -070079 dumb: "[ 50% 1/2] action with very long description to test eliding\n",
80 },
81 {
82 name: "action with output with ansi codes",
83 calls: actionWithOuptutWithAnsiCodes,
Colin Cross00bdfd82019-06-11 11:16:23 -070084 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 -070085 dumb: "[100% 1/1] action1\ncolor\n",
86 },
87 }
88
89 for _, tt := range tests {
90 t.Run(tt.name, func(t *testing.T) {
91 t.Run("smart", func(t *testing.T) {
92 smart := &fakeSmartTerminal{termWidth: 40}
Colin Cross097ed2a2019-06-08 21:48:58 -070093 stat := NewStatusOutput(smart, "", false)
Colin Crossdde49cb2019-06-08 21:59:42 -070094 tt.calls(stat)
95 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -070096
97 if g, w := smart.String(), tt.smart; g != w {
98 t.Errorf("want:\n%q\ngot:\n%q", w, g)
99 }
100 })
101
102 t.Run("dumb", func(t *testing.T) {
103 dumb := &bytes.Buffer{}
Colin Cross097ed2a2019-06-08 21:48:58 -0700104 stat := NewStatusOutput(dumb, "", false)
Colin Crossdde49cb2019-06-08 21:59:42 -0700105 tt.calls(stat)
106 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700107
108 if g, w := dumb.String(), tt.dumb; g != w {
109 t.Errorf("want:\n%q\ngot:\n%q", w, g)
110 }
111 })
112 })
113 }
114}
115
116type runner struct {
117 counts status.Counts
118 stat status.StatusOutput
119}
120
121func newRunner(stat status.StatusOutput, totalActions int) *runner {
122 return &runner{
123 counts: status.Counts{TotalActions: totalActions},
124 stat: stat,
125 }
126}
127
128func (r *runner) startAction(action *status.Action) {
129 r.counts.StartedActions++
130 r.counts.RunningActions++
131 r.stat.StartAction(action, r.counts)
132}
133
134func (r *runner) finishAction(result status.ActionResult) {
135 r.counts.FinishedActions++
136 r.counts.RunningActions--
137 r.stat.FinishAction(result, r.counts)
138}
139
140func (r *runner) finishAndStartAction(result status.ActionResult, action *status.Action) {
141 r.counts.FinishedActions++
142 r.stat.FinishAction(result, r.counts)
143
144 r.counts.StartedActions++
145 r.stat.StartAction(action, r.counts)
146}
147
148var (
149 action1 = &status.Action{Description: "action1"}
150 result1 = status.ActionResult{Action: action1}
151 action2 = &status.Action{Description: "action2"}
152 result2 = status.ActionResult{Action: action2}
153 action3 = &status.Action{Description: "action3"}
154 result3 = status.ActionResult{Action: action3}
155)
156
157func twoActions(stat status.StatusOutput) {
158 runner := newRunner(stat, 2)
159 runner.startAction(action1)
160 runner.finishAction(result1)
161 runner.startAction(action2)
162 runner.finishAction(result2)
163}
164
165func twoParallelActions(stat status.StatusOutput) {
166 runner := newRunner(stat, 2)
167 runner.startAction(action1)
168 runner.startAction(action2)
169 runner.finishAction(result1)
170 runner.finishAction(result2)
171}
172
173func actionsWithOutput(stat status.StatusOutput) {
174 result2WithOutput := status.ActionResult{Action: action2, Output: "output1\noutput2\n"}
175
176 runner := newRunner(stat, 3)
177 runner.startAction(action1)
178 runner.finishAction(result1)
179 runner.startAction(action2)
180 runner.finishAction(result2WithOutput)
181 runner.startAction(action3)
182 runner.finishAction(result3)
183}
184
185func actionsWithOutputWithoutNewline(stat status.StatusOutput) {
186 result2WithOutputWithoutNewline := status.ActionResult{Action: action2, Output: "output1\noutput2"}
187
188 runner := newRunner(stat, 3)
189 runner.startAction(action1)
190 runner.finishAction(result1)
191 runner.startAction(action2)
192 runner.finishAction(result2WithOutputWithoutNewline)
193 runner.startAction(action3)
194 runner.finishAction(result3)
195}
196
197func actionsWithError(stat status.StatusOutput) {
198 action2WithError := &status.Action{Description: "action2", Outputs: []string{"f1", "f2"}, Command: "touch f1 f2"}
199 result2WithError := status.ActionResult{Action: action2WithError, Output: "error1\nerror2\n", Error: fmt.Errorf("error1")}
200
201 runner := newRunner(stat, 3)
202 runner.startAction(action1)
203 runner.finishAction(result1)
204 runner.startAction(action2WithError)
205 runner.finishAction(result2WithError)
206 runner.startAction(action3)
207 runner.finishAction(result3)
208}
209
210func actionWithEmptyDescription(stat status.StatusOutput) {
211 action1 := &status.Action{Command: "command1"}
212 result1 := status.ActionResult{Action: action1}
213
214 runner := newRunner(stat, 1)
215 runner.startAction(action1)
216 runner.finishAction(result1)
217}
218
219func actionsWithMessages(stat status.StatusOutput) {
220 runner := newRunner(stat, 2)
221
222 runner.startAction(action1)
223 runner.finishAction(result1)
224
225 stat.Message(status.VerboseLvl, "verbose")
226 stat.Message(status.StatusLvl, "status")
227 stat.Message(status.PrintLvl, "print")
228 stat.Message(status.ErrorLvl, "error")
229
230 runner.startAction(action2)
231 runner.finishAction(result2)
232}
233
234func actionWithLongDescription(stat status.StatusOutput) {
235 action1 := &status.Action{Description: "action with very long description to test eliding"}
236 result1 := status.ActionResult{Action: action1}
237
238 runner := newRunner(stat, 2)
239
240 runner.startAction(action1)
241
242 runner.finishAction(result1)
243}
244
245func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) {
246 result1WithOutputWithAnsiCodes := status.ActionResult{Action: action1, Output: "\x1b[31mcolor\x1b[0m"}
247
248 runner := newRunner(stat, 1)
249 runner.startAction(action1)
250 runner.finishAction(result1WithOutputWithAnsiCodes)
251}
252
253func TestSmartStatusOutputWidthChange(t *testing.T) {
254 smart := &fakeSmartTerminal{termWidth: 40}
Colin Cross097ed2a2019-06-08 21:48:58 -0700255 stat := NewStatusOutput(smart, "", false)
Colin Cross4355ee62019-06-11 23:01:36 -0700256 smartStat := stat.(*smartStatusOutput)
257 smartStat.sigwinchHandled = make(chan bool)
Colin Crossdde49cb2019-06-08 21:59:42 -0700258
259 runner := newRunner(stat, 2)
260
261 action := &status.Action{Description: "action with very long description to test eliding"}
262 result := status.ActionResult{Action: action}
263
264 runner.startAction(action)
265 smart.termWidth = 30
Colin Cross4355ee62019-06-11 23:01:36 -0700266 // Fake a SIGWINCH
267 smartStat.sigwinch <- syscall.SIGWINCH
268 <-smartStat.sigwinchHandled
Colin Crossdde49cb2019-06-08 21:59:42 -0700269 runner.finishAction(result)
270
271 stat.Flush()
Colin Crossdde49cb2019-06-08 21:59:42 -0700272
Colin Cross00bdfd82019-06-11 11:16:23 -0700273 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 -0700274
275 if g := smart.String(); g != w {
276 t.Errorf("want:\n%q\ngot:\n%q", w, g)
277 }
278}