github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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  }