neovim

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

fileio_spec.lua (11527B)


      1 local uv = vim.uv
      2 
      3 local t = require('test.unit.testutil')
      4 local itp = t.gen_itp(it)
      5 
      6 local eq = t.eq
      7 local ffi = t.ffi
      8 local cimport = t.cimport
      9 local cppimport = t.cppimport
     10 local mkdir = t.mkdir
     11 
     12 local m = cimport('./src/nvim/os/os.h', './src/nvim/os/fileio.h')
     13 cppimport('fcntl.h')
     14 
     15 local fcontents = ''
     16 for i = 0, 255 do
     17  fcontents = fcontents .. (i == 0 and '\0' or ('%c'):format(i))
     18 end
     19 fcontents = fcontents:rep(16)
     20 
     21 local dir = 'Xtest-unit-file_spec.d'
     22 local file1 = dir .. '/file1.dat'
     23 local file2 = dir .. '/file2.dat'
     24 local linkf = dir .. '/file.lnk'
     25 local linkb = dir .. '/broken.lnk'
     26 local filec = dir .. '/created-file.dat'
     27 
     28 before_each(function()
     29  mkdir(dir)
     30 
     31  local f1 = io.open(file1, 'w')
     32  f1:write(fcontents)
     33  f1:close()
     34 
     35  local f2 = io.open(file2, 'w')
     36  f2:write(fcontents)
     37  f2:close()
     38 
     39  uv.fs_symlink('file1.dat', linkf)
     40  uv.fs_symlink('broken.dat', linkb)
     41 end)
     42 
     43 after_each(function()
     44  os.remove(file1)
     45  os.remove(file2)
     46  os.remove(linkf)
     47  os.remove(linkb)
     48  os.remove(filec)
     49  uv.fs_rmdir(dir)
     50 end)
     51 
     52 local function file_open(fname, flags, mode)
     53  local ret2 = ffi.new('FileDescriptor')
     54  local ret1 = m.file_open(ret2, fname, flags, mode)
     55  return ret1, ret2
     56 end
     57 
     58 local function file_open_fd(fd, flags)
     59  local ret2 = ffi.new('FileDescriptor')
     60  local ret1 = m.file_open_fd(ret2, fd, flags)
     61  return ret1, ret2
     62 end
     63 
     64 local function file_write(fp, buf)
     65  return m.file_write(fp, buf, #buf)
     66 end
     67 
     68 local function file_read(fp, size)
     69  local buf = nil
     70  if size == nil then
     71    size = 0
     72  else
     73    -- For some reason if length of NUL-bytes-string is the same as `char[?]`
     74    -- size luajit garbage collector crashes. But it does not do so in
     75    -- os_read[v] tests in os/fs_spec.lua.
     76    buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
     77  end
     78  local ret1 = m.file_read(fp, buf, size)
     79  local ret2 = ''
     80  if buf ~= nil then
     81    ret2 = ffi.string(buf, size)
     82  end
     83  return ret1, ret2
     84 end
     85 
     86 local function file_flush(fp)
     87  return m.file_flush(fp)
     88 end
     89 
     90 local function file_fsync(fp)
     91  return m.file_fsync(fp)
     92 end
     93 
     94 local function file_skip(fp, size)
     95  return m.file_skip(fp, size)
     96 end
     97 
     98 describe('file_open_fd', function()
     99  itp('can use file descriptor returned by os_open for reading', function()
    100    local fd = m.os_open(file1, m.kO_RDONLY, 0)
    101    local err, fp = file_open_fd(fd, m.kFileReadOnly)
    102    eq(0, err)
    103    eq({ #fcontents, fcontents }, { file_read(fp, #fcontents) })
    104    eq(0, m.file_close(fp, false))
    105  end)
    106  itp('can use file descriptor returned by os_open for writing', function()
    107    eq(nil, uv.fs_stat(filec))
    108    local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
    109    local err, fp = file_open_fd(fd, m.kFileWriteOnly)
    110    eq(0, err)
    111    eq(4, file_write(fp, 'test'))
    112    eq(0, m.file_close(fp, false))
    113    eq(4, uv.fs_stat(filec).size)
    114    eq('test', io.open(filec):read('*a'))
    115  end)
    116 end)
    117 
    118 describe('file_open', function()
    119  itp('can create a rwx------ file with kFileCreate', function()
    120    local err, fp = file_open(filec, m.kFileCreate, 448)
    121    eq(0, err)
    122    local attrs = uv.fs_stat(filec)
    123    eq(33216, attrs.mode)
    124    eq(0, m.file_close(fp, false))
    125  end)
    126 
    127  itp('can create a rw------- file with kFileCreate', function()
    128    local err, fp = file_open(filec, m.kFileCreate, 384)
    129    eq(0, err)
    130    local attrs = uv.fs_stat(filec)
    131    eq(33152, attrs.mode)
    132    eq(0, m.file_close(fp, false))
    133  end)
    134 
    135  itp('can create a rwx------ file with kFileCreateOnly', function()
    136    local err, fp = file_open(filec, m.kFileCreateOnly, 448)
    137    eq(0, err)
    138    local attrs = uv.fs_stat(filec)
    139    eq(33216, attrs.mode)
    140    eq(0, m.file_close(fp, false))
    141  end)
    142 
    143  itp('can create a rw------- file with kFileCreateOnly', function()
    144    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    145    eq(0, err)
    146    local attrs = uv.fs_stat(filec)
    147    eq(33152, attrs.mode)
    148    eq(0, m.file_close(fp, false))
    149  end)
    150 
    151  itp('fails to open an existing file with kFileCreateOnly', function()
    152    local err, _ = file_open(file1, m.kFileCreateOnly, 384)
    153    eq(m.UV_EEXIST, err)
    154  end)
    155 
    156  itp('fails to open an symlink with kFileNoSymlink', function()
    157    local err, _ = file_open(linkf, m.kFileNoSymlink, 384)
    158    -- err is UV_EMLINK in FreeBSD, but if I use `ok(err == m.UV_ELOOP or err ==
    159    -- m.UV_EMLINK)`, then I loose the ability to see actual `err` value.
    160    if err ~= m.UV_ELOOP then
    161      eq(m.UV_EMLINK, err)
    162    end
    163  end)
    164 
    165  itp('can open an existing file write-only with kFileCreate', function()
    166    local err, fp = file_open(file1, m.kFileCreate, 384)
    167    eq(0, err)
    168    eq(true, fp.wr)
    169    eq(0, m.file_close(fp, false))
    170  end)
    171 
    172  itp('can open an existing file read-only with zero', function()
    173    local err, fp = file_open(file1, 0, 384)
    174    eq(0, err)
    175    eq(false, fp.wr)
    176    eq(0, m.file_close(fp, false))
    177  end)
    178 
    179  itp('can open an existing file read-only with kFileReadOnly', function()
    180    local err, fp = file_open(file1, m.kFileReadOnly, 384)
    181    eq(0, err)
    182    eq(false, fp.wr)
    183    eq(0, m.file_close(fp, false))
    184  end)
    185 
    186  itp('can open an existing file read-only with kFileNoSymlink', function()
    187    local err, fp = file_open(file1, m.kFileNoSymlink, 384)
    188    eq(0, err)
    189    eq(false, fp.wr)
    190    eq(0, m.file_close(fp, false))
    191  end)
    192 
    193  itp('can truncate an existing file with kFileTruncate', function()
    194    local err, fp = file_open(file1, m.kFileTruncate, 384)
    195    eq(0, err)
    196    eq(true, fp.wr)
    197    eq(0, m.file_close(fp, false))
    198    local attrs = uv.fs_stat(file1)
    199    eq(0, attrs.size)
    200  end)
    201 
    202  itp('can open an existing file write-only with kFileWriteOnly', function()
    203    local err, fp = file_open(file1, m.kFileWriteOnly, 384)
    204    eq(0, err)
    205    eq(true, fp.wr)
    206    eq(0, m.file_close(fp, false))
    207    local attrs = uv.fs_stat(file1)
    208    eq(4096, attrs.size)
    209  end)
    210 
    211  itp('fails to create a file with just kFileWriteOnly', function()
    212    local err, _ = file_open(filec, m.kFileWriteOnly, 384)
    213    eq(m.UV_ENOENT, err)
    214    local attrs = uv.fs_stat(filec)
    215    eq(nil, attrs)
    216  end)
    217 
    218  itp('can truncate an existing file with kFileTruncate when opening a symlink', function()
    219    local err, fp = file_open(linkf, m.kFileTruncate, 384)
    220    eq(0, err)
    221    eq(true, fp.wr)
    222    eq(0, m.file_close(fp, false))
    223    local attrs = uv.fs_stat(file1)
    224    eq(0, attrs.size)
    225  end)
    226 
    227  itp('fails to open a directory write-only', function()
    228    local err, _ = file_open(dir, m.kFileWriteOnly, 384)
    229    eq(m.UV_EISDIR, err)
    230  end)
    231 
    232  itp('fails to open a broken symbolic link write-only', function()
    233    local err, _ = file_open(linkb, m.kFileWriteOnly, 384)
    234    eq(m.UV_ENOENT, err)
    235  end)
    236 
    237  itp('fails to open a broken symbolic link read-only', function()
    238    local err, _ = file_open(linkb, m.kFileReadOnly, 384)
    239    eq(m.UV_ENOENT, err)
    240  end)
    241 end)
    242 
    243 describe('file_close', function()
    244  itp('can flush writes to disk also with true argument', function()
    245    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    246    eq(0, err)
    247    local wsize = file_write(fp, 'test')
    248    eq(4, wsize)
    249    eq(0, uv.fs_stat(filec).size)
    250    eq(0, m.file_close(fp, true))
    251    eq(wsize, uv.fs_stat(filec).size)
    252  end)
    253 end)
    254 
    255 describe('file_fsync', function()
    256  itp('can flush writes to disk', function()
    257    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    258    eq(0, file_fsync(fp))
    259    eq(0, err)
    260    eq(0, uv.fs_stat(filec).size)
    261    local wsize = file_write(fp, 'test')
    262    eq(4, wsize)
    263    eq(0, uv.fs_stat(filec).size)
    264    eq(0, file_fsync(fp))
    265    eq(wsize, uv.fs_stat(filec).size)
    266    eq(0, m.file_close(fp, false))
    267  end)
    268 end)
    269 
    270 describe('file_flush', function()
    271  itp('can flush writes to disk', function()
    272    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    273    eq(0, file_flush(fp))
    274    eq(0, err)
    275    eq(0, uv.fs_stat(filec).size)
    276    local wsize = file_write(fp, 'test')
    277    eq(4, wsize)
    278    eq(0, uv.fs_stat(filec).size)
    279    eq(0, file_flush(fp))
    280    eq(wsize, uv.fs_stat(filec).size)
    281    eq(0, m.file_close(fp, false))
    282  end)
    283 end)
    284 
    285 describe('file_read', function()
    286  itp('can read small chunks of input until eof', function()
    287    local err, fp = file_open(file1, 0, 384)
    288    eq(0, err)
    289    eq(false, fp.wr)
    290    local shift = 0
    291    while shift < #fcontents do
    292      local size = 3
    293      local exp_err = size
    294      local exp_s = fcontents:sub(shift + 1, shift + size)
    295      if shift + size >= #fcontents then
    296        exp_err = #fcontents - shift
    297        exp_s = (fcontents:sub(shift + 1, shift + size) .. (('\0'):rep(size - exp_err)))
    298      end
    299      eq({ exp_err, exp_s }, { file_read(fp, size) })
    300      shift = shift + size
    301    end
    302    eq(0, m.file_close(fp, false))
    303  end)
    304 
    305  itp('can read the whole file at once', function()
    306    local err, fp = file_open(file1, 0, 384)
    307    eq(0, err)
    308    eq(false, fp.wr)
    309    eq({ #fcontents, fcontents }, { file_read(fp, #fcontents) })
    310    eq({ 0, ('\0'):rep(#fcontents) }, { file_read(fp, #fcontents) })
    311    eq(0, m.file_close(fp, false))
    312  end)
    313 
    314  itp('can read more then 1024 bytes after reading a small chunk', function()
    315    local err, fp = file_open(file1, 0, 384)
    316    eq(0, err)
    317    eq(false, fp.wr)
    318    eq({ 5, fcontents:sub(1, 5) }, { file_read(fp, 5) })
    319    eq({ #fcontents - 5, fcontents:sub(6) .. (('\0'):rep(5)) }, { file_read(fp, #fcontents) })
    320    eq(0, m.file_close(fp, false))
    321  end)
    322 
    323  itp('can read file by 768-byte-chunks', function()
    324    local err, fp = file_open(file1, 0, 384)
    325    eq(0, err)
    326    eq(false, fp.wr)
    327    local shift = 0
    328    while shift < #fcontents do
    329      local size = 768
    330      local exp_err = size
    331      local exp_s = fcontents:sub(shift + 1, shift + size)
    332      if shift + size >= #fcontents then
    333        exp_err = #fcontents - shift
    334        exp_s = (fcontents:sub(shift + 1, shift + size) .. (('\0'):rep(size - exp_err)))
    335      end
    336      eq({ exp_err, exp_s }, { file_read(fp, size) })
    337      shift = shift + size
    338    end
    339    eq(0, m.file_close(fp, false))
    340  end)
    341 end)
    342 
    343 describe('file_write', function()
    344  itp('can write the whole file at once', function()
    345    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    346    eq(0, err)
    347    eq(true, fp.wr)
    348    local wr = file_write(fp, fcontents)
    349    eq(#fcontents, wr)
    350    eq(0, m.file_close(fp, false))
    351    eq(wr, uv.fs_stat(filec).size)
    352    eq(fcontents, io.open(filec):read('*a'))
    353  end)
    354 
    355  itp('can write the whole file by small chunks', function()
    356    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    357    eq(0, err)
    358    eq(true, fp.wr)
    359    local shift = 0
    360    while shift < #fcontents do
    361      local size = 3
    362      local s = fcontents:sub(shift + 1, shift + size)
    363      local wr = file_write(fp, s)
    364      eq(wr, #s)
    365      shift = shift + size
    366    end
    367    eq(0, m.file_close(fp, false))
    368    eq(#fcontents, uv.fs_stat(filec).size)
    369    eq(fcontents, io.open(filec):read('*a'))
    370  end)
    371 
    372  itp('can write the whole file by 768-byte-chunks', function()
    373    local err, fp = file_open(filec, m.kFileCreateOnly, 384)
    374    eq(0, err)
    375    eq(true, fp.wr)
    376    local shift = 0
    377    while shift < #fcontents do
    378      local size = 768
    379      local s = fcontents:sub(shift + 1, shift + size)
    380      local wr = file_write(fp, s)
    381      eq(wr, #s)
    382      shift = shift + size
    383    end
    384    eq(0, m.file_close(fp, false))
    385    eq(#fcontents, uv.fs_stat(filec).size)
    386    eq(fcontents, io.open(filec):read('*a'))
    387  end)
    388 end)
    389 
    390 describe('file_skip', function()
    391  itp('can skip 3 bytes', function()
    392    local err, fp = file_open(file1, 0, 384)
    393    eq(0, err)
    394    eq(false, fp.wr)
    395    eq(3, file_skip(fp, 3))
    396    local rd, s = file_read(fp, 3)
    397    eq(3, rd)
    398    eq(fcontents:sub(4, 6), s)
    399    eq(0, m.file_close(fp, false))
    400  end)
    401 end)