mrubyedge 2.1.2

mruby/edge is yet another mruby that is specialized for running on WASM
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
##
# String ISO Test

UTF8STRING = __ENCODING__ == "UTF-8"
UNICODECASE = "\u00C4".downcase == "\u00E4"

assert('String', '15.2.10') do
  assert_equal Class, String.class
end

assert('RSTRING_CSTR with a non-terminated static string') do
  assert_true Mrbtest.nofree_cstr?
end

assert('String#<=>', '15.2.10.5.1') do
  a = '' <=> ''
  b = '' <=> 'not empty'
  c = 'not empty' <=> ''
  d = 'abc' <=> 'cba'
  e = 'cba' <=> 'abc'

  assert_equal  0, a
  assert_equal(-1, b)
  assert_equal  1, c
  assert_equal(-1, d)
  assert_equal  1, e
  assert_nil 'a' <=> 1024
end

assert('String#==', '15.2.10.5.2') do
  assert_equal 'abc', 'abc'
  assert_not_equal 'abc', 'cba'
end

# 'String#=~', '15.2.10.5.3' will be tested in mrbgems.

assert('String#+', '15.2.10.5.4') do
  assert_equal 'ab', 'a' + 'b'
end

assert('String#*', '15.2.10.5.5') do
  assert_equal 'aaaaa', 'a' * 5
  assert_equal '', 'a' * 0
  assert_raise(ArgumentError) { 'a' * -1 }
  assert_raise(TypeError) { 'a' * '1' }
  assert_raise(TypeError) { 'a' * nil }

  skip unless Object.const_defined?(:Float)
  assert_equal 'aa', 'a' * 2.1
  assert_raise(RangeError) { '' * 1e30 }
  assert_raise(RangeError) { '' * Float::INFINITY }
  assert_raise(RangeError) { '' * Float::NAN }
end

assert('String#[]', '15.2.10.5.6') do
  # length of args is 1
  assert_equal 'a', 'abc'[0]
  assert_equal 'c', 'abc'[-1]
  assert_nil 'abc'[10]
  assert_nil 'abc'[-10]
  assert_equal 'b', 'abc'[1.1] if Object.const_defined?(:Float)

  # length of args is 2
  assert_nil 'abc'[0, -1]
  assert_nil 'abc'[10, 0]
  assert_nil 'abc'[-10, 0]
  assert_equal '', 'abc'[0, 0]
  assert_equal 'bc', 'abc'[1, 2]

  # args is String
  assert_equal 'bc', 'abc'['bc']
  assert_nil 'abc'['XX']

  assert_raise(TypeError) { 'abc'[nil] }
end

assert('String#[](UTF-8)', '15.2.10.5.6') do
  assert_equal "ち", "こんにちは世界"[3]
  assert_equal nil, "こんにちは世界"[20]
  assert_equal "世", "こんにちは世界"[-2]
  assert_equal "世界", "こんにちは世界"[-2..-1]
  assert_equal "んに", "こんにちは世界"[1,2]
  assert_equal "世", "こんにちは世界"["世"]
end if UTF8STRING

assert('String#[](UTF-8) indexes a byte that spells no character on its own') do
  # Such a byte is a position of its own, which is what String#length counts
  # it as, and it takes that one position beside whole characters too.
  s = "\xED\xA0\x80"
  assert_equal "\xED", s[0]
  assert_equal "\xA0", s[1]
  assert_equal "\x80", s[2]
  assert_nil s[3]
  assert_equal "\xA0\x80", s[1, 2]
  assert_equal "b", "a\xE3\x81b"[3]
  assert_equal "\x80", "あ\x80"[1]
  assert_equal "あ", "\x80あ"[1]
end if UTF8STRING

assert('String#[](UTF-8) counts a negative index back from the end') do
  # Stepping back off the first character leaves the string, so a negative
  # index reaching past the head names no position rather than wrapping to
  # one. The length the range asks for is clamped to what is left after it.
  assert_equal "あ", "あい"[-2]
  assert_nil "あい"[-3]
  assert_equal "あ", "あい"[-2, 1]
  assert_nil "あい"[-3, 1]
  assert_equal "あい", "あい"[-2, 5]
end if UTF8STRING

assert('String#[] with Range') do
  a1 = 'abc'[1..0]
  b1 = 'abc'[1..1]
  c1 = 'abc'[1..2]
  d1 = 'abc'[1..3]
  e1 = 'abc'[1..4]
  f1 = 'abc'[0..-2]
  g1 = 'abc'[-2..3]
  h1 = 'abc'[3..4]
  i1 = 'abc'[4..5]
  j1 = 'abcdefghijklmnopqrstuvwxyz'[1..3]
  k1 = 'abcdefghijklmnopqrstuvwxyz'[-3..]
  a2 = 'abc'[1...0]
  b2 = 'abc'[1...1]
  c2 = 'abc'[1...2]
  d2 = 'abc'[1...3]
  e2 = 'abc'[1...4]
  f2 = 'abc'[0...-2]
  g2 = 'abc'[-2...3]
  h2 = 'abc'[3...4]
  i2 = 'abc'[4...5]
  j2 = 'abcdefghijklmnopqrstuvwxyz'[1...3]
  k2 = 'abcdefghijklmnopqrstuvwxyz'[-3...]

  assert_equal '', a1
  assert_equal 'b', b1
  assert_equal 'bc', c1
  assert_equal 'bc', d1
  assert_equal 'bc', e1
  assert_equal 'ab', f1
  assert_equal 'bc', g1
  assert_equal '', h1
  assert_nil i2
  assert_equal 'bcd', j1
  assert_equal 'xyz', k1
  assert_equal '', a2
  assert_equal '', b2
  assert_equal 'b', c2
  assert_equal 'bc', d2
  assert_equal 'bc', e2
  assert_equal 'a', f2
  assert_equal 'bc', g2
  assert_equal '', h2
  assert_nil i2
  assert_equal 'bc', j2
  assert_equal 'xyz', k2
end

assert('String#[] redefined on String itself reaches the redefinition') do
  # `OP_GETIDX` answers `s[1]` from C, and `OP_GETIDX0` answers `s[0]` the same
  # way, whenever the receiver's class is exactly `String`. Answering from C is
  # allowed only while `String#[]` is still the builtin those opcodes
  # reimplement, so both test the receiver against `mrb->idx_class[]`, which
  # the method table drops the moment `String#[]` is replaced. A redefinition
  # installed on `String` itself is therefore honored, as in CRuby, and not
  # only the subclass and singleton receivers that already failed the class
  # test for the other reason.
  String.class_eval do
    alias_method :__aref_before_test, :[]
    def [](*args)
      :overridden
    end
  end
  begin
    s = 'hello'
    sub = Class.new(String).new('hello')
    assert_equal :overridden, s[0]
    assert_equal :overridden, s[1]
    assert_equal :overridden, sub[0]
  ensure
    String.class_eval do
      alias_method :[], :__aref_before_test
      # `remove_method` comes from mruby-metaprog, which the core test build
      # does not have; the saved alias is harmless where it is missing.
      remove_method :__aref_before_test if respond_to?(:remove_method, true)
    end
  end
  # Aliasing the original implementation back makes `String#[]` resolve to it
  # again, which re-arms the opcodes.
  assert_equal 'h', 'hello'[0]
  assert_equal 'e', 'hello'[1]
end

assert('String#+ redefined on String itself reaches the redefinition') do
  # `OP_ADD` answers `s + t` from C for two Strings, which it may only do
  # while `String#+` is still the builtin it reimplements and the receiver's
  # class is exactly `String`. It tests the receiver against `mrb->idx_class[]`
  # on the same terms as the `[]` test above, so a redefinition installed on
  # `String` itself is honored as in CRuby, and so is one on a subclass or a
  # singleton, which the type tag alone could not tell from `String`. The
  # results are read before the operator is put back because the assertions
  # themselves build strings.
  String.class_eval do
    alias_method :__add_before_test, :+
    def +(other)
      [:overridden, self, other]
    end
  end
  begin
    s = 'hello'
    sum = s + ' world'
    literal = 'a' + 'b'
  ensure
    String.class_eval do
      alias_method :+, :__add_before_test
      remove_method :__add_before_test if respond_to?(:remove_method, true)
    end
  end
  assert_equal [:overridden, 'hello', ' world'], sum
  assert_equal [:overridden, 'a', 'b'], literal
  assert_equal 'hello world', 'hello' + ' world'

  sub = Class.new(String) { def +(other) [:subclass, self, other] end }.new('hi')
  assert_equal [:subclass, 'hi', '!'], sub + '!'
  single = 'hi'
  def single.+(other) [:singleton, self, other] end
  assert_equal [:singleton, 'hi', '!'], single + '!'
  assert_equal 'hi!', 'hi' + '!'
end

