github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/progress/ansimeter_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package progress_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "strings" 26 "time" 27 28 "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/progress" 31 ) 32 33 type ansiSuite struct{} 34 35 var _ = check.Suite(ansiSuite{}) 36 37 func (ansiSuite) TestNorm(c *check.C) { 38 msg := []rune(strings.Repeat("0123456789", 100)) 39 high := []rune("π€π€π€π€π€") 40 c.Assert(msg, check.HasLen, 1000) 41 for i := 1; i < 1000; i += 1 { 42 long := progress.Norm(i, msg) 43 short := progress.Norm(i, nil) 44 // a long message is truncated to fit 45 c.Check(long, check.HasLen, i) 46 c.Check(long[len(long)-1], check.Equals, rune('β¦')) 47 // a short message is padded to width 48 c.Check(short, check.HasLen, i) 49 c.Check(string(short), check.Equals, strings.Repeat(" ", i)) 50 // high unicode? no problem 51 c.Check(progress.Norm(i, high), check.HasLen, i) 52 } 53 // check it doesn't panic for negative nor zero widths 54 c.Check(progress.Norm(0, []rune("hello")), check.HasLen, 0) 55 c.Check(progress.Norm(-10, []rune("hello")), check.HasLen, 0) 56 } 57 58 func (ansiSuite) TestPercent(c *check.C) { 59 p := &progress.ANSIMeter{} 60 for i := -1000.; i < 1000.; i += 5 { 61 p.SetTotal(i) 62 for j := -1000.; j < 1000.; j += 3 { 63 p.SetWritten(j) 64 percent := p.Percent() 65 c.Check(percent, check.HasLen, 4) 66 c.Check(percent[len(percent)-1:], check.Equals, "%") 67 } 68 } 69 } 70 71 func (ansiSuite) TestStart(c *check.C) { 72 var buf bytes.Buffer 73 defer progress.MockStdout(&buf)() 74 defer progress.MockTermWidth(func() int { return 80 })() 75 76 p := &progress.ANSIMeter{} 77 p.Start("0123456789", 100) 78 c.Check(p.GetTotal(), check.Equals, 100.) 79 c.Check(p.GetWritten(), check.Equals, 0.) 80 c.Check(buf.String(), check.Equals, progress.CursorInvisible) 81 } 82 83 func (ansiSuite) TestFinish(c *check.C) { 84 var buf bytes.Buffer 85 defer progress.MockStdout(&buf)() 86 defer progress.MockTermWidth(func() int { return 80 })() 87 p := &progress.ANSIMeter{} 88 p.Finished() 89 c.Check(buf.String(), check.Equals, fmt.Sprint( 90 "\r", // move cursor to start of line 91 progress.ExitAttributeMode, // turn off color, reverse, bold, anything 92 progress.CursorVisible, // turn the cursor back on 93 progress.ClrEOL, // and clear the rest of the line 94 )) 95 } 96 97 func (ansiSuite) TestSetLayout(c *check.C) { 98 var buf bytes.Buffer 99 var width int 100 defer progress.MockStdout(&buf)() 101 defer progress.MockEmptyEscapes()() 102 defer progress.MockTermWidth(func() int { return width })() 103 104 p := &progress.ANSIMeter{} 105 msg := "0123456789" 106 ticker := time.NewTicker(time.Millisecond) 107 defer ticker.Stop() 108 p.Start(msg, 1e300) 109 for i := 1; i <= 80; i++ { 110 desc := check.Commentf("width %d", i) 111 width = i 112 buf.Reset() 113 <-ticker.C 114 p.Set(float64(i)) 115 out := buf.String() 116 c.Check([]rune(out), check.HasLen, i+1, desc) 117 switch { 118 case i < len(msg): 119 c.Check(out, check.Equals, "\r"+msg[:i-1]+"β¦", desc) 120 case i <= 15: 121 c.Check(out, check.Equals, fmt.Sprintf("\r%*s", -i, msg), desc) 122 case i <= 20: 123 c.Check(out, check.Equals, fmt.Sprintf("\r%*s ages!", -(i-6), msg), desc) 124 case i <= 29: 125 c.Check(out, check.Equals, fmt.Sprintf("\r%*s 0%% ages!", -(i-11), msg), desc) 126 default: 127 c.Check(out, check.Matches, fmt.Sprintf("\r%*s 0%% [ 0-9]{4}B/s ages!", -(i-20), msg), desc) 128 } 129 } 130 } 131 132 func (ansiSuite) TestSetLayoutMultibyte(c *check.C) { 133 var buf bytes.Buffer 134 var duration string 135 var msg = "0123456789" 136 defer progress.MockStdout(&buf)() 137 defer progress.MockEmptyEscapes()() 138 defer progress.MockTermWidth(func() int { return 80 })() 139 defer progress.MockFormatDuration(func(_ float64) string { 140 return duration 141 })() 142 143 for _, dstr := range []string{"ΠΌ", "θͺ"} { 144 duration = dstr 145 buf.Reset() 146 147 p := &progress.ANSIMeter{} 148 p.Start(msg, 1e300) 149 p.Set(0.99 * 1e300) 150 out := buf.String() 151 c.Check([]rune(out), check.HasLen, 80+1, check.Commentf("unexpected length: %v", len(out))) 152 c.Check(out, check.Matches, 153 fmt.Sprintf("\r0123456789 \\s+ 99%% +[0-9]+(\\.[0-9]+)?[kMGTPEZY]?B/s %s", dstr)) 154 } 155 } 156 157 func (ansiSuite) TestSetEscapes(c *check.C) { 158 var buf bytes.Buffer 159 defer progress.MockStdout(&buf)() 160 defer progress.MockSimpleEscapes()() 161 defer progress.MockTermWidth(func() int { return 10 })() 162 163 p := &progress.ANSIMeter{} 164 msg := "0123456789" 165 p.Start(msg, 10) 166 for i := 0.; i <= 10; i++ { 167 buf.Reset() 168 p.Set(i) 169 // here we're using the fact that the message has the same 170 // length as p's total to make the test simpler :-) 171 expected := "\r<MR>" + msg[:int(i)] + "<ME>" + msg[int(i):] 172 c.Check(buf.String(), check.Equals, expected, check.Commentf("%g", i)) 173 } 174 } 175 176 func (ansiSuite) TestSpin(c *check.C) { 177 termWidth := 9 178 var buf bytes.Buffer 179 defer progress.MockStdout(&buf)() 180 defer progress.MockSimpleEscapes()() 181 defer progress.MockTermWidth(func() int { return termWidth })() 182 183 p := &progress.ANSIMeter{} 184 msg := "0123456789" 185 c.Assert(len(msg), check.Equals, 10) 186 p.Start(msg, 10) 187 188 // term too narrow to fit msg 189 for i, s := range progress.Spinner { 190 buf.Reset() 191 p.Spin(msg) 192 expected := "\r" + msg[:8] + "β¦" 193 c.Check(buf.String(), check.Equals, expected, check.Commentf("%d (%s)", i, s)) 194 } 195 196 // term fits msg but not spinner 197 termWidth = 11 198 for i, s := range progress.Spinner { 199 buf.Reset() 200 p.Spin(msg) 201 expected := "\r" + msg + " " 202 c.Check(buf.String(), check.Equals, expected, check.Commentf("%d (%s)", i, s)) 203 } 204 205 // term fits msg and spinner 206 termWidth = 12 207 for i, s := range progress.Spinner { 208 buf.Reset() 209 p.Spin(msg) 210 expected := "\r" + msg + " " + s 211 c.Check(buf.String(), check.Equals, expected, check.Commentf("%d (%s)", i, s)) 212 } 213 } 214 215 func (ansiSuite) TestNotify(c *check.C) { 216 var buf bytes.Buffer 217 var width int 218 defer progress.MockStdout(&buf)() 219 defer progress.MockSimpleEscapes()() 220 defer progress.MockTermWidth(func() int { return width })() 221 222 p := &progress.ANSIMeter{} 223 p.Start("working", 1e300) 224 225 width = 10 226 p.Set(0) 227 p.Notify("hello there") 228 p.Set(1) 229 c.Check(buf.String(), check.Equals, "<VI>"+ // the VI from Start() 230 "\r<MR><ME>working "+ // the Set(0) 231 "\r<ME><CE>hello\n"+ // first line of the Notify (note it wrapped at word end) 232 "there\n"+ 233 "\r<MR><ME>working ") // the Set(1) 234 235 buf.Reset() 236 p.Set(0) 237 p.Notify("supercalifragilisticexpialidocious") 238 p.Set(1) 239 c.Check(buf.String(), check.Equals, ""+ // no Start() this time 240 "\r<MR><ME>working "+ // the Set(0) 241 "\r<ME><CE>supercalif\n"+ // the Notify, word is too long so it's just split 242 "ragilistic\n"+ 243 "expialidoc\n"+ 244 "ious\n"+ 245 "\r<MR><ME>working ") // the Set(1) 246 247 buf.Reset() 248 width = 16 249 p.Set(0) 250 p.Notify("hello there") 251 p.Set(1) 252 c.Check(buf.String(), check.Equals, ""+ // no Start() 253 "\r<MR><ME>working ages!"+ // the Set(0) 254 "\r<ME><CE>hello there\n"+ // first line of the Notify (no wrap!) 255 "\r<MR><ME>working ages!") // the Set(1) 256 257 } 258 259 func (ansiSuite) TestWrite(c *check.C) { 260 var buf bytes.Buffer 261 defer progress.MockStdout(&buf)() 262 defer progress.MockSimpleEscapes()() 263 defer progress.MockTermWidth(func() int { return 10 })() 264 265 p := &progress.ANSIMeter{} 266 p.Start("123456789x", 10) 267 for i := 0; i < 10; i++ { 268 n, err := fmt.Fprintf(p, "%d", i) 269 c.Assert(err, check.IsNil) 270 c.Check(n, check.Equals, 1) 271 } 272 273 c.Check(buf.String(), check.Equals, strings.Join([]string{ 274 "<VI>", // Start() 275 "\r<MR>1<ME>23456789x", 276 "\r<MR>12<ME>3456789x", 277 "\r<MR>123<ME>456789x", 278 "\r<MR>1234<ME>56789x", 279 "\r<MR>12345<ME>6789x", 280 "\r<MR>123456<ME>789x", 281 "\r<MR>1234567<ME>89x", 282 "\r<MR>12345678<ME>9x", 283 "\r<MR>123456789<ME>x", 284 "\r<MR>123456789x<ME>", 285 }, "")) 286 }