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)