assert('String#[]= redefined on String itself reaches the redefinition') do
  # `OP_SETIDX` answers `s[0] = 'X'` from C whenever the receiver's class is
  # exactly `String`, on the same terms as the `[]` test above: it tests the
  # receiver against `mrb->idx_class[]`, which the method table drops the
  # moment `String#[]=` is replaced. A redefinition that stores nothing makes
  # the difference visible in the receiver as well as in the return value.
  String.class_eval do
    alias_method :__aset_before_test, :[]=
    def []=(*args)
      $string_aset_redefinition_args = args
    end
  end
  begin
    s = 'hello'
    s[0] = 'X'
    seen = $string_aset_redefinition_args
    untouched = s.dup
    sub = Class.new(String).new('hello')
    sub[0] = 'X'
    seen_sub = $string_aset_redefinition_args
  ensure
    String.class_eval do
      alias_method :[]=, :__aset_before_test
      # `remove_method` comes from mruby-metaprog, which the core test build
      # does not have; the saved alias is harmless where it is missing.
      remove_method :__aset_before_test if respond_to?(:remove_method, true)
    end
    $string_aset_redefinition_args = nil
  end
  assert_equal [0, 'X'], seen
  assert_equal 'hello', untouched
  assert_equal [0, 'X'], seen_sub
  # Aliasing the original implementation back re-arms the opcode.
  s = 'hello'
  s[0] = 'X'
  assert_equal 'Xello', s
end

assert('String#[]= answers the same through the opcode and through a send') do
  # `s[x] = repl` is answered by `OP_SETIDX` in C, without a method lookup,
  # while `s.[]=(x, repl)` reaches `String#[]=` itself. The opcode
  # answers an Integer, String or Range index and a String replacement, and
  # leaves every other form to the method, so ask both ways and compare the
  # receiver each left behind.
  [0, 1, 4, -1, -5].each do |i|
    a = 'hello'; b = 'hello'
    a[i] = 'X'
    b.[]=(i, 'X')
    assert_equal b, a, "s[#{i}] = 'X'"
  end
  ['h', 'll', 'hello', ''].each do |sub|
    a = 'hello'; b = 'hello'
    a[sub] = 'X'
    b.[]=(sub, 'X')
    assert_equal b, a, "s[#{sub.inspect}] = 'X'"
  end
  [0..2, 1...3, -3..-1, 0..-1, 2..99, 3..1].each do |r|
    a = 'hello'; b = 'hello'
    a[r] = 'X'
    b.[]=(r, 'X')
    assert_equal b, a, "s[#{r.inspect}] = 'X'"
  end
  # Each loop above compares the two forms with each other, so anchor one case
  # of every index type to the result itself: a defect that made both forms
  # store nothing would agree with itself.
  a = 'hello'; a[1] = 'X'
  assert_equal 'hXllo', a
  a = 'hello'; a['ll'] = 'X'
  assert_equal 'heXo', a
  a = 'hello'; a[1..3] = 'X'
  assert_equal 'hXo', a
  # The forms the opcode leaves to the method raise what the method raises.
  assert_raise(IndexError) { 'hello'[99] = 'X' }
  assert_raise(IndexError) { 'hello'['zz'] = 'X' }
  assert_raise(IndexError) { 'hello'[99..100] = 'X' }
  assert_raise(TypeError) { 'hello'[0] = :sym }
  assert_raise(TypeError) { 'hello'[nil] = 'X' }
  assert_raise(FrozenError) { 'hello'.freeze[0] = 'X' }
end

assert('String#[]=') do
  # length of args is 1
  a = 'abc'
  assert_equal 'X', (a[0] = 'X')
  assert_equal 'Xbc', a

  b = 'abc'
  b[-1] = 'X'
  assert_equal 'abX', b

  c = 'abc'
  assert_raise(IndexError) do
    c[10] = 'X'
  end

  d = 'abc'
  assert_raise(IndexError) do
    d[-10] = 'X'
  end

  if Object.const_defined?(:Float)
    e = 'abc'
    e[1.1] = 'X'
    assert_equal 'aXc', e
  end

  f = 'abc'
  assert_equal 'X', f.[]=(0, 'X')
  assert_equal 'Xbc', f

  assert_raise(TypeError) { 'a'[0] = 1 }
  assert_raise(TypeError) { 'a'[:a] = '1' }

  # length of args is 2
  a1 = 'abc'
  assert_raise(IndexError) do
    a1[0, -1] = 'X'
  end

  b1 = 'abc'
  assert_raise(IndexError) do
    b1[10, 0] = 'X'
  end

  c1 = 'abc'
  assert_raise(IndexError) do
    c1[-10, 0] = 'X'
  end

  d1 = 'abc'
  d1[0, 0] = 'X'
  assert_equal 'Xabc', d1

  e1 = 'abc'
  assert_equal 'X', (e1[1, 3] = 'X')
  assert_equal 'aX', e1

  f1 = 'abc'
  assert_equal 'X', f1.[]=(0, 1, 'X')
  assert_equal 'Xbc', f1

  # args is RegExp
  # It will be tested in mrbgems.

  # args is String
  a3 = 'abc'
  assert_equal 'X', (a3['bc'] = 'X')
  assert_equal a3, 'aX'

  b3 = 'abc'
  assert_raise(IndexError) do
    b3['XX'] = 'Y'
  end

  c3 = 'abc'
  assert_equal 'X', c3.[]=('bc', 'X')
  assert_equal 'aX', c3

  assert_raise(TypeError) { 'a'[:a, 0] = '1' }
  assert_raise(TypeError) { 'a'[0, :a] = '1' }
  assert_raise(TypeError) { 'a'[0, 1] = 1 }
end

assert('String[]=(UTF-8)') do
  a = "➀➁➂➃➄"
  a[3] = "⚃"
  assert_equal "➀➁➂⚃➄", a

  b = "➀➁➂➃➄"
  b[3, 0] = "⛄"
  assert_equal "➀➁➂⛄➃➄", b

  c = "➀➁➂➃➄"
  c[3, 2] = "⚃⚄"
  assert_equal "➀➁➂⚃⚄", c

  d = "➀➁➂➃➄"
  d[5] = "⛄"
  assert_equal "➀➁➂➃➄⛄", d

  e = "➀➁➂➃➄"
  e[5, 0] = "⛄"
  assert_equal "➀➁➂➃➄⛄", e

  f = "➀➁➂➃➄"
  f[5, 2] = "⛄"
  assert_equal "➀➁➂➃➄⛄", f

  g = "➀➁➂➃➄"
  assert_raise(IndexError) { g[6] = "⛄" }

  h = "➀➁➂➃➄"
  assert_raise(IndexError) { h[6, 0] = "⛄" }

  i = "➀➁➂➃➄"
  assert_raise(IndexError) { i[6, 2] = "⛄" }

  j = "➀➁➂➃➄"
  j["➃"] = "⚃"
  assert_equal "➀➁➂⚃➄", j

  k = "➀➁➂➃➄"
  assert_raise(IndexError) { k["⛄"] = "⛇" }

  l = "➀➁➂➃➄"
  assert_nothing_raised { l["➂"] = "" }
  assert_equal "➀➁➃➄", l

  m = "➀➁➂➃➄"
  assert_raise(TypeError) { m["➂"] = nil }
  assert_equal "➀➁➂➃➄", m
end if UTF8STRING

assert('String#[]= on a shared buffer') do
  # Assigning to the empty range at the end appends, and an append writes
  # only above what every other sharer of the buffer reads, so it stays in
  # the buffer instead of taking a copy of it. The sharer must not see the
  # bytes the string gained. Each string is shortened before it is shared, to
  # leave spare capacity behind: one that has none cannot be grown in place
  # anyway, so it would not tell the two behaviours apart.
  a = "a" * 100 + "z" * 100
  a[100, 100] = ""
  a_slice = a[0, 60]
  a[a.length, 0] = "1234567890"
  assert_equal "a" * 100 + "1234567890", a
  assert_equal "a" * 60, a_slice

  # Assigning below the end writes where the sharer reads, so the buffer has
  # to be taken away from it first.
  b = "b" * 100 + "y" * 100
  b[100, 100] = ""
  b_slice = b[0, 60]
  b[0, 1] = "1234567890"
  assert_equal "1234567890" + "b" * 99, b
  assert_equal "b" * 60, b_slice

  # The loop that made growth at the end quadratic: the slice shares the
  # buffer again on every turn, and the append past it stays in place.
  c = ""
  50.times { c[c.length, 0] = "0123456789012345678901234567890123456789"; c[0, 30] }
  assert_equal 2000, c.length
  assert_equal "0123456789012345678901234567890123456789", c[-40, 40]
end

assert('String#capitalize', '15.2.10.5.7') do
  a = 'abc'
  a.capitalize

  assert_equal 'abc', a
  assert_equal 'Abc', 'abc'.capitalize
end

assert('String#capitalize!', '15.2.10.5.8') do
  a = 'abc'
  a.capitalize!

  assert_equal 'Abc', a
  assert_equal nil, 'Abc'.capitalize!
end

