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)