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