github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	"os"
    26  	"strings"
    27  	"time"
    28  
    29  	"gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/progress"
    32  )
    33  
    34  type ansiSuite struct {
    35  	stdout *os.File
    36  }
    37  
    38  var _ = check.Suite(ansiSuite{})
    39  
    40  func (ansiSuite) TestNorm(c *check.C) {
    41  	msg := []rune(strings.Repeat("0123456789", 100))
    42  	high := []rune("🤗🤗🤗🤗🤗")
    43  	c.Assert(msg, check.HasLen, 1000)
    44  	for i := 1; i < 1000; i += 1 {
    45  		long := progress.Norm(i, msg)
    46  		short := progress.Norm(i, nil)
    47  		// a long message is truncated to fit
    48  		c.Check(long, check.HasLen, i)
    49  		c.Check(long[len(long)-1], check.Equals, rune('…'))
    50  		// a short message is padded to width
    51  		c.Check(short, check.HasLen, i)
    52  		c.Check(string(short), check.Equals, strings.Repeat(" ", i))
    53  		// high unicode? no problem
    54  		c.Check(progress.Norm(i, high), check.HasLen, i)
    55  	}
    56  	// check it doesn't panic for negative nor zero widths
    57  	c.Check(progress.Norm(0, []rune("hello")), check.HasLen, 0)
    58  	c.Check(progress.Norm(-10, []rune("hello")), check.HasLen, 0)
    59  }
    60  
    61  func (ansiSuite) TestPercent(c *check.C) {
    62  	p := &progress.ANSIMeter{}
    63  	for i := -1000.; i < 1000.; i += 5 {
    64  		p.SetTotal(i)
    65  		for j := -1000.; j < 1000.; j += 3 {
    66  			p.SetWritten(j)
    67  			percent := p.Percent()
    68  			c.Check(percent, check.HasLen, 4)
    69  			c.Check(percent[len(percent)-1:], check.Equals, "%")
    70  		}
    71  	}
    72  }
    73  
    74  func (ansiSuite) TestStart(c *check.C) {
    75  	var buf bytes.Buffer
    76  	defer progress.MockStdout(&buf)()
    77  	defer progress.MockTermWidth(func() int { return 80 })()
    78  
    79  	p := &progress.ANSIMeter{}
    80  	p.Start("0123456789", 100)
    81  	c.Check(p.GetTotal(), check.Equals, 100.)
    82  	c.Check(p.GetWritten(), check.Equals, 0.)
    83  	c.Check(buf.String(), check.Equals, progress.CursorInvisible)
    84  }
    85  
    86  func (ansiSuite) TestFinish(c *check.C) {
    87  	var buf bytes.Buffer
    88  	defer progress.MockStdout(&buf)()
    89  	defer progress.MockTermWidth(func() int { return 80 })()
    90  	p := &progress.ANSIMeter{}
    91  	p.Finished()
    92  	c.Check(buf.String(), check.Equals, fmt.Sprint(
    93  		"\r", // move cursor to start of line
    94  		progress.ExitAttributeMode, // turn off color, reverse, bold, anything
    95  		progress.CursorVisible,     // turn the cursor back on
    96  		progress.ClrEOL,            // and clear the rest of the line
    97  	))
    98  }
    99  
   100  func (ansiSuite) TestSetLayout(c *check.C) {
   101  	var buf bytes.Buffer
   102  	var width int
   103  	defer progress.MockStdout(&buf)()
   104  	defer progress.MockEmptyEscapes()()
   105  	defer progress.MockTermWidth(func() int { return width })()
   106  
   107  	p := &progress.ANSIMeter{}
   108  	msg := "0123456789"
   109  	ticker := time.NewTicker(time.Millisecond)
   110  	defer ticker.Stop()
   111  	p.Start(msg, 1E300)
   112  	for i := 1; i <= 80; i++ {
   113  		desc := check.Commentf("width %d", i)
   114  		width = i
   115  		buf.Reset()
   116  		<-ticker.C
   117  		p.Set(float64(i))
   118  		out := buf.String()
   119  		c.Check([]rune(out), check.HasLen, i+1, desc)
   120  		switch {
   121  		case i < len(msg):
   122  			c.Check(out, check.Equals, "\r"+msg[:i-1]+"…", desc)
   123  		case i <= 15:
   124  			c.Check(out, check.Equals, fmt.Sprintf("\r%*s", -i, msg), desc)
   125  		case i <= 20:
   126  			c.Check(out, check.Equals, fmt.Sprintf("\r%*s ages!", -(i-6), msg), desc)
   127  		case i <= 29:
   128  			c.Check(out, check.Equals, fmt.Sprintf("\r%*s   0%% ages!", -(i-11), msg), desc)
   129  		default:
   130  			c.Check(out, check.Matches, fmt.Sprintf("\r%*s   0%%  [ 0-9]{4}B/s ages!", -(i-20), msg), desc)
   131  		}
   132  	}
   133  }
   134  
   135  func (ansiSuite) TestSetEscapes(c *check.C) {
   136  	var buf bytes.Buffer
   137  	defer progress.MockStdout(&buf)()
   138  	defer progress.MockSimpleEscapes()()
   139  	defer progress.MockTermWidth(func() int { return 10 })()
   140  
   141  	p := &progress.ANSIMeter{}
   142  	msg := "0123456789"
   143  	p.Start(msg, 10)
   144  	for i := 0.; i <= 10; i++ {
   145  		buf.Reset()
   146  		p.Set(i)
   147  		// here we're using the fact that the message has the same
   148  		// length as p's total to make the test simpler :-)
   149  		expected := "\r<MR>" + msg[:int(i)] + "<ME>" + msg[int(i):]
   150  		c.Check(buf.String(), check.Equals, expected, check.Commentf("%g", i))
   151  	}
   152  }
   153  
   154  func (ansiSuite) TestSpin(c *check.C) {
   155  	termWidth := 9
   156  	var buf bytes.Buffer
   157  	defer progress.MockStdout(&buf)()
   158  	defer progress.MockSimpleEscapes()()
   159  	defer progress.MockTermWidth(func() int { return termWidth })()
   160  
   161  	p := &progress.ANSIMeter{}
   162  	msg := "0123456789"
   163  	c.Assert(len(msg), check.Equals, 10)
   164  	p.Start(msg, 10)
   165  
   166  	// term too narrow to fit msg
   167  	for i, s := range progress.Spinner {
   168  		buf.Reset()
   169  		p.Spin(msg)
   170  		expected := "\r" + msg[:8] + "…"
   171  		c.Check(buf.String(), check.Equals, expected, check.Commentf("%d (%s)", i, s))
   172  	}
   173  
   174  	// term fits msg but not spinner
   175  	termWidth = 11
   176  	for i, s := range progress.Spinner {
   177  		buf.Reset()
   178  		p.Spin(msg)
   179  		expected := "\r" + msg + " "
   180  		c.Check(buf.String(), check.Equals, expected, check.Commentf("%d (%s)", i, s))
   181  	}
   182  
   183  	// term fits msg and spinner
   184  	termWidth = 12
   185  	for i, s := range progress.Spinner {
   186  		buf.Reset()
   187  		p.Spin(msg)
   188  		expected := "\r" + msg + " " + s
   189  		c.Check(buf.String(), check.Equals, expected, check.Commentf("%d (%s)", i, s))
   190  	}
   191  }
   192  
   193  func (ansiSuite) TestNotify(c *check.C) {
   194  	var buf bytes.Buffer
   195  	var width int
   196  	defer progress.MockStdout(&buf)()
   197  	defer progress.MockSimpleEscapes()()
   198  	defer progress.MockTermWidth(func() int { return width })()
   199  
   200  	p := &progress.ANSIMeter{}
   201  	p.Start("working", 1E300)
   202  
   203  	width = 10
   204  	p.Set(0)
   205  	p.Notify("hello there")
   206  	p.Set(1)
   207  	c.Check(buf.String(), check.Equals, "<VI>"+ // the VI from Start()
   208  		"\r<MR><ME>working   "+ // the Set(0)
   209  		"\r<ME><CE>hello\n"+ // first line of the Notify (note it wrapped at word end)
   210  		"there\n"+
   211  		"\r<MR><ME>working   ") // the Set(1)
   212  
   213  	buf.Reset()
   214  	p.Set(0)
   215  	p.Notify("supercalifragilisticexpialidocious")
   216  	p.Set(1)
   217  	c.Check(buf.String(), check.Equals, ""+ // no Start() this time
   218  		"\r<MR><ME>working   "+ // the Set(0)
   219  		"\r<ME><CE>supercalif\n"+ // the Notify, word is too long so it's just split
   220  		"ragilistic\n"+
   221  		"expialidoc\n"+
   222  		"ious\n"+
   223  		"\r<MR><ME>working   ") // the Set(1)
   224  
   225  	buf.Reset()
   226  	width = 16
   227  	p.Set(0)
   228  	p.Notify("hello there")
   229  	p.Set(1)
   230  	c.Check(buf.String(), check.Equals, ""+ // no Start()
   231  		"\r<MR><ME>working    ages!"+ // the Set(0)
   232  		"\r<ME><CE>hello there\n"+ // first line of the Notify (no wrap!)
   233  		"\r<MR><ME>working    ages!") // the Set(1)
   234  
   235  }
   236  
   237  func (ansiSuite) TestWrite(c *check.C) {
   238  	var buf bytes.Buffer
   239  	defer progress.MockStdout(&buf)()
   240  	defer progress.MockSimpleEscapes()()
   241  	defer progress.MockTermWidth(func() int { return 10 })()
   242  
   243  	p := &progress.ANSIMeter{}
   244  	p.Start("123456789x", 10)
   245  	for i := 0; i < 10; i++ {
   246  		n, err := fmt.Fprintf(p, "%d", i)
   247  		c.Assert(err, check.IsNil)
   248  		c.Check(n, check.Equals, 1)
   249  	}
   250  
   251  	c.Check(buf.String(), check.Equals, strings.Join([]string{
   252  		"<VI>", // Start()
   253  		"\r<MR>1<ME>23456789x",
   254  		"\r<MR>12<ME>3456789x",
   255  		"\r<MR>123<ME>456789x",
   256  		"\r<MR>1234<ME>56789x",
   257  		"\r<MR>12345<ME>6789x",
   258  		"\r<MR>123456<ME>789x",
   259  		"\r<MR>1234567<ME>89x",
   260  		"\r<MR>12345678<ME>9x",
   261  		"\r<MR>123456789<ME>x",
   262  		"\r<MR>123456789x<ME>",
   263  	}, ""))
   264  }