gitee.com/quant1x/gox@v1.21.2/text/runewidth/runewidth_test.go (about)

     1  //go:build !js && !appengine
     2  // +build !js,!appengine
     3  
     4  package runewidth
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"fmt"
     9  	"os"
    10  	"sort"
    11  	"testing"
    12  	"unicode/utf8"
    13  )
    14  
    15  var _ sort.Interface = (*table)(nil) // ensure that type "table" does implement sort.Interface
    16  
    17  func init() {
    18  	os.Setenv("RUNEWIDTH_EASTASIAN", "")
    19  	handleEnv()
    20  }
    21  
    22  func (t table) Len() int {
    23  	return len(t)
    24  }
    25  
    26  func (t table) Less(i, j int) bool {
    27  	return t[i].first < t[j].first
    28  }
    29  
    30  func (t *table) Swap(i, j int) {
    31  	(*t)[i], (*t)[j] = (*t)[j], (*t)[i]
    32  }
    33  
    34  type tableInfo struct {
    35  	tbl     table
    36  	name    string
    37  	wantN   int
    38  	wantSHA string
    39  }
    40  
    41  var tables = []tableInfo{
    42  	{private, "private", 137468, "a4a641206dc8c5de80bd9f03515a54a706a5a4904c7684dc6a33d65c967a51b2"},
    43  	{nonprint, "nonprint", 2143, "288904683eb225e7c4c0bd3ee481b53e8dace404ec31d443afdbc4d13729fe95"},
    44  	{combining, "combining", 465, "3cce13deb5e23f9f7327f2b1ef162328285a7dcf277a98302a8f7cdd43971268"},
    45  	{doublewidth, "doublewidth", 182440, "3d16eda8650dc2c92d6318d32f0b4a74fda5a278db2d4544b1dd65863394823c"},
    46  	{ambiguous, "ambiguous", 138739, "d05e339a10f296de6547ff3d6c5aee32f627f6555477afebd4a3b7e3cf74c9e3"},
    47  	{emoji, "emoji", 3535, "9ec17351601d49c535658de8d129c1d0ccda2e620669fc39a2faaee7dedcef6d"},
    48  	{narrow, "narrow", 111, "fa897699c5e3cd9141c638d539331b0bdd508b874e22996c5e929767d455fc5a"},
    49  	{neutral, "neutral", 27333, "5455f5e75c307f70b4e9b2384dc5a8bcd91a4c5e2b24b2b185dfad4d860ee5c2"},
    50  }
    51  
    52  func TestTableChecksums(t *testing.T) {
    53  	for _, ti := range tables {
    54  		gotN := 0
    55  		buf := make([]byte, utf8.MaxRune+1)
    56  		for r := rune(0); r <= utf8.MaxRune; r++ {
    57  			if inTable(r, ti.tbl) {
    58  				gotN++
    59  				buf[r] = 1
    60  			}
    61  		}
    62  		gotSHA := fmt.Sprintf("%x", sha256.Sum256(buf))
    63  		if gotN != ti.wantN || gotSHA != ti.wantSHA {
    64  			t.Errorf("table = %s,\n\tn = %d want %d,\n\tsha256 = %s want %s", ti.name, gotN, ti.wantN, gotSHA, ti.wantSHA)
    65  		}
    66  	}
    67  }
    68  
    69  func TestRuneWidthChecksums(t *testing.T) {
    70  	var testcases = []struct {
    71  		name           string
    72  		eastAsianWidth bool
    73  		wantSHA        string
    74  	}{
    75  		{"ea-no", false, "4eb632b105d3b2c800dda9141381d0b8a95250a3a5c7f1a5ca2c4d4daaa85234"},
    76  		{"ea-yes", true, "c2ddc3bdf42d81d4c23050e21eda46eb639b38b15322d35e8eb6c26f3b83ce92"},
    77  	}
    78  
    79  	for _, testcase := range testcases {
    80  		c := NewCondition()
    81  		c.EastAsianWidth = testcase.eastAsianWidth
    82  		buf := make([]byte, utf8.MaxRune+1)
    83  		for r := rune(0); r <= utf8.MaxRune; r++ {
    84  			buf[r] = byte(c.RuneWidth(r))
    85  		}
    86  		gotSHA := fmt.Sprintf("%x", sha256.Sum256(buf))
    87  		if gotSHA != testcase.wantSHA {
    88  			t.Errorf("TestRuneWidthChecksums = %s,\n\tsha256 = %s want %s",
    89  				testcase.name, gotSHA, testcase.wantSHA)
    90  		}
    91  
    92  		// Test with LUT
    93  		c.CreateLUT()
    94  		for r := rune(0); r <= utf8.MaxRune; r++ {
    95  			buf[r] = byte(c.RuneWidth(r))
    96  		}
    97  		gotSHA = fmt.Sprintf("%x", sha256.Sum256(buf))
    98  		if gotSHA != testcase.wantSHA {
    99  			t.Errorf("TestRuneWidthChecksums = %s,\n\tsha256 = %s want %s",
   100  				testcase.name, gotSHA, testcase.wantSHA)
   101  		}
   102  	}
   103  }
   104  
   105  func TestDefaultLUT(t *testing.T) {
   106  	var testcases = []struct {
   107  		name           string
   108  		eastAsianWidth bool
   109  		wantSHA        string
   110  	}{
   111  		{"ea-no", false, "4eb632b105d3b2c800dda9141381d0b8a95250a3a5c7f1a5ca2c4d4daaa85234"},
   112  		{"ea-yes", true, "c2ddc3bdf42d81d4c23050e21eda46eb639b38b15322d35e8eb6c26f3b83ce92"},
   113  	}
   114  
   115  	old := os.Getenv("RUNEWIDTH_EASTASIAN")
   116  	defer os.Setenv("RUNEWIDTH_EASTASIAN", old)
   117  
   118  	CreateLUT()
   119  	for _, testcase := range testcases {
   120  		c := DefaultCondition
   121  
   122  		if testcase.eastAsianWidth {
   123  			os.Setenv("RUNEWIDTH_EASTASIAN", "1")
   124  		} else {
   125  			os.Setenv("RUNEWIDTH_EASTASIAN", "0")
   126  		}
   127  		handleEnv()
   128  
   129  		buf := make([]byte, utf8.MaxRune+1)
   130  		for r := rune(0); r <= utf8.MaxRune; r++ {
   131  			buf[r] = byte(c.RuneWidth(r))
   132  		}
   133  		gotSHA := fmt.Sprintf("%x", sha256.Sum256(buf))
   134  		if gotSHA != testcase.wantSHA {
   135  			t.Errorf("TestRuneWidthChecksums = %s,\n\tsha256 = %s want %s",
   136  				testcase.name, gotSHA, testcase.wantSHA)
   137  		}
   138  	}
   139  	// Remove for other tests.
   140  	DefaultCondition.combinedLut = nil
   141  }
   142  
   143  func checkInterval(first, last rune) bool {
   144  	return first >= 0 && first <= utf8.MaxRune &&
   145  		last >= 0 && last <= utf8.MaxRune &&
   146  		first <= last
   147  }
   148  
   149  func isCompact(t *testing.T, ti *tableInfo) bool {
   150  	tbl := ti.tbl
   151  	for i := range tbl {
   152  		e := tbl[i]
   153  		if !checkInterval(e.first, e.last) { // sanity check
   154  			t.Errorf("table invalid: table = %s index = %d %v", ti.name, i, e)
   155  			return false
   156  		}
   157  		if i+1 < len(tbl) && e.last+1 >= tbl[i+1].first { // can be combined into one entry
   158  			t.Errorf("table not compact: table = %s index = %d %v %v", ti.name, i, e, tbl[i+1])
   159  			return false
   160  		}
   161  	}
   162  	return true
   163  }
   164  
   165  func TestSorted(t *testing.T) {
   166  	for _, ti := range tables {
   167  		if !sort.IsSorted(&ti.tbl) {
   168  			t.Errorf("table not sorted: %s", ti.name)
   169  		}
   170  		if !isCompact(t, &ti) {
   171  			t.Errorf("table not compact: %s", ti.name)
   172  		}
   173  	}
   174  }
   175  
   176  var runewidthtests = []struct {
   177  	in     rune
   178  	out    int
   179  	eaout  int
   180  	nseout int
   181  }{
   182  	{'世', 2, 2, 2},
   183  	{'界', 2, 2, 2},
   184  	{'セ', 1, 1, 1},
   185  	{'カ', 1, 1, 1},
   186  	{'イ', 1, 1, 1},
   187  	{'☆', 1, 2, 2}, // double width in ambiguous
   188  	{'☺', 1, 1, 2},
   189  	{'☻', 1, 1, 2},
   190  	{'♥', 1, 2, 2},
   191  	{'♦', 1, 1, 2},
   192  	{'♣', 1, 2, 2},
   193  	{'♠', 1, 2, 2},
   194  	{'♂', 1, 2, 2},
   195  	{'♀', 1, 2, 2},
   196  	{'♪', 1, 2, 2},
   197  	{'♫', 1, 1, 2},
   198  	{'☼', 1, 1, 2},
   199  	{'↕', 1, 2, 2},
   200  	{'‼', 1, 1, 2},
   201  	{'↔', 1, 2, 2},
   202  	{'\x00', 0, 0, 0},
   203  	{'\x01', 0, 0, 0},
   204  	{'\u0300', 0, 0, 0},
   205  	{'\u2028', 0, 0, 0},
   206  	{'\u2029', 0, 0, 0},
   207  	{'a', 1, 1, 1}, // ASCII classified as "na" (narrow)
   208  	{'⟦', 1, 1, 1}, // non-ASCII classified as "na" (narrow)
   209  	{'👁', 1, 1, 2},
   210  }
   211  
   212  func TestRuneWidth(t *testing.T) {
   213  	c := NewCondition()
   214  	c.EastAsianWidth = false
   215  	for _, tt := range runewidthtests {
   216  		if out := c.RuneWidth(tt.in); out != tt.out {
   217  			t.Errorf("RuneWidth(%q) = %d, want %d (EastAsianWidth=false)", tt.in, out, tt.out)
   218  		}
   219  	}
   220  	c.EastAsianWidth = true
   221  	for _, tt := range runewidthtests {
   222  		if out := c.RuneWidth(tt.in); out != tt.eaout {
   223  			t.Errorf("RuneWidth(%q) = %d, want %d (EastAsianWidth=true)", tt.in, out, tt.eaout)
   224  		}
   225  	}
   226  	c.StrictEmojiNeutral = false
   227  	for _, tt := range runewidthtests {
   228  		if out := c.RuneWidth(tt.in); out != tt.nseout {
   229  			t.Errorf("RuneWidth(%q) = %d, want %d (StrictEmojiNeutral=false)", tt.in, out, tt.eaout)
   230  		}
   231  	}
   232  }
   233  
   234  var isambiguouswidthtests = []struct {
   235  	in  rune
   236  	out bool
   237  }{
   238  	{'世', false},
   239  	{'■', true},
   240  	{'界', false},
   241  	{'○', true},
   242  	{'㈱', false},
   243  	{'①', true},
   244  	{'②', true},
   245  	{'③', true},
   246  	{'④', true},
   247  	{'⑤', true},
   248  	{'⑥', true},
   249  	{'⑦', true},
   250  	{'⑧', true},
   251  	{'⑨', true},
   252  	{'⑩', true},
   253  	{'⑪', true},
   254  	{'⑫', true},
   255  	{'⑬', true},
   256  	{'⑭', true},
   257  	{'⑮', true},
   258  	{'⑯', true},
   259  	{'⑰', true},
   260  	{'⑱', true},
   261  	{'⑲', true},
   262  	{'⑳', true},
   263  	{'☆', true},
   264  }
   265  
   266  func TestIsAmbiguousWidth(t *testing.T) {
   267  	for _, tt := range isambiguouswidthtests {
   268  		if out := IsAmbiguousWidth(tt.in); out != tt.out {
   269  			t.Errorf("IsAmbiguousWidth(%q) = %v, want %v", tt.in, out, tt.out)
   270  		}
   271  	}
   272  }
   273  
   274  var stringwidthtests = []struct {
   275  	in    string
   276  	out   int
   277  	eaout int
   278  }{
   279  	{"■㈱の世界①", 10, 12},
   280  	{"スター☆", 7, 8},
   281  	{"つのだ☆HIRO", 11, 12},
   282  }
   283  
   284  func TestStringWidth(t *testing.T) {
   285  	c := NewCondition()
   286  	c.EastAsianWidth = false
   287  	for _, tt := range stringwidthtests {
   288  		if out := c.StringWidth(tt.in); out != tt.out {
   289  			t.Errorf("StringWidth(%q) = %d, want %d", tt.in, out, tt.out)
   290  		}
   291  	}
   292  	c.EastAsianWidth = true
   293  	for _, tt := range stringwidthtests {
   294  		if out := c.StringWidth(tt.in); out != tt.eaout {
   295  			t.Errorf("StringWidth(%q) = %d, want %d (EA)", tt.in, out, tt.eaout)
   296  		}
   297  	}
   298  }
   299  
   300  func TestStringWidthInvalid(t *testing.T) {
   301  	s := "こんにちわ\x00世界"
   302  	if out := StringWidth(s); out != 14 {
   303  		t.Errorf("StringWidth(%q) = %d, want %d", s, out, 14)
   304  	}
   305  }
   306  
   307  func TestTruncateSmaller(t *testing.T) {
   308  	s := "あいうえお"
   309  	expected := "あいうえお"
   310  
   311  	if out := Truncate(s, 10, "..."); out != expected {
   312  		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
   313  	}
   314  }
   315  
   316  func TestTruncate(t *testing.T) {
   317  	s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
   318  	expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
   319  	out := Truncate(s, 80, "...")
   320  	if out != expected {
   321  		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
   322  	}
   323  	width := StringWidth(out)
   324  	if width != 79 {
   325  		t.Errorf("width of Truncate(%q) should be %d, but %d", s, 79, width)
   326  	}
   327  }
   328  
   329  func TestTruncateFit(t *testing.T) {
   330  	s := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
   331  	expected := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
   332  
   333  	out := Truncate(s, 80, "...")
   334  	if out != expected {
   335  		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
   336  	}
   337  	width := StringWidth(out)
   338  	if width != 80 {
   339  		t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
   340  	}
   341  }
   342  
   343  func TestTruncateJustFit(t *testing.T) {
   344  	s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
   345  	expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
   346  
   347  	out := Truncate(s, 80, "...")
   348  	if out != expected {
   349  		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
   350  	}
   351  	width := StringWidth(out)
   352  	if width != 80 {
   353  		t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
   354  	}
   355  }
   356  
   357  func TestWrap(t *testing.T) {
   358  	s := `東京特許許可局局長はよく柿喰う客だ/東京特許許可局局長はよく柿喰う客だ
   359  123456789012345678901234567890
   360  
   361  END`
   362  	expected := `東京特許許可局局長はよく柿喰う
   363  客だ/東京特許許可局局長はよく
   364  柿喰う客だ
   365  123456789012345678901234567890
   366  
   367  END`
   368  
   369  	if out := Wrap(s, 30); out != expected {
   370  		t.Errorf("Wrap(%q) = %q, want %q", s, out, expected)
   371  	}
   372  }
   373  
   374  func TestTruncateNoNeeded(t *testing.T) {
   375  	s := "あいうえおあい"
   376  	expected := "あいうえおあい"
   377  
   378  	if out := Truncate(s, 80, "..."); out != expected {
   379  		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
   380  	}
   381  }
   382  
   383  var truncatelefttests = []struct {
   384  	s      string
   385  	w      int
   386  	prefix string
   387  	out    string
   388  }{
   389  	{"source", 4, "", "ce"},
   390  	{"source", 4, "...", "...ce"},
   391  	{"あいうえお", 6, "", "えお"},
   392  	{"あいうえお", 6, "...", "...えお"},
   393  	{"あいうえお", 10, "", ""},
   394  	{"あいうえお", 10, "...", "..."},
   395  	{"あいうえお", 5, "", " えお"},
   396  	{"Aあいうえお", 5, "", "うえお"},
   397  }
   398  
   399  func TestTruncateLeft(t *testing.T) {
   400  	t.Parallel()
   401  
   402  	for _, tt := range truncatelefttests {
   403  		if out := TruncateLeft(tt.s, tt.w, tt.prefix); out != tt.out {
   404  			t.Errorf("TruncateLeft(%q) = %q, want %q", tt.s, out, tt.out)
   405  		}
   406  	}
   407  }
   408  
   409  var isneutralwidthtests = []struct {
   410  	in  rune
   411  	out bool
   412  }{
   413  	{'→', false},
   414  	{'┊', false},
   415  	{'┈', false},
   416  	{'~', false},
   417  	{'└', false},
   418  	{'⣀', true},
   419  	{'⣀', true},
   420  }
   421  
   422  func TestIsNeutralWidth(t *testing.T) {
   423  	for _, tt := range isneutralwidthtests {
   424  		if out := IsNeutralWidth(tt.in); out != tt.out {
   425  			t.Errorf("IsNeutralWidth(%q) = %v, want %v", tt.in, out, tt.out)
   426  		}
   427  	}
   428  }
   429  
   430  func TestFillLeft(t *testing.T) {
   431  	s := "あxいうえお"
   432  	expected := "    あxいうえお"
   433  
   434  	if out := FillLeft(s, 15); out != expected {
   435  		t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
   436  	}
   437  }
   438  
   439  func TestFillLeftFit(t *testing.T) {
   440  	s := "あいうえお"
   441  	expected := "あいうえお"
   442  
   443  	if out := FillLeft(s, 10); out != expected {
   444  		t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
   445  	}
   446  }
   447  
   448  func TestFillRight(t *testing.T) {
   449  	s := "あxいうえお"
   450  	expected := "あxいうえお    "
   451  
   452  	if out := FillRight(s, 15); out != expected {
   453  		t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
   454  	}
   455  }
   456  
   457  func TestFillRightFit(t *testing.T) {
   458  	s := "あいうえお"
   459  	expected := "あいうえお"
   460  
   461  	if out := FillRight(s, 10); out != expected {
   462  		t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
   463  	}
   464  }
   465  
   466  func TestEnv(t *testing.T) {
   467  	old := os.Getenv("RUNEWIDTH_EASTASIAN")
   468  	defer os.Setenv("RUNEWIDTH_EASTASIAN", old)
   469  
   470  	os.Setenv("RUNEWIDTH_EASTASIAN", "0")
   471  	handleEnv()
   472  
   473  	if w := RuneWidth('│'); w != 1 {
   474  		t.Errorf("RuneWidth('│') = %d, want %d", w, 1)
   475  	}
   476  }
   477  
   478  func TestZeroWidthJoiner(t *testing.T) {
   479  	c := NewCondition()
   480  
   481  	var tests = []struct {
   482  		in   string
   483  		want int
   484  	}{
   485  		{"👩", 2},
   486  		{"👩\u200d", 2},
   487  		{"👩\u200d🍳", 2},
   488  		{"\u200d🍳", 2},
   489  		{"👨\u200d👨", 2},
   490  		{"👨\u200d👨\u200d👧", 2},
   491  		{"🏳️\u200d🌈", 1},
   492  		{"あ👩\u200d🍳い", 6},
   493  		{"あ\u200d🍳い", 6},
   494  		{"あ\u200dい", 4},
   495  	}
   496  
   497  	for _, tt := range tests {
   498  		if got := c.StringWidth(tt.in); got != tt.want {
   499  			t.Errorf("StringWidth(%q) = %d, want %d", tt.in, got, tt.want)
   500  		}
   501  	}
   502  }