neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

strings_spec.lua (11115B)


      1 local t = require('test.unit.testutil')
      2 local itp = t.gen_itp(it)
      3 
      4 local child_call_once = t.child_call_once
      5 local cimport = t.cimport
      6 local eq = t.eq
      7 local ffi = t.ffi
      8 local to_cstr = t.to_cstr
      9 
     10 local strings = cimport('stdlib.h', './src/nvim/strings.h', './src/nvim/memory.h')
     11 
     12 local UVARNUM_TYPE
     13 
     14 child_call_once(function()
     15  UVARNUM_TYPE = ffi.typeof('uvarnumber_T')
     16 end)
     17 
     18 describe('vim_strsave_escaped()', function()
     19  local vim_strsave_escaped = function(s, chars)
     20    local res = strings.vim_strsave_escaped(to_cstr(s), to_cstr(chars))
     21    local ret = ffi.string(res)
     22 
     23    -- Explicitly free memory so we are sure it is allocated: if it was not it
     24    -- will crash.
     25    strings.xfree(res)
     26    return ret
     27  end
     28 
     29  itp('precedes by a backslash all chars from second argument', function()
     30    eq([[\a\b\c\d]], vim_strsave_escaped('abcd', 'abcd'))
     31  end)
     32 
     33  itp('precedes by a backslash chars only from second argument', function()
     34    eq([[\a\bcd]], vim_strsave_escaped('abcd', 'ab'))
     35  end)
     36 
     37  itp('returns a copy of passed string if second argument is empty', function()
     38    eq('text \n text', vim_strsave_escaped('text \n text', ''))
     39  end)
     40 
     41  itp('returns an empty string if first argument is empty string', function()
     42    eq('', vim_strsave_escaped('', '\r'))
     43  end)
     44 
     45  itp('returns a copy of passed string if it does not contain chars from 2nd argument', function()
     46    eq('some text', vim_strsave_escaped('some text', 'a'))
     47  end)
     48 end)
     49 
     50 describe('vim_strnsave_unquoted()', function()
     51  local vim_strnsave_unquoted = function(s, len)
     52    local res = strings.vim_strnsave_unquoted(to_cstr(s), len or #s)
     53    local ret = ffi.string(res)
     54    -- Explicitly free memory so we are sure it is allocated: if it was not it
     55    -- will crash.
     56    strings.xfree(res)
     57    return ret
     58  end
     59 
     60  itp('copies unquoted strings as-is', function()
     61    eq('-c', vim_strnsave_unquoted('-c'))
     62    eq('', vim_strnsave_unquoted(''))
     63  end)
     64 
     65  itp('respects length argument', function()
     66    eq('', vim_strnsave_unquoted('-c', 0))
     67    eq('-', vim_strnsave_unquoted('-c', 1))
     68    eq('-', vim_strnsave_unquoted('"-c', 2))
     69  end)
     70 
     71  itp('unquotes fully quoted word', function()
     72    eq('/bin/sh', vim_strnsave_unquoted('"/bin/sh"'))
     73  end)
     74 
     75  itp('unquotes partially quoted word', function()
     76    eq('/Program Files/sh', vim_strnsave_unquoted('/Program" "Files/sh'))
     77  end)
     78 
     79  itp('removes ""', function()
     80    eq('/Program Files/sh', vim_strnsave_unquoted('/""Program" "Files/sh'))
     81  end)
     82 
     83  itp('performs unescaping of "', function()
     84    eq('/"Program Files"/sh', vim_strnsave_unquoted('/"\\""Program Files"\\""/sh'))
     85  end)
     86 
     87  itp('performs unescaping of \\', function()
     88    eq('/\\Program Files\\foo/sh', vim_strnsave_unquoted('/"\\\\"Program Files"\\\\foo"/sh'))
     89  end)
     90 
     91  itp('strips quote when there is no pair to it', function()
     92    eq('/Program Files/sh', vim_strnsave_unquoted('/Program" Files/sh'))
     93    eq('', vim_strnsave_unquoted('"'))
     94  end)
     95 
     96  itp('allows string to end with one backslash unescaped', function()
     97    eq('/Program Files/sh\\', vim_strnsave_unquoted('/Program" Files/sh\\'))
     98  end)
     99 
    100  itp('does not perform unescaping out of quotes', function()
    101    eq('/Program\\ Files/sh\\', vim_strnsave_unquoted('/Program\\ Files/sh\\'))
    102  end)
    103 
    104  itp('does not unescape \\n', function()
    105    eq('/Program\\nFiles/sh', vim_strnsave_unquoted('/Program"\\n"Files/sh'))
    106  end)
    107 end)
    108 
    109 describe('vim_strchr()', function()
    110  local vim_strchr = function(s, c)
    111    local str = to_cstr(s)
    112    local res = strings.vim_strchr(str, c)
    113    if res == nil then
    114      return nil
    115    else
    116      return res - str
    117    end
    118  end
    119  itp('handles NUL and <0 correctly', function()
    120    eq(nil, vim_strchr('abc', 0))
    121    eq(nil, vim_strchr('abc', -1))
    122  end)
    123  itp('works', function()
    124    eq(0, vim_strchr('abc', ('a'):byte()))
    125    eq(1, vim_strchr('abc', ('b'):byte()))
    126    eq(2, vim_strchr('abc', ('c'):byte()))
    127    eq(0, vim_strchr('a«b»c', ('a'):byte()))
    128    eq(3, vim_strchr('a«b»c', ('b'):byte()))
    129    eq(6, vim_strchr('a«b»c', ('c'):byte()))
    130 
    131    eq(nil, vim_strchr('«»', ('«'):byte()))
    132    -- 0xAB == 171 == '«'
    133    eq(nil, vim_strchr('\171', 0xAB))
    134    eq(0, vim_strchr('«»', 0xAB))
    135    eq(3, vim_strchr('„«»“', 0xAB))
    136 
    137    eq(7, vim_strchr('„«»“', 0x201C))
    138    eq(nil, vim_strchr('„«»“', 0x201D))
    139    eq(0, vim_strchr('„«»“', 0x201E))
    140 
    141    eq(0, vim_strchr('\244\143\188\128', 0x10FF00))
    142    eq(2, vim_strchr(\244\143\188\128»', 0x10FF00))
    143    --                   |0xDBFF     |0xDF00  - surrogate pair for 0x10FF00
    144    eq(nil, vim_strchr(\237\175\191\237\188\128»', 0x10FF00))
    145  end)
    146 end)
    147 
    148 describe('vim_snprintf()', function()
    149  local function a(expected, buf, bsize, fmt, ...)
    150    local args = { ... }
    151    local ctx = string.format('snprintf(buf, %d, "%s"', bsize, fmt)
    152    for _, x in ipairs(args) do
    153      ctx = ctx .. ', ' .. tostring(x)
    154    end
    155    ctx = ctx .. string.format(') = %s', expected)
    156    eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...), ctx)
    157    if bsize > 0 then
    158      local actual = ffi.string(buf, math.min(#expected + 1, bsize))
    159      eq(expected:sub(1, bsize - 1) .. '\0', actual, ctx)
    160    end
    161  end
    162 
    163  local function uv(n)
    164    return ffi.cast(UVARNUM_TYPE, n)
    165  end
    166  local function i(n)
    167    return ffi.cast('int', n)
    168  end
    169  local function l(n)
    170    return ffi.cast('long', n)
    171  end
    172  local function ll(n)
    173    return ffi.cast('long long', n)
    174  end
    175  local function z(n)
    176    return ffi.cast('ptrdiff_t', n)
    177  end
    178  local function u(n)
    179    return ffi.cast('unsigned', n)
    180  end
    181  local function ul(n)
    182    return ffi.cast('unsigned long', n)
    183  end
    184  local function ull(n)
    185    return ffi.cast('unsigned long long', n)
    186  end
    187  local function uz(n)
    188    return ffi.cast('size_t', n)
    189  end
    190 
    191  itp('truncation', function()
    192    for bsize = 0, 14 do
    193      local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
    194      a('1.00000001e7', buf, bsize, '%.8g', 10000000.1)
    195      a('1234567', buf, bsize, '%d', i(1234567))
    196      a('1234567', buf, bsize, '%ld', l(1234567))
    197      a('  1234567', buf, bsize, '%9ld', l(1234567))
    198      a('1234567  ', buf, bsize, '%-9ld', l(1234567))
    199      a('deadbeef', buf, bsize, '%x', u(0xdeadbeef))
    200      a('001100', buf, bsize, '%06b', uv(12))
    201      a('one two', buf, bsize, '%s %s', 'one', 'two')
    202      a('1.234000', buf, bsize, '%f', 1.234)
    203      a('1.234000e+00', buf, bsize, '%e', 1.234)
    204      a('nan', buf, bsize, '%f', 0.0 / 0.0)
    205      a('inf', buf, bsize, '%f', 1.0 / 0.0)
    206      a('-inf', buf, bsize, '%f', -1.0 / 0.0)
    207      a('-0.000000', buf, bsize, '%f', tonumber('-0.0'))
    208      a('漢語', buf, bsize, '%s', '漢語')
    209      a('  漢語', buf, bsize, '%8s', '漢語')
    210      a('漢語  ', buf, bsize, '%-8s', '漢語')
    211      a('漢', buf, bsize, '%.3s', '漢語')
    212      a('  foo', buf, bsize, '%5S', 'foo')
    213      a('%%%', buf, bsize, '%%%%%%')
    214      a('0x87654321', buf, bsize, '%p', ffi.cast('char *', 0x87654321))
    215      a('0x0087654321', buf, bsize, '%012p', ffi.cast('char *', 0x87654321))
    216    end
    217  end)
    218 
    219  itp('positional arguments', function()
    220    for bsize = 0, 24 do
    221      local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
    222      a('1234567  ', buf, bsize, '%1$*2$ld', l(1234567), i(-9))
    223      a('1234567  ', buf, bsize, '%1$*2$.*3$ld', l(1234567), i(-9), i(5))
    224      a('1234567  ', buf, bsize, '%1$*3$.*2$ld', l(1234567), i(5), i(-9))
    225      a('1234567  ', buf, bsize, '%3$*1$.*2$ld', i(-9), i(5), l(1234567))
    226      a('1234567', buf, bsize, '%1$ld', l(1234567))
    227      a('  1234567', buf, bsize, '%1$*2$ld', l(1234567), i(9))
    228      a('9 12345 7654321', buf, bsize, '%2$ld %1$d %3$lu', i(12345), l(9), ul(7654321))
    229      a('9 1234567 7654321', buf, bsize, '%2$d %1$ld %3$lu', l(1234567), i(9), ul(7654321))
    230      a('9 1234567 7654321', buf, bsize, '%2$d %1$lld %3$lu', ll(1234567), i(9), ul(7654321))
    231      a('9 12345 7654321', buf, bsize, '%2$ld %1$u %3$lu', u(12345), l(9), ul(7654321))
    232      a('9 1234567 7654321', buf, bsize, '%2$d %1$lu %3$lu', ul(1234567), i(9), ul(7654321))
    233      a('9 1234567 7654321', buf, bsize, '%2$d %1$llu %3$lu', ull(1234567), i(9), ul(7654321))
    234      a('9 deadbeef 7654321', buf, bsize, '%2$d %1$x %3$lu', u(0xdeadbeef), i(9), ul(7654321))
    235      a('9 c 7654321', buf, bsize, '%2$ld %1$c %3$lu', i(('c'):byte()), l(9), ul(7654321))
    236      a('9 hi 7654321', buf, bsize, '%2$ld %1$s %3$lu', 'hi', l(9), ul(7654321))
    237      a('9 0.000000e+00 7654321', buf, bsize, '%2$ld %1$e %3$lu', 0.0, l(9), ul(7654321))
    238      a('two one two', buf, bsize, '%2$s %1$s %2$s', 'one', 'two', 'three')
    239      a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three')
    240      a('1234567', buf, bsize, '%1$d', i(1234567))
    241      a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef))
    242      a('001100', buf, bsize, '%2$0*1$b', i(6), uv(12))
    243      a('001100', buf, bsize, '%1$0.*2$b', uv(12), i(6))
    244      a('one two', buf, bsize, '%1$s %2$s', 'one', 'two')
    245      a('001100', buf, bsize, '%06b', uv(12))
    246      a('two one', buf, bsize, '%2$s %1$s', 'one', 'two')
    247      a('1.234000', buf, bsize, '%1$f', 1.234)
    248      a('1.234000e+00', buf, bsize, '%1$e', 1.234)
    249      a('nan', buf, bsize, '%1$f', 0.0 / 0.0)
    250      a('inf', buf, bsize, '%1$f', 1.0 / 0.0)
    251      a('-inf', buf, bsize, '%1$f', -1.0 / 0.0)
    252      a('-0.000000', buf, bsize, '%1$f', tonumber('-0.0'))
    253    end
    254  end)
    255 
    256  itp('%zd and %zu', function()
    257    local bsize = 20
    258    local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
    259    a('-1234567 -7654321', buf, bsize, '%zd %zd', z(-1234567), z(-7654321))
    260    a('-7654321 -1234567', buf, bsize, '%2$zd %1$zd', z(-1234567), z(-7654321))
    261    a('1234567 7654321', buf, bsize, '%zu %zu', uz(1234567), uz(7654321))
    262    a('7654321 1234567', buf, bsize, '%2$zu %1$zu', uz(1234567), uz(7654321))
    263  end)
    264 end)
    265 
    266 describe('strcase_save()', function()
    267  local strcase_save = function(input_string, upper)
    268    local res = strings.strcase_save(to_cstr(input_string), upper)
    269    return ffi.string(res)
    270  end
    271 
    272  itp('decodes overlong encoded characters.', function()
    273    eq('A', strcase_save('\xc1\x81', true))
    274    eq('a', strcase_save('\xc1\x81', false))
    275  end)
    276 end)
    277 
    278 describe('reverse_text', function()
    279  local reverse_text = function(str)
    280    return t.internalize(strings.reverse_text(to_cstr(str)))
    281  end
    282 
    283  itp('handles empty string', function()
    284    eq('', reverse_text(''))
    285  end)
    286 
    287  itp('handles simple cases', function()
    288    eq('a', reverse_text('a'))
    289    eq('ba', reverse_text('ab'))
    290  end)
    291 
    292  itp('handles multibyte characters', function()
    293    eq('bα', reverse_text('αb'))
    294    eq('Yötön yö', reverse_text('öy nötöY'))
    295  end)
    296 
    297  itp('handles combining chars', function()
    298    local utf8_COMBINING_RING_ABOVE = '\204\138'
    299    local utf8_COMBINING_RING_BELOW = '\204\165'
    300    eq(
    301      'bba' .. utf8_COMBINING_RING_ABOVE .. utf8_COMBINING_RING_BELOW .. 'aa',
    302      reverse_text('aaa' .. utf8_COMBINING_RING_ABOVE .. utf8_COMBINING_RING_BELOW .. 'bb')
    303    )
    304  end)
    305 
    306  itp('treats invalid utf as separate characters', function()
    307    eq('\192ba', reverse_text('ab\192'))
    308  end)
    309 
    310  itp('treats an incomplete utf continuation sequence as valid', function()
    311    eq('\194ba', reverse_text('ab\194'))
    312  end)
    313 end)