assert('String#capitalize - Unicode') do
  # The first character takes title case, which is not always its upper case.
  assert_equal 'Dz', 'dz'.capitalize
  assert_equal 'DZ', 'dz'.upcase
  # The rest takes lower case, whatever case it came in.
  assert_equal 'Dzabc', 'dzabc'.capitalize
  assert_equal 'Aä', 'aÄ'.capitalize
  # A mapping that spells more than one character.
  assert_equal 'Fi', 'fi'.capitalize
  # Georgian Mkhedruli upper cases to Mtavruli and title cases to itself, so
  # the two answers have to be told apart rather than shared.
  assert_equal 'Ა', 'ა'.upcase
  assert_equal 'ა', 'ა'.capitalize
  assert_nil 'ა'.capitalize!
end if UNICODECASE

assert('String#downcase - Unicode') do
  assert_equal 'äöü', 'ÄÖÜ'.downcase
  # Word final sigma is a mapping that reads its neighbours, which neither
  # this nor CRuby applies: both answer "σ" for the last one too.
  assert_equal 'σοφοσ', 'ΣΟΦΟΣ'.downcase
  # A mapping changes how many bytes a character takes: U+212A is three bytes
  # and lower cases to the one of "k".
  assert_equal 'k', "\u{212a}".downcase
  assert_equal 1, "\u{212a}".downcase.bytesize
  # And it can spell more characters than it was handed.
  assert_equal "i\u{307}", 'İ'.downcase
  assert_equal 2, 'İ'.downcase.length
  # A script without case has nothing to map.
  assert_equal '日本', '日本'.downcase
  assert_nil '日本'.downcase!
end if UNICODECASE

assert('String case conversion - bytes that spell no character') do
  # A run of bytes that spells no character has no case, and answering as
  # though it were the byte it starts with would hand back a string nobody
  # asked for, so the conversion refuses it.
  broken = "\xC3ABC"
  assert_raise(ArgumentError) { broken.downcase }
  assert_raise(ArgumentError) { broken.upcase }
  assert_raise(ArgumentError) { broken.capitalize }
  assert_raise_with_message(ArgumentError, 'input string invalid') { broken.downcase }
  # The receiver of a refused conversion stands as it was.
  assert_raise(ArgumentError) { broken.downcase! }
  assert_equal [195, 65, 66, 67], broken.bytes
  # ASCII ahead of the broken run does not save it, and neither does a
  # conversion that would have changed nothing.
  assert_raise(ArgumentError) { "abc\x80".downcase }
  assert_raise(ArgumentError) { "\x80".downcase }
end if UNICODECASE

assert('String#upcase - Unicode') do
  assert_equal 'ÄÖÜ', 'äöü'.upcase
  assert_equal 'ΣΟΦΟΣ', 'σοφος'.upcase
  # A mapping that spells more than one character.
  assert_equal 'SS', 'ß'.upcase
  assert_equal 2, 'ß'.upcase.length
  assert_equal 'FI', 'fi'.upcase
  # And one that shortens: U+0131 is two bytes and upper cases to "I".
  assert_equal 'I', 'ı'.upcase
  assert_equal 1, 'ı'.upcase.bytesize
  assert_equal '日本', '日本'.upcase
  assert_nil '日本'.upcase!
end if UNICODECASE

assert('String#upcase - an answer that outgrows an embedded buffer') do
  # U+0390 is two bytes and upper cases to six, so a string short enough to
  # live inside its own object converts to one that cannot. The answer is
  # built beside the string, and a buffer that leaves an object carries over
  # what the object says it holds: the walk has to say how much it has
  # written, or the bytes written so far are dropped where the buffer moves.
  assert_equal "Ϊ́" * 4, ("ΐ" * 4).upcase
  assert_equal 24, ("ΐ" * 4).upcase.bytesize
  # The same crossing one character at a time, so wherever the boundary of an
  # embedded string falls, some length below walks over it.
  1.upto(12) do |n|
    assert_equal "Ϊ́" * n, ("ΐ" * n).upcase
  end
end if UNICODECASE

assert('String#downcase - a receiver above the embedded buffer') do
  # A receiver too long to live inside its own object holds its bytes in a
  # heap (or shared) buffer that str_replace() has to release rather than
  # overwrite, and the coderange the walk sets has to survive the copy that
  # hands the converted buffer to the receiver.
  long = "ÄÖÜ" * 20
  assert_equal 60, long.downcase.length
  assert_equal "äöü" * 20, long.downcase
end if UNICODECASE

assert('String case conversion - a frozen receiver') do
  # upcase! and capitalize! find nothing to change in 'Ä' and would answer
  # nil without the walk's own mrb_check_frozen(), so they are what pins it.
  # downcase! changes the character and raises from str_replace() further in,
  # so it answers FrozenError whether or not that check is there.
  assert_raise(FrozenError) { 'Ä'.freeze.upcase! }
  assert_raise(FrozenError) { 'Ä'.freeze.downcase! }
  assert_raise(FrozenError) { 'Ä'.freeze.capitalize! }
  # An all-ASCII receiver never enters the walk and raises from the ASCII
  # loop's own mrb_str_modify_keep_cr() instead.
  assert_raise(FrozenError) { 'AB'.freeze.downcase! }
end if UNICODECASE

assert('String case conversion - a receiver sharing its buffer') do
  # A copy above the embedded buffer shares the bytes of what it was copied
  # from, and the walk reads them where they are rather than unsharing first.
  # Converting the copy must leave the original holding what it held, and a
  # copy the walk finds nothing to change in must still read as what it was.
  src = "ÄÖÜ" * 20
  copy = src.dup
  assert_equal "äöü" * 20, copy.downcase!
  assert_equal "ÄÖÜ" * 20, src
  copy = src.dup
  assert_nil copy.upcase!
  assert_equal "ÄÖÜ" * 20, copy
  assert_equal "ÄÖÜ" * 20, src
  # A literal above the embedded buffer can read its bytes from the program
  # itself, and lets go of them the same way, without freeing them.
  assert_equal "äöüäöüäöüäöüäöüäöüäöüäöüäöüäöü", "ÄÖÜÄÖÜÄÖÜÄÖÜÄÖÜÄÖÜÄÖÜÄÖÜÄÖÜÄÖÜ".downcase!
end if UNICODECASE

assert('String case conversion - ASCII only') do
  # The other reading of case: a build that converts by ASCII, whether by
  # MRB_USE_ASCII_CTYPE or by reading its strings as bytes, has no mapping above
  # ASCII, so a character that has one on the Unicode side stands as it was
  # while the ASCII beside it still converts.
  assert_equal 'Ä', 'Ä'.downcase
  assert_equal 'ä', 'ä'.upcase
  assert_equal 'Ä', 'Ä'.capitalize
  assert_equal 'äb', 'äB'.downcase
  assert_equal 'ÄB', 'Äb'.upcase
  assert_equal 'Äb', 'ÄB'.capitalize
  # A conversion that maps nothing is one that changed nothing.
  assert_nil 'Ä'.downcase!
  assert_nil 'ä'.upcase!
  assert_nil 'Ä'.capitalize!
  # Refusing a run of bytes that spells no character belongs to the walk over
  # characters; a walk that only knows ASCII hands the bytes back untouched.
  assert_equal [195, 97, 98, 99], "\xC3ABC".downcase.bytes
  assert_equal [195, 65, 66, 67], "\xC3ABC".upcase.bytes
end unless UNICODECASE

assert('String#chomp', '15.2.10.5.9') do
  a = 'abc'.chomp
  b = ''.chomp
  c = "abc\n".chomp
  d = "abc\n\n".chomp
  e = "abc\t".chomp("\t")
  f = "abc\n"

  f.chomp

  assert_equal 'abc', a
  assert_equal '', b
  assert_equal 'abc', c
  assert_equal "abc\n", d
  assert_equal 'abc', e
  assert_equal "abc\n", f
end

assert('String#chomp does not cut inside a character') do
  # The separator is matched byte by byte, so it can line up with the tail of
  # a character rather than a character of its own. Cutting there would leave
  # a string that is not UTF-8, so it counts as no match.
  assert_equal "あ", "あ".chomp("\x82")
  assert_equal "あ", "あ".chomp("\x81\x82")
  assert_equal "あい", "あい".chomp("\x84")
  assert_equal "aあ", "aあ".chomp("\x82")
  assert_nil "あ".chomp!("\x82")
  # a separator that is a whole character still cuts
  assert_equal "", "あ".chomp("あ")
  assert_equal "あ", "あい".chomp("い")
end if UTF8STRING

assert('String#chomp!', '15.2.10.5.10') do
  a = 'abc'
  b = ''
  c = "abc\n"
  d = "abc\n\n"
  e = "abc\t"

  a.chomp!
  b.chomp!
  c.chomp!
  d.chomp!
  e.chomp!("\t")

  assert_equal 'abc', a
  assert_equal '', b
  assert_equal 'abc', c
  assert_equal "abc\n", d
  assert_equal 'abc', e
end

assert('String#chop', '15.2.10.5.11') do
  a = ''.chop
  b = 'abc'.chop
  c = 'abc'

  c.chop

  assert_equal '', a
  assert_equal 'ab', b
  assert_equal 'abc', c
end

