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\n\x1b[31m\x1b[1mFAILED:\x1b[0m 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\n\x1b[31m\x1b[1mFAILED:\x1b[0m 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: actionWithOutputWithAnsiCodes, 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\x1b[31mcolor message\x1b[0m\n", 86 simple: "[100% 1/1] action1\ncolor\ncolor message\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, 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, 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, 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 actionWithOutputWithAnsiCodes(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 stat.Message(status.PrintLvl, "\x1b[31mcolor message\x1b[0m") 268} 269 270func TestSmartStatusOutputWidthChange(t *testing.T) { 271 os.Setenv(tableHeightEnVar, "") 272 273 smart := &fakeSmartTerminal{termWidth: 40} 274 stat := NewStatusOutput(smart, "", false, false, false) 275 smartStat := stat.(*smartStatusOutput) 276 smartStat.sigwinchHandled = make(chan bool) 277 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 285 // Fake a SIGWINCH 286 smartStat.sigwinch <- syscall.SIGWINCH 287 <-smartStat.sigwinchHandled 288 runner.finishAction(result) 289 290 stat.Flush() 291 292 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" 293 294 if g := smart.String(); g != w { 295 t.Errorf("want:\n%q\ngot:\n%q", w, g) 296 } 297} 298 299func TestSmartStatusDoesntHideAfterSucecss(t *testing.T) { 300 os.Setenv(tableHeightEnVar, "") 301 302 smart := &fakeSmartTerminal{termWidth: 40} 303 stat := NewStatusOutput(smart, "", false, false, false) 304 smartStat := stat.(*smartStatusOutput) 305 smartStat.sigwinchHandled = make(chan bool) 306 307 runner := newRunner(stat, 2) 308 309 action1 := &status.Action{Description: "action1"} 310 result1 := status.ActionResult{ 311 Action: action1, 312 Output: "Output1", 313 } 314 315 action2 := &status.Action{Description: "action2"} 316 result2 := status.ActionResult{ 317 Action: action2, 318 Output: "Output2", 319 } 320 321 runner.startAction(action1) 322 runner.startAction(action2) 323 runner.finishAction(result1) 324 runner.finishAction(result2) 325 326 stat.Flush() 327 328 w := "\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\nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nOutput2\n" 329 330 if g := smart.String(); g != w { 331 t.Errorf("want:\n%q\ngot:\n%q", w, g) 332 } 333} 334 335func TestSmartStatusHideAfterFailure(t *testing.T) { 336 os.Setenv(tableHeightEnVar, "") 337 338 smart := &fakeSmartTerminal{termWidth: 40} 339 stat := NewStatusOutput(smart, "", false, false, false) 340 smartStat := stat.(*smartStatusOutput) 341 smartStat.sigwinchHandled = make(chan bool) 342 343 runner := newRunner(stat, 2) 344 345 action1 := &status.Action{Description: "action1"} 346 result1 := status.ActionResult{ 347 Action: action1, 348 Output: "Output1", 349 Error: fmt.Errorf("Error1"), 350 } 351 352 action2 := &status.Action{Description: "action2"} 353 result2 := status.ActionResult{ 354 Action: action2, 355 Output: "Output2", 356 } 357 358 runner.startAction(action1) 359 runner.startAction(action2) 360 runner.finishAction(result1) 361 runner.finishAction(result2) 362 363 stat.Flush() 364 365 w := "\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\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n" 366 367 if g := smart.String(); g != w { 368 t.Errorf("want:\n%q\ngot:\n%q", w, g) 369 } 370} 371 372func TestSmartStatusHideAfterFailurePlural(t *testing.T) { 373 os.Setenv(tableHeightEnVar, "") 374 375 smart := &fakeSmartTerminal{termWidth: 40} 376 stat := NewStatusOutput(smart, "", false, false, false) 377 smartStat := stat.(*smartStatusOutput) 378 smartStat.sigwinchHandled = make(chan bool) 379 380 runner := newRunner(stat, 2) 381 382 action1 := &status.Action{Description: "action1"} 383 result1 := status.ActionResult{ 384 Action: action1, 385 Output: "Output1", 386 Error: fmt.Errorf("Error1"), 387 } 388 389 action2 := &status.Action{Description: "action2"} 390 result2 := status.ActionResult{ 391 Action: action2, 392 Output: "Output2", 393 } 394 395 action3 := &status.Action{Description: "action3"} 396 result3 := status.ActionResult{ 397 Action: action3, 398 Output: "Output3", 399 } 400 401 runner.startAction(action1) 402 runner.startAction(action2) 403 runner.startAction(action3) 404 runner.finishAction(result1) 405 runner.finishAction(result2) 406 runner.finishAction(result3) 407 408 stat.Flush() 409 410 w := "\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[ 0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n" 411 412 if g := smart.String(); g != w { 413 t.Errorf("want:\n%q\ngot:\n%q", w, g) 414 } 415} 416 417func TestSmartStatusDontHideErrorAfterFailure(t *testing.T) { 418 os.Setenv(tableHeightEnVar, "") 419 420 smart := &fakeSmartTerminal{termWidth: 40} 421 stat := NewStatusOutput(smart, "", false, false, false) 422 smartStat := stat.(*smartStatusOutput) 423 smartStat.sigwinchHandled = make(chan bool) 424 425 runner := newRunner(stat, 2) 426 427 action1 := &status.Action{Description: "action1"} 428 result1 := status.ActionResult{ 429 Action: action1, 430 Output: "Output1", 431 Error: fmt.Errorf("Error1"), 432 } 433 434 action2 := &status.Action{Description: "action2"} 435 result2 := status.ActionResult{ 436 Action: action2, 437 Output: "Output2", 438 Error: fmt.Errorf("Error1"), 439 } 440 441 runner.startAction(action1) 442 runner.startAction(action2) 443 runner.finishAction(result1) 444 runner.finishAction(result2) 445 446 stat.Flush() 447 448 w := "\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\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput2\n" 449 450 if g := smart.String(); g != w { 451 t.Errorf("want:\n%q\ngot:\n%q", w, g) 452 } 453} 454