1// 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" 20 "os" 21 "syscall" 22 "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 simple string 33 }{ 34 { 35 name: "two actions", 36 calls: twoActions, 37 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", 38 simple: "[ 50% 1/2] action1\n[100% 2/2] action2\n", 39 }, 40 { 41 name: "two parallel actions", 42 calls: twoParallelActions, 43 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", 44 simple: "[ 50% 1/2] action1\n[100% 2/2] action2\n", 45 }, 46 { 47 name: "action with output", 48 calls: actionsWithOutput, 49 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", 50 simple: "[ 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, 55 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", 56 simple: "[ 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, 61 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", 62 simple: "[ 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, 67 smart: "\r\x1b[1m[ 0% 0/1] command1\x1b[0m\x1b[K\r\x1b[1m[100% 1/1] command1\x1b[0m\x1b[K\n", 68 simple: "[100% 1/1] command1\n", 69 }, 70 { 71 name: "messages", 72 calls: actionsWithMessages, 73 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", 74 simple: "[ 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, 79 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", 80 simple: "[ 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, 85 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", 86 simple: "[100% 1/1] action1\ncolor\n", 87 }, 88 } 89 90 os.Setenv(tableHeightEnVar, "") 91 92 for _, tt := range tests { 93 t.Run(tt.name, func(t *testing.T) { 94 95 t.Run("smart", func(t *testing.T) { 96 smart := &fakeSmartTerminal{termWidth: 40} 97 stat := NewStatusOutput(smart, "", false, false) 98 tt.calls(stat) 99 stat.Flush() 100 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("simple", func(t *testing.T) { 107 simple := &bytes.Buffer{} 108 stat := NewStatusOutput(simple, "", false, false) 109 tt.calls(stat) 110 stat.Flush() 111 112 if g, w := simple.String(), tt.simple; g != w { 113 t.Errorf("want:\n%q\ngot:\n%q", w, g) 114 } 115 }) 116 117 t.Run("force simple", func(t *testing.T) { 118 smart := &fakeSmartTerminal{termWidth: 40} 119 stat := NewStatusOutput(smart, "", true, false) 120 tt.calls(stat) 121 stat.Flush() 122 123 if g, w := smart.String(), tt.simple; g != w { 124 t.Errorf("want:\n%q\ngot:\n%q", w, g) 125 } 126 }) 127 }) 128 } 129} 130 131type runner struct { 132 counts status.Counts 133 stat status.StatusOutput 134} 135 136func newRunner(stat status.StatusOutput, totalActions int) *runner { 137 return &runner{ 138 counts: status.Counts{TotalActions: totalActions}, 139 stat: stat, 140 } 141} 142 143func (r *runner) startAction(action *status.Action) { 144 r.counts.StartedActions++ 145 r.counts.RunningActions++ 146 r.stat.StartAction(action, r.counts) 147} 148 149func (r *runner) finishAction(result status.ActionResult) { 150 r.counts.FinishedActions++ 151 r.counts.RunningActions-- 152 r.stat.FinishAction(result, r.counts) 153} 154 155func (r *runner) finishAndStartAction(result status.ActionResult, action *status.Action) { 156 r.counts.FinishedActions++ 157 r.stat.FinishAction(result, r.counts) 158 159 r.counts.StartedActions++ 160 r.stat.StartAction(action, r.counts) 161} 162 163var ( 164 action1 = &status.Action{Description: "action1"} 165 result1 = status.ActionResult{Action: action1} 166 action2 = &status.Action{Description: "action2"} 167 result2 = status.ActionResult{Action: action2} 168 action3 = &status.Action{Description: "action3"} 169 result3 = status.ActionResult{Action: action3} 170) 171 172func twoActions(stat status.StatusOutput) { 173 runner := newRunner(stat, 2) 174 runner.startAction(action1) 175 runner.finishAction(result1) 176 runner.startAction(action2) 177 runner.finishAction(result2) 178} 179 180func twoParallelActions(stat status.StatusOutput) { 181 runner := newRunner(stat, 2) 182 runner.startAction(action1) 183 runner.startAction(action2) 184 runner.finishAction(result1) 185 runner.finishAction(result2) 186} 187 188func actionsWithOutput(stat status.StatusOutput) { 189 result2WithOutput := status.ActionResult{Action: action2, Output: "output1\noutput2\n"} 190 191 runner := newRunner(stat, 3) 192 runner.startAction(action1) 193 runner.finishAction(result1) 194 runner.startAction(action2) 195 runner.finishAction(result2WithOutput) 196 runner.startAction(action3) 197 runner.finishAction(result3) 198} 199 200func actionsWithOutputWithoutNewline(stat status.StatusOutput) { 201 result2WithOutputWithoutNewline := status.ActionResult{Action: action2, Output: "output1\noutput2"} 202 203 runner := newRunner(stat, 3) 204 runner.startAction(action1) 205 runner.finishAction(result1) 206 runner.startAction(action2) 207 runner.finishAction(result2WithOutputWithoutNewline) 208 runner.startAction(action3) 209 runner.finishAction(result3) 210} 211 212func actionsWithError(stat status.StatusOutput) { 213 action2WithError := &status.Action{Description: "action2", Outputs: []string{"f1", "f2"}, Command: "touch f1 f2"} 214 result2WithError := status.ActionResult{Action: action2WithError, Output: "error1\nerror2\n", Error: fmt.Errorf("error1")} 215 216 runner := newRunner(stat, 3) 217 runner.startAction(action1) 218 runner.finishAction(result1) 219 runner.startAction(action2WithError) 220 runner.finishAction(result2WithError) 221 runner.startAction(action3) 222 runner.finishAction(result3) 223} 224 225func actionWithEmptyDescription(stat status.StatusOutput) { 226 action1 := &status.Action{Command: "command1"} 227 result1 := status.ActionResult{Action: action1} 228 229 runner := newRunner(stat, 1) 230 runner.startAction(action1) 231 runner.finishAction(result1) 232} 233 234func actionsWithMessages(stat status.StatusOutput) { 235 runner := newRunner(stat, 2) 236 237 runner.startAction(action1) 238 runner.finishAction(result1) 239 240 stat.Message(status.VerboseLvl, "verbose") 241 stat.Message(status.StatusLvl, "status") 242 stat.Message(status.PrintLvl, "print") 243 stat.Message(status.ErrorLvl, "error") 244 245 runner.startAction(action2) 246 runner.finishAction(result2) 247} 248 249func actionWithLongDescription(stat status.StatusOutput) { 250 action1 := &status.Action{Description: "action with very long description to test eliding"} 251 result1 := status.ActionResult{Action: action1} 252 253 runner := newRunner(stat, 2) 254 255 runner.startAction(action1) 256 257 runner.finishAction(result1) 258} 259 260func actionWithOuptutWithAnsiCodes(stat status.StatusOutput) { 261 result1WithOutputWithAnsiCodes := status.ActionResult{Action: action1, Output: "\x1b[31mcolor\x1b[0m"} 262 263 runner := newRunner(stat, 1) 264 runner.startAction(action1) 265 runner.finishAction(result1WithOutputWithAnsiCodes) 266} 267 268func TestSmartStatusOutputWidthChange(t *testing.T) { 269 os.Setenv(tableHeightEnVar, "") 270 271 smart := &fakeSmartTerminal{termWidth: 40} 272 stat := NewStatusOutput(smart, "", false, false) 273 smartStat := stat.(*smartStatusOutput) 274 smartStat.sigwinchHandled = make(chan bool) 275 276 runner := newRunner(stat, 2) 277 278 action := &status.Action{Description: "action with very long description to test eliding"} 279 result := status.ActionResult{Action: action} 280 281 runner.startAction(action) 282 smart.termWidth = 30 283 // Fake a SIGWINCH 284 smartStat.sigwinch <- syscall.SIGWINCH 285 <-smartStat.sigwinchHandled 286 runner.finishAction(result) 287 288 stat.Flush() 289 290 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" 291 292 if g := smart.String(); g != w { 293 t.Errorf("want:\n%q\ngot:\n%q", w, g) 294 } 295} 296