...

Source file src/codeberg.org/tslocum/cview/textview_test.go

Documentation: codeberg.org/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  )
     8  
     9  const (
    10  	// 512 bytes
    11  	randomDataSize = 512
    12  
    13  	// Write randomData 64 times (32768 bytes) before appending
    14  	appendSetupWriteCount = 64
    15  )
    16  
    17  var (
    18  	randomData        = generateRandomData()
    19  	textViewTestCases = generateTestCases()
    20  )
    21  
    22  type textViewTestCase struct {
    23  	app      bool
    24  	color    bool
    25  	region   bool
    26  	scroll   bool
    27  	wrap     bool
    28  	wordwrap bool
    29  }
    30  
    31  func (c *textViewTestCase) String() string {
    32  	return fmt.Sprintf("Append=%c/Color=%c/Region=%c/Scroll=%c/Wrap=%c/WordWrap=%c", cl(c.app), cl(c.color), cl(c.region), cl(c.scroll), cl(c.wrap), cl(c.wordwrap))
    33  }
    34  
    35  func TestTextViewWrite(t *testing.T) {
    36  	t.Parallel()
    37  
    38  	for _, c := range textViewTestCases {
    39  		c := c // Capture
    40  
    41  		t.Run(c.String(), func(t *testing.T) {
    42  			t.Parallel()
    43  
    44  			var (
    45  				tv           = tvc(c)
    46  				expectedData []byte
    47  				n            int
    48  				err          error
    49  			)
    50  
    51  			if c.app {
    52  				expectedData, err = prepareAppendTextView(tv)
    53  				if err != nil {
    54  					t.Errorf("failed to prepare append TextView: %s", err)
    55  				}
    56  
    57  				expectedData = append(expectedData, randomData...)
    58  			} else {
    59  				expectedData = randomData
    60  			}
    61  
    62  			n, err = tv.Write(randomData)
    63  			if err != nil {
    64  				t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
    65  			} else if n != randomDataSize {
    66  				t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
    67  			}
    68  
    69  			contents := tv.GetText(false)
    70  			if len(contents) != len(expectedData) {
    71  				t.Errorf("failed to write: incorrect contents: expected %d bytes, got %d", len(contents), len(expectedData))
    72  			} else if !bytes.Equal([]byte(contents), expectedData) {
    73  				t.Errorf("failed to write: incorrect contents: values do not match")
    74  			}
    75  
    76  			tv.Clear()
    77  		})
    78  	}
    79  }
    80  
    81  func BenchmarkTextViewWrite(b *testing.B) {
    82  	for _, c := range textViewTestCases {
    83  		c := c // Capture
    84  
    85  		b.Run(c.String(), func(b *testing.B) {
    86  			var (
    87  				tv  = tvc(c)
    88  				n   int
    89  				err error
    90  			)
    91  
    92  			if c.app {
    93  				_, err = prepareAppendTextView(tv)
    94  				if err != nil {
    95  					b.Errorf("failed to prepare append TextView: %s", err)
    96  				}
    97  			}
    98  
    99  			b.ReportAllocs()
   100  			b.ResetTimer()
   101  
   102  			for i := 0; i < b.N; i++ {
   103  				n, err = tv.Write(randomData)
   104  				if err != nil {
   105  					b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   106  				} else if n != randomDataSize {
   107  					b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   108  				}
   109  
   110  				if !c.app {
   111  					b.StopTimer()
   112  					tv.Clear()
   113  					b.StartTimer()
   114  				}
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  func BenchmarkTextViewIndex(b *testing.B) {
   121  	for _, c := range textViewTestCases {
   122  		c := c // Capture
   123  
   124  		b.Run(c.String(), func(b *testing.B) {
   125  			var (
   126  				tv  = tvc(c)
   127  				n   int
   128  				err error
   129  			)
   130  
   131  			_, err = prepareAppendTextView(tv)
   132  			if err != nil {
   133  				b.Errorf("failed to prepare append TextView: %s", err)
   134  			}
   135  
   136  			n, err = tv.Write(randomData)
   137  			if err != nil {
   138  				b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   139  			} else if n != randomDataSize {
   140  				b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   141  			}
   142  
   143  			tv.index = nil
   144  			tv.reindexBuffer(80)
   145  
   146  			b.ReportAllocs()
   147  			b.ResetTimer()
   148  
   149  			for i := 0; i < b.N; i++ {
   150  				tv.index = nil
   151  				tv.reindexBuffer(80)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestTextViewGetText(t *testing.T) {
   158  	t.Parallel()
   159  
   160  	tv := NewTextView()
   161  	tv.SetDynamicColors(true)
   162  	tv.SetRegions(true)
   163  
   164  	n, err := tv.Write(randomData)
   165  	if err != nil {
   166  		t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   167  	} else if n != randomDataSize {
   168  		t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   169  	}
   170  
   171  	suffix := []byte(`["start"]outer[b]inner[-]outer[""]`)
   172  	suffixStripped := []byte("outerinnerouter")
   173  
   174  	n, err = tv.Write(suffix)
   175  	if err != nil {
   176  		t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   177  	}
   178  
   179  	if !bytes.Equal(tv.GetBytes(false), append(randomData, suffix...)) {
   180  		t.Error("failed to get un-stripped text: unexpected suffix")
   181  	}
   182  
   183  	if !bytes.Equal(tv.GetBytes(true), append(randomData, suffixStripped...)) {
   184  		t.Error("failed to get text stripped text: unexpected suffix")
   185  	}
   186  }
   187  
   188  func BenchmarkTextViewGetText(b *testing.B) {
   189  	for _, c := range textViewTestCases {
   190  		c := c // Capture
   191  
   192  		if c.app {
   193  			continue // Skip for this benchmark
   194  		}
   195  
   196  		b.Run(c.String(), func(b *testing.B) {
   197  			var (
   198  				tv  = tvc(c)
   199  				n   int
   200  				err error
   201  				v   []byte
   202  			)
   203  
   204  			_, err = prepareAppendTextView(tv)
   205  			if err != nil {
   206  				b.Errorf("failed to prepare append TextView: %s", err)
   207  			}
   208  
   209  			n, err = tv.Write(randomData)
   210  			if err != nil {
   211  				b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   212  			} else if n != randomDataSize {
   213  				b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   214  			}
   215  
   216  			v = tv.GetBytes(true)
   217  
   218  			b.ReportAllocs()
   219  			b.ResetTimer()
   220  
   221  			for i := 0; i < b.N; i++ {
   222  				v = tv.GetBytes(true)
   223  			}
   224  
   225  			_ = v
   226  		})
   227  	}
   228  }
   229  
   230  type textViewResult struct {
   231  	x     int
   232  	y     int
   233  	str   string
   234  	width int
   235  }
   236  
   237  type textViewRegionsTestCase struct {
   238  	text    string           // Text to test with.
   239  	normal  []textViewResult // How the text should appear normally.
   240  	escaped []textViewResult // How the text should appear when escaped.
   241  }
   242  
   243  var textViewHelloWorldResult = []textViewResult{
   244  	{x: 0, y: 0, str: "H", width: 1},
   245  	{x: 1, y: 0, str: "e", width: 1},
   246  	{x: 2, y: 0, str: "l", width: 1},
   247  	{x: 3, y: 0, str: "l", width: 1},
   248  	{x: 4, y: 0, str: "o", width: 1},
   249  	{x: 5, y: 0, str: ",", width: 1},
   250  	{x: 7, y: 0, str: "w", width: 1},
   251  	{x: 8, y: 0, str: "o", width: 1},
   252  	{x: 9, y: 0, str: "r", width: 1},
   253  	{x: 10, y: 0, str: "l", width: 1},
   254  	{x: 11, y: 0, str: "d", width: 1},
   255  	{x: 12, y: 0, str: "!", width: 1},
   256  }
   257  
   258  var textViewRegionsTestCases = []textViewRegionsTestCase{
   259  	{
   260  		text:    `Hello, world!`,
   261  		normal:  textViewHelloWorldResult,
   262  		escaped: textViewHelloWorldResult,
   263  	}, {
   264  		text: "[TEST\033[0m]\033[36mTEST",
   265  		normal: []textViewResult{
   266  			{x: 0, y: 0, str: "[", width: 1},
   267  			{x: 1, y: 0, str: "T", width: 1},
   268  			{x: 2, y: 0, str: "E", width: 1},
   269  			{x: 3, y: 0, str: "S", width: 1},
   270  			{x: 4, y: 0, str: "T", width: 1},
   271  			{x: 5, y: 0, str: "]", width: 1},
   272  			{x: 6, y: 0, str: "T", width: 1},
   273  			{x: 7, y: 0, str: "E", width: 1},
   274  			{x: 8, y: 0, str: "S", width: 1},
   275  			{x: 9, y: 0, str: "T", width: 1},
   276  		},
   277  		escaped: []textViewResult{
   278  			{x: 0, y: 0, str: "[", width: 1},
   279  			{x: 1, y: 0, str: "T", width: 1},
   280  			{x: 2, y: 0, str: "E", width: 1},
   281  			{x: 3, y: 0, str: "S", width: 1},
   282  			{x: 4, y: 0, str: "T", width: 1},
   283  			{x: 5, y: 0, str: "[", width: 1},
   284  			{x: 6, y: 0, str: "]", width: 1},
   285  			{x: 7, y: 0, str: "T", width: 1},
   286  			{x: 8, y: 0, str: "E", width: 1},
   287  			{x: 9, y: 0, str: "S", width: 1},
   288  			{x: 10, y: 0, str: "T", width: 1},
   289  		},
   290  	},
   291  }
   292  
   293  func TestTextViewANSI(t *testing.T) {
   294  	t.Parallel()
   295  
   296  	for j := 0; j < 2; j++ {
   297  		for i, c := range textViewRegionsTestCases {
   298  			label := "Normal"
   299  			expectedResult := c.normal
   300  			if j == 1 {
   301  				label = "Escaped"
   302  				expectedResult = c.escaped
   303  			}
   304  
   305  			t.Run(fmt.Sprintf("%s/%d", label, i+1), func(t *testing.T) {
   306  				t.Parallel()
   307  
   308  				tv := NewTextView()
   309  				tv.SetDynamicColors(true)
   310  
   311  				app, err := newTestApp(tv)
   312  				if err != nil {
   313  					t.Errorf("failed to initialize Application: %s", err)
   314  				}
   315  				app.screen.SetSize(screenW, screenH)
   316  				tv.SetRect(0, 0, screenW, screenH)
   317  
   318  				content := c.text
   319  				if j == 1 {
   320  					content = Escape(content)
   321  				}
   322  
   323  				content = TranslateANSI(content)
   324  
   325  				tv.SetText(content)
   326  
   327  				tv.Draw(app.screen)
   328  				var expected textViewResult
   329  				for y := 0; y < screenH; y++ {
   330  					for x := 0; x < screenW; x++ {
   331  						expected = textViewResult{
   332  							str:   " ",
   333  							width: 1,
   334  						}
   335  						for _, nc := range expectedResult {
   336  							if nc.x == x && nc.y == y {
   337  								expected = nc
   338  								break
   339  							}
   340  						}
   341  
   342  						str, _, width := app.screen.Get(x, y)
   343  						if str != expected.str {
   344  							t.Errorf("unexpected str at %d, %d: expected '%s', got '%s'", x, y, expected.str, str)
   345  						}
   346  						if width != expected.width {
   347  							t.Errorf("unexpected width at %d, %d: expected %d, got %d", x, y, expected.width, width)
   348  						}
   349  					}
   350  				}
   351  			})
   352  		}
   353  	}
   354  }
   355  
   356  func TestTextViewDraw(t *testing.T) {
   357  	t.Parallel()
   358  
   359  	for _, c := range textViewTestCases {
   360  		c := c // Capture
   361  
   362  		t.Run(c.String(), func(t *testing.T) {
   363  			t.Parallel()
   364  
   365  			tv := tvc(c)
   366  
   367  			app, err := newTestApp(tv)
   368  			if err != nil {
   369  				t.Errorf("failed to initialize Application: %s", err)
   370  			}
   371  
   372  			if c.app {
   373  				_, err = prepareAppendTextView(tv)
   374  				if err != nil {
   375  					t.Errorf("failed to prepare append TextView: %s", err)
   376  				}
   377  
   378  				tv.Draw(app.screen)
   379  			}
   380  
   381  			n, err := tv.Write(randomData)
   382  			if err != nil {
   383  				t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   384  			} else if n != randomDataSize {
   385  				t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   386  			}
   387  
   388  			tv.Draw(app.screen)
   389  		})
   390  	}
   391  }
   392  
   393  func BenchmarkTextViewDraw(b *testing.B) {
   394  	for _, c := range textViewTestCases {
   395  		c := c // Capture
   396  
   397  		b.Run(c.String(), func(b *testing.B) {
   398  			tv := tvc(c)
   399  
   400  			app, err := newTestApp(tv)
   401  			if err != nil {
   402  				b.Errorf("failed to initialize Application: %s", err)
   403  			}
   404  
   405  			if c.app {
   406  				_, err = prepareAppendTextView(tv)
   407  				if err != nil {
   408  					b.Errorf("failed to prepare append TextView: %s", err)
   409  				}
   410  
   411  				tv.Draw(app.screen)
   412  			}
   413  
   414  			n, err := tv.Write(randomData)
   415  			if err != nil {
   416  				b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   417  			} else if n != randomDataSize {
   418  				b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   419  			}
   420  
   421  			tv.Draw(app.screen)
   422  
   423  			b.ReportAllocs()
   424  			b.ResetTimer()
   425  
   426  			for i := 0; i < b.N; i++ {
   427  				tv.Draw(app.screen)
   428  			}
   429  		})
   430  	}
   431  }
   432  
   433  func TestTextViewMaxLines(t *testing.T) {
   434  	t.Parallel()
   435  
   436  	tv := NewTextView()
   437  
   438  	// append 100 lines with no limit set:
   439  	for i := 0; i < 100; i++ {
   440  		_, err := tv.Write([]byte(fmt.Sprintf("L%d\n", i)))
   441  		if err != nil {
   442  			t.Errorf("failed to write to TextView: %s", err)
   443  		}
   444  	}
   445  
   446  	// retrieve the total text and see we have the 100 lines:
   447  	count := bytes.Count(tv.GetBytes(true), []byte("\n"))
   448  	if count != 100 {
   449  		t.Errorf("expected 100 lines, got %d", count)
   450  	}
   451  
   452  	// now set the maximum lines to 20, this should clip the buffer:
   453  	tv.SetMaxLines(20)
   454  	// verify buffer was clipped:
   455  	count = len(bytes.Split(tv.GetBytes(true), []byte("\n")))
   456  	if count != 20 {
   457  		t.Errorf("expected 20 lines, got %d", count)
   458  	}
   459  
   460  	// append 100 more lines:
   461  	for i := 100; i < 200; i++ {
   462  		_, err := tv.Write([]byte(fmt.Sprintf("L%d\n", i)))
   463  		if err != nil {
   464  			t.Errorf("failed to write to TextView: %s", err)
   465  		}
   466  	}
   467  
   468  	// Sice max lines is set to 20, we should still get 20 lines:
   469  	txt := tv.GetBytes(true)
   470  	lines := bytes.Split(txt, []byte("\n"))
   471  	count = len(lines)
   472  	if count != 20 {
   473  		t.Errorf("expected 20 lines, got %d", count)
   474  	}
   475  
   476  	// and those 20 lines should be the last ones:
   477  	if !bytes.Equal(lines[0], []byte("L181")) {
   478  		t.Errorf("expected to get L181, got %s", lines[0])
   479  	}
   480  }
   481  
   482  func generateTestCases() []*textViewTestCase {
   483  	var cases []*textViewTestCase
   484  	for i := 0; i < 2; i++ {
   485  		app := i == 1
   486  		for i := 0; i < 2; i++ {
   487  			color := i == 1
   488  			for i := 0; i < 2; i++ {
   489  				region := i == 1
   490  				for i := 0; i < 2; i++ {
   491  					scroll := i == 1
   492  					for i := 0; i < 2; i++ {
   493  						wrap := i == 1
   494  						for i := 0; i < 2; i++ {
   495  							wordwrap := i == 1
   496  							if !wrap && wordwrap {
   497  								continue // WordWrap requires Wrap
   498  							}
   499  							cases = append(cases, &textViewTestCase{app, color, region, scroll, wrap, wordwrap})
   500  						}
   501  					}
   502  				}
   503  			}
   504  		}
   505  	}
   506  	return cases
   507  }
   508  
   509  func generateRandomData() []byte {
   510  	var (
   511  		b bytes.Buffer
   512  		r = 33
   513  	)
   514  
   515  	for i := 0; i < randomDataSize; i++ {
   516  		if i%80 == 0 && i <= 160 {
   517  			b.WriteRune('\n')
   518  		} else if i%7 == 0 {
   519  			b.WriteRune(' ')
   520  		} else {
   521  			b.WriteRune(rune(r))
   522  		}
   523  
   524  		r++
   525  		if r == 127 {
   526  			r = 33
   527  		}
   528  	}
   529  
   530  	return b.Bytes()
   531  }
   532  
   533  func tvc(c *textViewTestCase) *TextView {
   534  	tv := NewTextView()
   535  	tv.SetDynamicColors(c.color)
   536  	tv.SetRegions(c.region)
   537  	tv.SetScrollable(c.scroll)
   538  	tv.SetWrap(c.wrap)
   539  	tv.SetWordWrap(c.wordwrap)
   540  	return tv
   541  }
   542  
   543  func cl(v bool) rune {
   544  	if v {
   545  		return 'Y'
   546  	}
   547  	return 'N'
   548  }
   549  
   550  func prepareAppendTextView(t *TextView) ([]byte, error) {
   551  	var b []byte
   552  	for i := 0; i < appendSetupWriteCount; i++ {
   553  		b = append(b, randomData...)
   554  
   555  		n, err := t.Write(randomData)
   556  		if err != nil {
   557  			return nil, fmt.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
   558  		} else if n != randomDataSize {
   559  			return nil, fmt.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
   560  		}
   561  	}
   562  
   563  	return b, nil
   564  }
   565  

View as plain text