assert('String#chop(UTF-8)', '15.2.10.5.11') do
  a = ''.chop
  b = 'あいう'.chop
  c = "あ\nい".chop.chop

  assert_equal '', a
  assert_equal 'あい', b
  assert_equal 'あ', c
end if UTF8STRING

assert('String#chop!', '15.2.10.5.12') do
  a = ''
  b = 'abc'

  a.chop!
  b.chop!

  assert_equal a, ''
  assert_equal b, 'ab'
end

assert('String#chop!(UTF-8)', '15.2.10.5.12') do
  a = ''
  b = "あいうえ\n"
  c = "あいうえ\n"

  a.chop!
  b.chop!
  c.chop!
  c.chop!

  assert_equal a, ''
  assert_equal b, 'あいうえ'
  assert_equal c, 'あいう'
end if UTF8STRING

assert('String#chop! cuts where String#length counts a character') do
  # The last character starts where the character covering the last byte
  # starts, and a byte that no lead byte reaches is a character of its own.
  assert_equal "あ", "あ\x82".chop
  assert_equal "\x80\x80", "\x80\x80\x80".chop
  # A sequence RFC 3629 forbids spells no character, so its bytes stand alone.
  assert_equal "\xC0", "\xC0\x80".chop
  assert_equal "\xED\xA0", "\xED\xA0\x80".chop
  # A lead byte the string end cuts short reaches none of the bytes after it.
  assert_equal "a\xE3", "a\xE3\x81".chop
  # a whole character still goes at once, however many bytes it spells
  assert_equal "", "\u{1F600}".chop
  # and the \r\n pair is still taken together after one
  assert_equal "あ", "あ\r\n".chop
end if UTF8STRING

assert('String#downcase', '15.2.10.5.13') do
  a = 'ABC'.downcase
  b = 'ABC'

  b.downcase

  assert_equal 'abc', a
  assert_equal 'ABC', b
end

assert('String#downcase!', '15.2.10.5.14') do
  a = 'ABC'

  a.downcase!

  assert_equal 'abc', a
  assert_equal nil, 'abc'.downcase!
end

assert('String#each_line', '15.2.10.5.15') do
  a = "first line\nsecond line\nthird line"
  list = ["first line\n", "second line\n", "third line"]
  n_list = []

  a.each_line do |line|
    n_list << line
  end

  assert_equal list, n_list

  n_list.clear
  a.each_line("li") do |line|
    n_list << line
  end
  assert_equal ["first li", "ne\nsecond li", "ne\nthird li", "ne"], n_list
end

assert('String#empty?', '15.2.10.5.16') do
  a = ''
  b = 'not empty'

  assert_true a.empty?
  assert_false b.empty?
end

assert('String#eql?', '15.2.10.5.17') do
  assert_true 'abc'.eql?('abc')
  assert_false 'abc'.eql?('cba')
end

assert('String#gsub', '15.2.10.5.18') do
  assert_equal('aBcaBc', 'abcabc'.gsub('b', 'B'), 'gsub without block')
  assert_equal('aBcaBc', 'abcabc'.gsub('b'){|w| w.capitalize }, 'gsub with block')
  assert_equal('$a$a$',  '#a#a#'.gsub('#', '$'), 'mruby/mruby#847')
  assert_equal('$a$a$',  '#a#a#'.gsub('#'){|_w| '$' }, 'mruby/mruby#847 with block')
  assert_equal('$$a$$',  '##a##'.gsub('##', '$$'), 'mruby/mruby#847 another case')
  assert_equal('$$a$$',  '##a##'.gsub('##'){|_w| '$$' }, 'mruby/mruby#847 another case with block')
  assert_equal('A',      'a'.gsub('a', 'A'))
  assert_equal('A',      'a'.gsub('a'){|w| w.capitalize })
  assert_equal("<a><><>", 'a'.gsub('a', '<\0><\1><\2>'))
  assert_equal(".h.e.l.l.o.", "hello".gsub("", "."))
  a = []
  assert_equal(".h.e.l.l.o.", "hello".gsub("") { |i| a << i; "." })
  assert_equal(["", "", "", "", "", ""], a)
  assert_raise(ArgumentError) { "".gsub }
  assert_raise(ArgumentError) { "".gsub("", "", "") }
end

assert('String#gsub with backslash') do
  s = 'abXcdXef'
  assert_equal 'ab<\\>cd<\\>ef',    s.gsub('X', '<\\\\>')
  assert_equal 'ab<X>cd<X>ef',      s.gsub('X', '<\\&>')
  assert_equal 'ab<X>cd<X>ef',      s.gsub('X', '<\\0>')
  assert_equal 'ab<ab>cd<abXcd>ef', s.gsub('X', '<\\`>')
  assert_equal 'ab<cdXef>cd<ef>ef', s.gsub('X', '<\\\'>')
end

assert('String#gsub!', '15.2.10.5.19') do
  a = 'abcabc'
  a.gsub!('b', 'B')

  b = 'abcabc'
  b.gsub!('b') { |w| w.capitalize }

  assert_equal 'aBcaBc', a
  assert_equal 'aBcaBc', b
end

assert('String#hash', '15.2.10.5.20') do
  a = 'abc'

  assert_equal 'abc'.hash, a.hash
end

assert('String#include?', '15.2.10.5.21') do
  assert_true 'abc'.include?('a')
  assert_false 'abc'.include?('d')
end

assert('String#index', '15.2.10.5.22') do
  assert_equal 0, 'abc'.index('a')
  assert_nil 'abc'.index('d')
  assert_equal 3, 'abcabc'.index('a', 1)
  assert_equal 5, "hello".index("", 5)
  assert_equal nil, "hello".index("", 6)
  assert_equal 3, "hello".index("l", -2)
  assert_raise(ArgumentError) { "hello".index }
  assert_raise(TypeError) { "hello".index(101) }
end

assert('String#index over a subject longer than one search word') do
  # A needle of two bytes or more in a subject long enough to be read a word
  # at a time is searched in two parts: whole words first, then the bytes past
  # the last whole one, one at a time.  The cases above are all shorter than a
  # word, so they only ever reach the second part; these place the same needle
  # on either side of the seam, and past the seam at the last position it can
  # start at.
  head = 'x' * 100
  assert_equal 100, (head + 'needle' + head).index('needle')
  assert_equal 100, (head + 'needle' + 'x').index('needle')
  assert_equal 100, (head + 'ne').index('ne')
  assert_nil (head + head).index('needle')
end

assert('String#index(UTF-8)', '15.2.10.5.22') do
  assert_equal 0, '⓿➊➋➌➍➎'.index('⓿')
  assert_nil '⓿➊➋➌➍➎'.index('➓')
  assert_equal 6, '⓿➊➋➌➍➎⓿➊➋➌➍➎'.index('⓿', 1)
  assert_equal 6, '⓿➊➋➌➍➎⓿➊➋➌➍➎'.index('⓿', -7)
  assert_equal 6, "⓿➊➋➌➍➎".index("", 6)
  assert_equal nil, "⓿➊➋➌➍➎".index("", 7)
  # A needle whose bytes spell no character is found nowhere; the
  # byte-indexed counterparts live in mruby-string-ext's tests, where
  # String#b is available to write them.
  assert_nil '⓿➊➋➌➍➎'.index("\xe2")
  assert_nil '⓿➊➋➌➍➎'.index("\xe3")
  assert_equal 6, "\xd1\xd1\xd1\xd1\xd1\xd1⓿➊➋➌➍➎".index('⓿')
end if UTF8STRING

assert('String#initialize', '15.2.10.5.23') do
  a = ''
  a.__send__(:initialize,'abc')
  assert_equal 'abc', a

  a.__send__(:initialize,'abcdefghijklmnopqrstuvwxyz')
  assert_equal 'abcdefghijklmnopqrstuvwxyz', a
end

assert('String#initialize_copy', '15.2.10.5.24') do
  a = ''
  a.__send__(:initialize_copy, 'abc')

  assert_equal 'abc', a
end

assert('String#intern', '15.2.10.5.25') do
  assert_equal :abc, 'abc'.intern
end

assert('String#length', '15.2.10.5.26') do
  assert_equal 3, 'abc'.length
end

assert('String#length(UTF-8)', '15.2.10.5.26') do
  assert_equal 3, 'あいう'.length

  # A substring too long to embed shares the parent's buffer, so the byte
  # after its last one belongs to the parent instead of terminating it.
  s = ('あ' * 40)[0, 20]
  assert_equal 60, s.bytesize
  assert_equal 20, s.length
  assert_nil s[20]
  assert_equal 20, ('あ' * 40)[10, 20].length
  assert_equal 10, ("\u{1F600}" * 20)[0, 10].length

  # These answered correctly all along: a substring short enough to embed is
  # copied and terminated, one reaching the parent's end stops at the parent's
  # terminator, and a run of non-ASCII bytes ends on an ASCII one.
  assert_equal 2, ('あ' * 40)[0, 2].length
  assert_equal 20, ('あ' * 40)[20, 20].length
  assert_equal 20, ('aあ' * 30)[0, 20].length

  # Cut in the middle of a character, the loose bytes count one each,
  # whether the string ends in the parent's buffer or in its own.
  assert_equal 21, ('あ' * 40).byteslice(0, 59).length
  assert_equal 2, "\xe3\x81".length
