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 }