end if UTF8STRING

assert('String#length over every lead byte') do
  # Every lead byte followed by up to five continuation bytes, once with the
  # lowest continuation byte and once with the highest, so that every boundary
  # RFC 3629 draws has a sequence on each side of it: a shorter spelling (C0,
  # C1, E0 80, F0 80), a surrogate (ED A0 and above), U+10FFFF (F4 90 and
  # above, F5 to F7), and the five and six byte lengths (F8 to FD). The count
  # a character-indexed build owes is what the reading of RFC 3629 below
  # answers; over these 3072 strings its answers are CRuby's. A byte-indexed
  # build owes one per byte. The next test holds the sequences right on
  # either side of each bound on the second byte.
  min = [0, 128, 2048, 65536, 2097152, 67108864]
  claim = ->(c) {
    if c < 0x80 then 1 elsif c < 0xC0 then 0 elsif c < 0xE0 then 2
    elsif c < 0xF0 then 3 elsif c < 0xF8 then 4 elsif c < 0xFC then 5
    elsif c < 0xFE then 6 else 0 end
  }
  # the byte length of the character at bytes[i], or 1 where the bytes there
  # spell none; the fillers are continuation bytes, so only the count and the
  # value can fall short
  charlen = ->(bytes, i) {
    c = bytes[i]
    n = claim.call(c)
    next 1 if n < 2 || n > 4 || bytes.size - i < n
    v = c & (0x7F >> n)
    (1...n).each {|k| v = (v << 6) | (bytes[i + k] & 0x3F) }
    next 1 if v < min[n - 1] || v > 0x10FFFF || (0xD800 <= v && v <= 0xDFFF)
    n
  }
  0.upto(255) do |c|
    [0x80, 0xBF].each do |f|
      0.upto(5) do |k|
        bytes = [c] + [f] * k
        s = "\0" * bytes.size
        bytes.each_with_index {|b, i| s.setbyte(i, b) }
        expected = 0
        i = 0
        while i < bytes.size
          i += UTF8STRING ? charlen.call(bytes, i) : 1
          expected += 1
        end
        assert_equal expected, s.length, bytes.inspect
      end
    end
  end
end

assert('String#length on either side of each bound on the second byte') do
  # Each pair differs in the byte after the lead alone, one step across the
  # floor of three and four bytes, the surrogates and U+10FFFF.
  [
    ["\xE0\x9F\xBF", 3],     ["\xE0\xA0\x80", 1],
    ["\xED\x9F\xBF", 1],     ["\xED\xA0\x80", 3],
    ["\xF0\x8F\xBF\xBF", 4], ["\xF0\x90\x80\x80", 1],
    ["\xF4\x8F\xBF\xBF", 1], ["\xF4\x90\x80\x80", 4],
  ].each do |s, n|
    assert_equal (UTF8STRING ? n : s.bytesize), s.length, s.inspect
  end
end

assert('String#length leaves what its walk read on the string') do
  # Counting decodes every sequence, which is the whole of what asking whether
  # the string reads as UTF-8 does, and the readers that follow are owed the
  # same answers a string counted first used to give.
  s = 'あいう'
  assert_equal 3, s.length
  assert_equal 'い', s[1]

  # A count is not a reading: bytes that spell no character count one each,
  # which is the length these have always had, wherever the break sits.
  [["あ\xffい", 3], ["あ\xe3\x81", 3], ["\x82あ", 2]].each do |str, n|
    assert_equal n, str.length, str.inspect
  end
  b = "あ\xffい"
  assert_equal 3, b.length
  assert_equal "\xff", b[1]
  assert_equal 2, b.index('い')

  # A write through the buffer takes the reading back.
  m = 'あい'
  m.length
  m[1] = "\xff"
  assert_equal 2, m.length
  assert_equal "\xff", m[1]
end if UTF8STRING

assert('String#length of a string already read as UTF-8') do
  # A string that has been read carries what its bytes spell, and a string that
  # spells characters holds one per byte that no character continues, which is
  # counted a word at a time rather than a character at a time. The two counts
  # are of the same characters, so they answer the same over strings of every
  # length up to a few words, holding characters of one to four bytes in every
  # order: a character crosses a word boundary at each offset, and the bytes
  # left over past the last whole word run from none to one short of a word.
  pool = ['a', "é", 'あ', "\u{1F600}"]
  0.upto(3) do |w|
    0.upto(24) do |k|
      s = ''
      k.times {|i| s += pool[(i + w) % 4] }
      cold = ('' + s).length      # counted with nothing read of it
      s[0]                        # read, which leaves what it reads recorded
      assert_equal k, cold, [w, k].inspect
      assert_equal k, s.length, [w, k].inspect
    end
  end
end if UTF8STRING

assert('character indexing of a string already read as UTF-8') do
  # Both directions of the mapping between a character index and a byte offset
  # read words of a string whose bytes are known to spell characters, and both
  # owe what stepping a character at a time answers: the same strings as the
  # count above, asked for every position in them.
  pool = ['a', "é", 'あ', "\u{1F600}"]
  0.upto(3) do |w|
    0.upto(20) do |k|
      chars = (0...k).map {|i| pool[(i + w) % 4] }
      s = chars.join
      s[0]                        # read, which leaves what it reads recorded
      k.times do |j|
        where = [w, k, j].inspect
        assert_equal chars[j], s[j], where
        assert_equal chars[j] + (chars[j + 1] || ''), s[j, 2], where
        assert_equal chars[j], s[j - k], where
        # the pool repeats every four characters, so the one at `j` is the
        # first of its own from there
        assert_equal j, s.index(chars[j], j), where
      end
      assert_nil s[k]
    end
  end

  # A needle lands where a character does or it is not found: these bytes are
  # the tail of a character rather than one of their own.
  a = 'あい'
  a[0]
  assert_nil a.index("\x81")
  assert_equal 1, a.index('い')
end if UTF8STRING

# 'String#match', '15.2.10.5.27' will be tested in mrbgems.

assert('String#replace', '15.2.10.5.28') do
  a = ''
  a.replace('abc')

  assert_equal 'abc', a
  assert_equal 'abc', 'cba'.replace(a)

  b = 'abc' * 10
  c = ('cba' * 10).dup
  b.replace(c)
  c.replace(b)
  assert_equal c, b

  # shared string
  s = "foo" * 100
  a = s[10, 90]                # create shared string
  assert_equal("", s.replace(""))    # clear
  assert_equal("", s)          # s is cleared
  assert_not_equal("", a)      # a should not be affected
end

assert('String#reverse', '15.2.10.5.29') do
  a = 'abc'
  a.reverse

  assert_equal 'abc', a
  assert_equal 'cba', 'abc'.reverse
end

assert('String#reverse(UTF-8)', '15.2.10.5.29') do
  a = 'こんにちは世界!'
  a.reverse

  assert_equal 'こんにちは世界!', a
  assert_equal '!界世はちにんこ', 'こんにちは世界!'.reverse
  assert_equal 'あ', 'あ'.reverse
end if UTF8STRING

assert('String#reverse!', '15.2.10.5.30') do
  a = 'abc'
  a.reverse!

  assert_equal 'cba', a
  assert_equal 'cba', 'abc'.reverse!
end

assert('String#reverse! - a frozen receiver') do
  # A string of one character or none reverses into itself and returns
  # without writing, which is where the frozen check used to sit.
  assert_raise(FrozenError) { ''.freeze.reverse! }
  assert_raise(FrozenError) { 'a'.freeze.reverse! }
  assert_raise(FrozenError) { 'ab'.freeze.reverse! }
end

assert('String#reverse!(UTF-8) - a frozen receiver') do
  # One character of several bytes is the same early return, reached by the
  # character count rather than the byte count.
  assert_raise(FrozenError) { 'あ'.freeze.reverse! }
end if UTF8STRING

assert('String - a frozen receiver of an empty splice at the end') do
  # The empty splice at the end is handed to mrb_str_cat() as an append of
  # no bytes, whose frozen check covers what the callers used to ask.
  assert_raise(FrozenError) { 'abc'.freeze.bytesplice(3, 0, '') }
  s = 'abc'.freeze
  assert_raise(FrozenError) { s[3, 0] = '' }
end

assert('String#reverse!(UTF-8)', '15.2.10.5.30') do
  a = 'こんにちは世界!'
  a.reverse!

  assert_equal '!界世はちにんこ', a
  assert_equal '!界世はちにんこ', 'こんにちは世界!'.reverse!

  b = 'あ'
  b.reverse!
  assert_equal 'あ', b
end if UTF8STRING

assert('String#rindex', '15.2.10.5.31') do
  assert_equal 0, 'abc'.rindex('a')
  assert_equal 0, 'abc'.rindex('a', 3)
  assert_nil 'abc'.rindex('a', -4)
  assert_nil 'abc'.rindex('d')
  assert_equal 6, 'abcabc'.rindex('')
  assert_equal 3, 'abcabc'.rindex('a')
  assert_equal 0, 'abcabc'.rindex('a', 1)
  assert_equal 3, 'abcabc'.rindex('a', 4)
  assert_equal 0, 'abcabc'.rindex('a', -4)
  assert_raise(ArgumentError) { "hello".rindex }
  assert_raise(TypeError) { "hello".rindex(101) }
end

assert('String#rindex(UTF-8)', '15.2.10.5.31') do
  str = "こんにちは世界!\nこんにちは世界!"
  assert_nil str.rindex('さ')
  assert_equal 12, str.rindex('ち')
  assert_equal 3, str.rindex('ち', 10)
  assert_equal 3, str.rindex('ち', -6)

  broken = "\xf0☀\xf1☁\xf2☂\xf3☃\xf0☀\xf1☁\xf2☂\xf3☃"
  assert_nil broken.rindex("\x81") # "\x81" is a part of "☁" ("\xe2\x98\x81")
  assert_equal 11, broken.rindex("☁")
  assert_equal 11, broken.rindex("☁", 12)
  assert_equal 11, broken.rindex("☁", 11)
  assert_equal  3, broken.rindex("☁", 10)
end if UTF8STRING

assert('String#rindex reaches the first character from a negative position') do
  # A negative `pos` counts characters back from the end, so minus the length
  # names the first character rather than a step past it.
  assert_equal 0, "あいう".rindex("あ", -3)
  assert_nil "あいう".rindex("あ", -4)
  assert_equal 3, "あいうあ".rindex("あ", -1)
  assert_equal 0, "あいうあ".rindex("あ", -4)
  assert_nil "あいうあ".rindex("あ", -5)
end if UTF8STRING

assert('String#rindex steps by the characters String#length counts') do
  # A byte that no lead byte reaches spells no character with its neighbours,
  # so it is one position of its own, which is what #length counts it as.
  str = "あ\x80x"
  assert_equal 3, str.length
  assert_equal 0, str.rindex("あ", -2)
  # The byte is a position of its own, but a needle spelling no character is
  # found nowhere, so neither direction reports it. They agree, which is what
  # this block is about.
  assert_nil str.rindex("\x80")
  assert_equal str.index("\x80"), str.rindex("\x80")

  # Searching backward from `pos` may not answer a position after it.
  assert_nil str.rindex("x", 1)
  assert_equal 2, str.rindex("x", 2)

  # A sequence RFC 3629 forbids spells no character either, and its bytes
  # stand alone the same way.
  assert_equal 3, "\xC0\x80a".length
  assert_nil "\xC0\x80a".rindex("\xC0")
  assert_nil "\xC0\x80a".rindex("\x80")
  assert_equal 3, "\xED\xA0\x80".length
  assert_nil "\xED\xA0\x80".rindex("\x80")

  # A lead byte the string end cuts short reaches none of the bytes that
  # follow it, so those stand alone too.
  assert_equal 3, "a\xE3\x81".length
  assert_nil "a\xE3\x81".rindex("\xE3")
  assert_nil "a\xE3\x81".rindex("\x81")
end if UTF8STRING

assert('a byte search refuses an offset inside a character') do
  # An offset that lands inside a character names no position the string has,
  # so searching from it is refused rather than started from the middle of
  # one. The boundaries are the ones #length counts over.
  s = "aあb"          # 61 E3 81 82 62; boundaries 0, 1, 4, 5
  assert_equal 4, s.byteindex("b", 1)
  assert_equal 4, s.byteindex("b", 4)
  assert_raise(IndexError) { s.byteindex("b", 2) }
  assert_raise(IndexError) { s.byteindex("b", 3) }
  assert_raise(IndexError) { s.byterindex("a", 2) }
  # a negative offset is counted from the end first, then asked the same
  assert_equal 4, s.byteindex("b", -1)
  assert_raise(IndexError) { s.byteindex("b", -2) }
  # past either end is out of range rather than off a boundary
  assert_nil s.byteindex("b", 6)
  assert_nil s.byteindex("b", -6)
  assert_equal 0, s.byterindex("a", 99)
  # ASCII has a boundary at every byte, and so does a byte-indexed string
  assert_equal 2, "abc".byteindex("c", 1)
end if UTF8STRING

assert('String#byterindex searches bytes') do
  # `byterindex` answers byte positions, so it walks bytes the way
  # `byteindex` does. Walking characters instead, it passed over every byte
  # inside a multi-byte sequence and reported nothing there.
  str = "aあb" # "\x61\xe3\x81\x82\x62"
  assert_equal 4, str.byterindex("b")
  # Whatever a needle that spells no character answers, the two directions
  # answer it alike, which is what this block is about. On a UTF-8 build that
  # is nil, since such a needle names nothing to look for; the byte search
  # itself is asked of a byte-indexed subject in mruby-string-ext's tests,
  # where String#b is available to write one.
  assert_equal str.byteindex("\x81"), str.byterindex("\x81")
  if UTF8STRING
    assert_nil str.byterindex("\xe3")
    assert_nil str.byterindex("\x81")
    assert_nil str.byterindex("\x81", 1)
  end

  assert_equal 3, 'abcabc'.byterindex('a')
  assert_equal 0, 'abcabc'.byterindex('a', 1)
  assert_equal 6, 'abcabc'.byterindex('')
  assert_nil 'abc'.byterindex('d')
end

# assert('String#scan', '15.2.10.5.32') do
#   # Not implemented yet
# end

assert('String#size', '15.2.10.5.33') do
  assert_equal 3, 'abc'.size
end

assert('String#size(UTF-8)', '15.2.10.5.33') do
  str = 'こんにちは世界!'
  assert_equal 8, str.size
  assert_not_equal str.bytesize, str.size
  assert_equal 2, str[1, 2].size
end if UTF8STRING

assert('String#size(UTF-8) counts invalid sequences per byte') do
  # RFC 3629: overlong forms, UTF-16 surrogates, and code points above
  # U+10FFFF are not characters, so each of their bytes counts on its own
  assert_equal 2, "\xC0\x80".size          # overlong NUL
  assert_equal 3, "\xE0\x9F\xBF".size      # overlong (< U+0800)
  assert_equal 3, "\xED\xA0\x80".size      # surrogate U+D800
  assert_equal 4, "\xF0\x8F\xBF\xBF".size  # overlong (< U+10000)
  assert_equal 4, "\xF4\x90\x80\x80".size  # above U+10FFFF
  assert_equal 4, "\xF5\x80\x80\x80".size  # above U+10FFFF
  assert_equal 1, "\u{D7FF}".size          # last code point before surrogates
  assert_equal 1, "\u{E000}".size          # first code point after surrogates
  assert_equal 1, "\u{10FFFF}".size        # largest valid code point
end if UTF8STRING

assert('String#size(UTF-8) counts a broken sequence beside whole characters') do
  # The bytes of a broken sequence count one each wherever they sit, so a
  # string that carries one counts more positions than it holds characters.
  assert_equal 4, "abc\x80".size
  assert_equal 4, "\x80abc".size
  assert_equal 2, "あ\x80".size
  assert_equal 2, "\x80あ".size
  assert_equal 4, "a\xE3\x81b".size
  assert_equal 4, "\u{1F600}\xC0\xAF\xC0".size
end if UTF8STRING

assert('String#slice', '15.2.10.5.34') do
  # length of args is 1
  a = 'abc'.slice(0)
  b = 'abc'.slice(-1)
  c = 'abc'.slice(10)
  d = 'abc'.slice(-10)

  # length of args is 2
  a1 = 'abc'.slice(0, -1)
  b1 = 'abc'.slice(10, 0)
  c1 = 'abc'.slice(-10, 0)
  d1 = 'abc'.slice(0, 0)
  e1 = 'abc'.slice(1, 2)

  # slice of shared string
  e11 = e1.slice(0)

  # args is RegExp
  # It will be tested in mrbgems.

  # args is String
  a3 = 'abc'.slice('bc')
  b3 = 'abc'.slice('XX')

  assert_equal 'a', a
  assert_equal 'c', b
  assert_nil c
  assert_nil d
  assert_nil a1
  assert_nil b1
  assert_nil c1
  assert_equal '', d1
  assert_equal 'bc', e1
  assert_equal 'b', e11
  assert_equal 'bc', a3
  assert_nil b3
end

# TODO Broken ATM
assert('String#split', '15.2.10.5.35') do
  # without RegExp behavior is actually unspecified
  assert_equal ['abc', 'abc', 'abc'], 'abc abc abc'.split
  assert_equal ["a", "b", "c", "", "d"], 'a,b,c,,d'.split(',')
  assert_equal ['abc', 'abc', 'abc'], 'abc abc abc'.split(nil)
  assert_equal ['a', 'b', 'c'], 'abc'.split("")
end

assert('String#split(UTF-8)', '15.2.10.5.35') do
  got = "こんにちは世界!".split('')
  assert_equal ['こ', 'ん', 'に', 'ち', 'は', '世', '界', '!'], got
  got = "こんにちは世界!".split('に')
  assert_equal ['こん', 'ちは世界!'], got
end if UTF8STRING

assert('String#sub', '15.2.10.5.36') do
  assert_equal 'aBcabc', 'abcabc'.sub('b', 'B')
  assert_equal 'aBcabc', 'abcabc'.sub('b') { |w| w.capitalize }
  assert_equal 'aa$', 'aa#'.sub('#', '$')
  assert_equal '.abc', "abc".sub("", ".")

  str = "abc"
  miss = str.sub("X", "Z")
  assert_equal str, miss
  assert_not_same str, miss

  a = []
  assert_equal '.abc', "abc".sub("") { |i| a << i; "." }
  assert_equal [""], a
end

assert('String#sub with backslash') do
  s = 'abXcdXef'
  assert_equal 'ab<\\>cdXef',    s.sub('X', '<\\\\>')
  assert_equal 'ab<X>cdXef',     s.sub('X', '<\\&>')
  assert_equal 'ab<X>cdXef',     s.sub('X', '<\\0>')
  assert_equal 'ab<ab>cdXef',    s.sub('X', '<\\`>')
  assert_equal 'ab<cdXef>cdXef', s.sub('X', '<\\\'>')
end

assert('String#sub!', '15.2.10.5.37') do
  a = 'abcabc'
  a.sub!('b', 'B')

  b = 'abcabc'
  b.sub!('b') { |w| w.capitalize }

  assert_equal 'aBcabc', a
  assert_equal 'aBcabc', b
end

assert('String#to_f', '15.2.10.5.38') do
  assert_operator(0.0, :eql?, ''.to_f)
  assert_operator(123456789.0, :eql?, '123456789'.to_f)
  assert_operator(12345.6789, :eql?, '12345.6789'.to_f)
  assert_operator(0.0, :eql?, '1e-2147483648'.to_f)
  assert_operator(Float::INFINITY, :eql?, '1e2147483648'.to_f)
  assert_operator(0.0, :eql?, 'a'.to_f)
  assert_operator(4.0, :eql?, '4a5'.to_f)
  assert_operator(12.0, :eql?, '1_2__3'.to_f)
  assert_operator(123.0, :eql?, '1_2_3'.to_f)
  assert_operator(68.0, :eql?, '68_'.to_f)
  assert_operator(68.0, :eql?, '68._7'.to_f)
  assert_operator(68.7, :eql?, '68.7_'.to_f)
  assert_operator(68.7, :eql?, '68.7_ '.to_f)
  assert_operator(6.0, :eql?, '6 8.7'.to_f)
  assert_operator(68.0, :eql?, '68. 7'.to_f)
  assert_operator(0.0, :eql?, '_68'.to_f)
  assert_operator(0.0, :eql?, ' _68'.to_f)
  assert_operator(12.34, :eql?, '1_2.3_4'.to_f)
  assert_operator(12.3, :eql?, '1_2.3__4'.to_f)
  assert_operator(0.9, :eql?, '.9'.to_f)
  assert_operator(0.9, :eql?, "\t\r\n\f\v .9 \t\r\n\f\v".to_f)
  # an extremely long digit string must not overflow the truncated-digit
  # counter; the value is far beyond Float range, so it saturates to
  # Infinity instead of triggering signed-overflow UB (#6958)
  assert_operator(Float::INFINITY, :eql?, ('9' * 200000).to_f)
end if Object.const_defined?(:Float)

assert('String#to_i', '15.2.10.5.39') do
  assert_operator 0, :eql?, ''.to_i
  assert_operator 32143, :eql?, '32143'.to_i
  assert_operator 10, :eql?, 'a'.to_i(16)
  assert_operator 4, :eql?, '100'.to_i(2)
  assert_operator 1_000, :eql?, '1_000'.to_i
  assert_operator 0, :eql?, 'a'.to_i
  assert_operator 4, :eql?, '4a5'.to_i
  assert_operator 12, :eql?, '1_2__3'.to_i
  assert_operator 123, :eql?, '1_2_3'.to_i
  assert_operator 68, :eql?, '68_'.to_i
  assert_operator 68, :eql?, '68_ '.to_i
  assert_operator 0, :eql?, '_68'.to_i
  assert_operator 0, :eql?, ' _68'.to_i
  assert_operator 68, :eql?, "\t\r\n\f\v 68 \t\r\n\f\v".to_i
  assert_operator 6, :eql?, ' 6 8 '.to_i
end

assert('String#to_s', '15.2.10.5.40') do
  assert_equal 'abc', 'abc'.to_s
end

assert('String#to_sym', '15.2.10.5.41') do
  assert_equal :abc, 'abc'.to_sym
end

assert('String#upcase', '15.2.10.5.42') do
  a = 'abc'.upcase
  b = 'abc'

  b.upcase

  assert_equal 'ABC', a
  assert_equal 'abc', b
end

assert('String#upcase!', '15.2.10.5.43') do
  a = 'abc'

  a.upcase!

  assert_equal 'ABC', a
  assert_equal nil, 'ABC'.upcase!

  a = 'abcdefghijklmnopqrstuvwxyz'
  b = a.dup
  a.upcase!
  b.upcase!
  assert_equal 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', b
end

assert('String#inspect', '15.2.10.5.46') do
  assert_equal "\"\\x00\"", "\0".inspect
  assert_equal "\"foo\"", "foo".inspect
  # a byte spelled out as hex reads out in upper case, the way CRuby writes it
  assert_equal "\"\\xAB\"", "\xAB".inspect
  if UTF8STRING
    assert_equal '"る"', "る".inspect
  else
    assert_equal '"\xE3\x82\x8B"', "る".inspect
  end

  # should not raise an exception - regress #1210
  assert_nothing_raised do
    ("\1" * 100).inspect
  end
end

# Not ISO specified

assert('String interpolation (mrb_str_concat for shared strings)') do
  a = "A" * 32
  assert_equal "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:", "#{a}:"
end

assert('String interpolation calls #to_s') do
  k = Class.new { def to_s; "called"; end }
  assert_equal "x called", "x #{k.new}"

  # A to_s that answers no String is not taken at its word: the object gets
  # the default representation instead, as a conversion to String does
  # everywhere else.
  bad = Class.new { def to_s; 42; end }
  assert_equal "#<", "#{bad.new}"[0, 2]
  nils = Class.new { def to_s; nil; end }
  assert_equal "#<", "#{nils.new}"[0, 2]

  # An exception out of to_s is raised from the interpolation, not swallowed.
  boom = Class.new { def to_s; raise "boom"; end }
  assert_raise(RuntimeError) { "#{boom.new}" }

  # Interpolations nest, and the types converted without a call are spelled
  # the way a to_s of their own would spell them.
  assert_equal "a1b", "a#{"#{1}"}b"
  assert_equal "1 sym  Integer", "#{1} #{:sym} #{nil} #{Integer}"
  assert_equal "true false", "#{true} #{false}"
  assert_equal "1.5 Infinity NaN", "#{1.5} #{1.0 / 0} #{0.0 / 0.0}" if 1.respond_to?(:to_f)
end

assert('String interpolation spells a built-in value itself') do
  # A value is spelled by what it is, with the C function the type's own
  # to_s is: an override on one of those core classes is not read back, as
  # mruby's other C paths over built-ins do not read one either. An object
  # answers for itself instead (see below).
  assert_equal "1 s  true false", "#{1} #{:s} #{nil} #{true} #{false}"
  assert_equal "1.5", "#{1.5}" if 1.respond_to?(:to_f)

  # An integer too wide to store inline is spelled here too. The shift count
  # is a variable because a constant shift out of mrb_int range makes the
  # build fail rather than raise, and the whole thing is guarded because a
  # build without mruby-bigint has no such integer to spell.
  begin
    k = 100
    big = 1 << k
    assert_equal "1267650600228229401496703205376", "#{big}"
    assert_equal big.to_s, "#{big}"
  rescue RangeError
  end

  # The same spelling every path uses, so a join, a format and an
  # interpolation cannot drift apart.
  vals = [1, :s, nil, true, false]
  assert_equal vals.map { |v| "#{v}" }.join(","), vals.join(",")
end

assert('String interpolation asks a class what it spells') do
  # A class is an object, not a value: `def self.to_s` is a method on one
  # object rather than an override of a core class, so it is answered.
  k = Class.new { def self.to_s; "named"; end }
  assert_equal "a named", "a #{k}"
  assert_equal "named", [k].join
  m = Module.new { def self.to_s; "mod"; end }
  assert_equal "mod", "#{m}"

  # A class that says nothing of its own is still spelled by its path.
  assert_equal "Integer", "#{Integer}"

  # Naming a class in a message does not go through that method: the message
  # is built while an exception is raised, which is no place to run Ruby.
  begin
    k.new.no_such_method
  rescue NoMethodError => e
    assert_false e.message.include?("named")
  end
end

assert('String#bytes') do
  str1 = "hello"
  bytes1 = [104, 101, 108, 108, 111]

  str2 = "\xFF"
  bytes2 = [0xFF]

  assert_equal bytes1, str1.bytes
  assert_equal bytes2, str2.bytes
end

assert('String#each_byte') do
  str1 = "hello"
  bytes1 = [104, 101, 108, 108, 111]
  bytes2 = []

  str1.each_byte {|b| bytes2 << b }

  assert_equal bytes1, bytes2
end

assert('String#freeze') do
  str = "hello"
  str.freeze

  assert_raise(FrozenError) { str.upcase! }
end

assert('String literal concatenation') do
  assert_equal 2, ("A" "B").size
  assert_equal 3, ('A' "B" 'C').size
  assert_equal 4, (%(A) "B#{?C}" "D").size
end

assert('String#getbyte') do
  str1 = "hello"
  bytes1 = [104, 101, 108, 108, 111]
  assert_equal bytes1[0], str1.getbyte(0)
  assert_equal bytes1[-1], str1.getbyte(-1)
  assert_equal bytes1[6], str1.getbyte(6)

  str2 = "\xFF"
  bytes2 = [0xFF]
  assert_equal bytes2[0], str2.getbyte(0)
end

assert('String#setbyte') do
  str1 = "hello"
  h = "H".getbyte(0)
  str1.setbyte(0, h)
  assert_equal(h, str1.getbyte(0))
  assert_equal("Hello", str1)
end

assert('String#byteslice') do
  str1 = "hello"
  str2 = "\u3042ab"  # "\xE3\x81\x82ab"

  assert_equal("h", str1.byteslice(0))
  assert_equal("e", str1.byteslice(1))
  assert_equal(nil, str1.byteslice(5))
  assert_equal("o", str1.byteslice(-1))
  assert_equal(nil, str1.byteslice(-6))
  assert_equal("\xE3", str2.byteslice(0))
  assert_equal("\x81", str2.byteslice(1))
  assert_equal(nil, str2.byteslice(5))
  assert_equal("b", str2.byteslice(-1))
  assert_equal(nil, str2.byteslice(-6))

  assert_equal("", str1.byteslice(0, 0))
  assert_equal(str1, str1.byteslice(0, 6))
  assert_equal("el", str1.byteslice(1, 2))
  assert_equal("", str1.byteslice(5, 1))
  assert_equal("o", str1.byteslice(-1, 6))
  assert_equal(nil, str1.byteslice(-6, 1))
  assert_equal(nil, str1.byteslice(0, -1))
  assert_equal("", str2.byteslice(0, 0))
  assert_equal(str2, str2.byteslice(0, 6))
  assert_equal("\x81\x82", str2.byteslice(1, 2))
  assert_equal("", str2.byteslice(5, 1))
  assert_equal("b", str2.byteslice(-1, 6))
  assert_equal(nil, str2.byteslice(-6, 1))
  assert_equal(nil, str2.byteslice(0, -1))

  assert_equal("ell", str1.byteslice(1..3))
  assert_equal("el", str1.byteslice(1...3))
  assert_equal("h", str1.byteslice(0..0))
  assert_equal("", str1.byteslice(5..0))
  assert_equal("o", str1.byteslice(4..5))
  assert_equal(nil, str1.byteslice(6..0))
  assert_equal("", str1.byteslice(-1..0))
  assert_equal("llo", str1.byteslice(-3..5))
  assert_equal("\x81\x82a", str2.byteslice(1..3))
  assert_equal("\x81\x82", str2.byteslice(1...3))
  assert_equal("\xE3", str2.byteslice(0..0))
  assert_equal("", str2.byteslice(5..0))
  assert_equal("b", str2.byteslice(4..5))
  assert_equal(nil, str2.byteslice(6..0))
  assert_equal("", str2.byteslice(-1..0))
  assert_equal("\x82ab", str2.byteslice(-3..5))

  assert_raise(ArgumentError) { str1.byteslice }
  assert_raise(ArgumentError) { str1.byteslice(1, 2, 3) }
  assert_raise(TypeError) { str1.byteslice("1") }
  assert_raise(TypeError) { str1.byteslice("1", 2) }
  assert_raise(TypeError) { str1.byteslice(1, "2") }
  assert_raise(TypeError) { str1.byteslice(1..2, 3) }

  skip unless Object.const_defined?(:Float)
  assert_equal("o", str1.byteslice(4.0))
  assert_equal("\x82ab", str2.byteslice(2.0, 3.0))
end

assert('String#bytesplice') do
  # range, replace (len1=len2)
  a = "0123456789"
  assert_equal "0ab3456789", a.bytesplice(1..2, "ab")

  # range, replace (len1>len2)
  a = "0123456789"
  assert_equal "0ab456789", a.bytesplice(1..3, "ab")

  # range, replace (len1<len2)
  a = "0123456789"
  assert_equal "0ab23456789", a.bytesplice(1..1, "ab")

  # idx, len, replace (len1=len2)
  a = "0123456789"
  assert_equal "0ab3456789", a.bytesplice(1, 2, "ab")

  # idx, len, replace (len1>len2)
  a = "0123456789"
  assert_equal "0ab456789", a.bytesplice(1, 3, "ab")

  # idx, len, replace (len1<len2)
  a = "0123456789"
  assert_equal "0ab23456789", a.bytesplice(1, 1, "ab")

  b = "abcdefg"
  # range, replace, range (len1=len2)
  a = "0123456789"
  assert_equal "0ab3456789", a.bytesplice(1..2, b, 0..1)

  # range, replace, range (len1>len2)
  a = "0123456789"
  assert_equal "0bc456789", a.bytesplice(1..3, b, 1..2)

  # range, replace, range (len1<len2)
  a = "0123456789"
  assert_equal "0cd23456789", a.bytesplice(1..1, b, 2..3)

  # idx, len, replace, idx, len (len1=len2)
  a = "0123456789"
  assert_equal "0ab3456789", a.bytesplice(1, 2, b, 0, 2)

  # idx, len, replace, idx, len (len1>len2)
  a = "0123456789"
  assert_equal "0bc456789", a.bytesplice(1, 3, b, 1, 2)

  # idx, len, replace, idx, len (len1<len2)
  a = "0123456789"
  assert_equal "0cd23456789", a.bytesplice(1, 1, b, 2, 2)

  # check the object type to replace
  assert_raise(TypeError) { "0123456789".bytesplice(1, 1, Object.new) }

  # check the overflow to index and length (to be pass without crash)
  assert_nothing_raised { "0123456789".bytesplice(8, ~(-1 << 31), "ab") } # for MRB_INT32
  # The shift width comes from a variable because `1 << 63` written out is
  # constant folded, and the fold fails while this file is compiled on
  # MRB_INT32 without bigint, dropping every test in it.
  shift = 63
  assert_nothing_raised { begin; "0123456789".bytesplice(8, ~(-1 << shift), "ab"); rescue ArgumentError, RangeError; end } # for MRB_INT64

  # check the negative index
  assert_equal "0ab3456789", "0123456789".bytesplice(-9, 2, "ab")
  assert_equal "ab23456789", "0123456789".bytesplice(-10, 2, "ab")
  assert_raise(IndexError) { "0123456789".bytesplice(-11, 2, "ab") }

  # check the negative length
  assert_raise(IndexError) { "0123456789".bytesplice(3, -4, "ab") }

  # with an empty string
  assert_equal "012789", "0123456789".bytesplice(3, 4, "")
end

assert('String#bytesplice on a shared buffer') do
  # A longer replacement grows the string and then writes from `idx1`, which
  # a string sharing the same buffer still reads, so growing has to take the
  # buffer away from it. Each string is shortened before it is shared, to
  # leave spare capacity behind: one that has none cannot be grown in place
  # anyway, so it would not tell the two behaviours apart.
  a = "a" * 100 + "z" * 100
  a.bytesplice(100, 100, "")
  a_slice = a[0, 60]
  a.bytesplice(0, 1, "1234567890")
  assert_equal "1234567890" + "a" * 99, a
  assert_equal "a" * 60, a_slice

  # The sharer is the one that grows, ending below what its parent holds.
  # Contract rather than detection: that is already the case where a growth
  # keeping the buffer would have to take a copy regardless.
  b = "b" * 100 + "y" * 100
  b.bytesplice(100, 100, "")
  b_slice = b[0, 60]
  b_slice.bytesplice(0, 1, "1234567890")
  assert_equal "1234567890" + "b" * 59, b_slice
  assert_equal "b" * 100, b

  # Splicing the empty range at the end appends, and an append writes only
  # above what the sharer reads, so it stays in the buffer.
  c = "c" * 100 + "x" * 100
  c.bytesplice(100, 100, "")
  c_slice = c[0, 60]
  c.bytesplice(c.bytesize, 0, "1234567890")
  assert_equal "c" * 100 + "1234567890", c
  assert_equal "c" * 60, c_slice

  # The loop that made growth at the end quadratic: the slice shares the
  # buffer again on every turn, and the append past it stays in place.
  d = ""
  50.times { d.bytesplice(d.bytesize, 0, "0123456789012345678901234567890123456789"); d[0, 30] }
  assert_equal 2000, d.bytesize
  assert_equal "0123456789012345678901234567890123456789", d.byteslice(-40, 40)
end