fs_spec.lua (39252B)
1 local uv = vim.uv 2 local bit = require('bit') 3 4 local t = require('test.unit.testutil') 5 local itp = t.gen_itp(it) 6 7 local cimport = t.cimport 8 local cppimport = t.cppimport 9 local internalize = t.internalize 10 local ok = t.ok 11 local eq = t.eq 12 local neq = t.neq 13 local ffi = t.ffi 14 local cstr = t.cstr 15 local to_cstr = t.to_cstr 16 local OK = t.OK 17 local FAIL = t.FAIL 18 local NULL = t.NULL 19 local mkdir = t.mkdir 20 local endswith = vim.endswith 21 22 local NODE_NORMAL = 0 23 local NODE_WRITABLE = 1 24 25 local fs = cimport('./src/nvim/os/os.h', './src/nvim/path.h') 26 cppimport('sys/stat.h') 27 cppimport('fcntl.h') 28 cimport('uv.h') 29 30 local s = '' 31 for i = 0, 255 do 32 s = s .. (i == 0 and '\0' or ('%c'):format(i)) 33 end 34 local fcontents = s:rep(16) 35 36 local directory = nil 37 local absolute_executable = nil 38 local executable_name = nil 39 40 local function set_bit(number, to_set) 41 return bit.bor(number, to_set) 42 end 43 44 local function unset_bit(number, to_unset) 45 return bit.band(number, (bit.bnot(to_unset))) 46 end 47 48 local function assert_file_exists(filepath) 49 neq(nil, uv.fs_stat(filepath)) 50 end 51 52 local function assert_file_does_not_exist(filepath) 53 eq(nil, uv.fs_stat(filepath)) 54 end 55 56 local function os_setperm(filename, perm) 57 return fs.os_setperm((to_cstr(filename)), perm) 58 end 59 60 local function os_getperm(filename) 61 local perm = fs.os_getperm((to_cstr(filename))) 62 return tonumber(perm) 63 end 64 65 describe('fs.c', function() 66 local function os_isdir(name) 67 return fs.os_isdir(to_cstr(name)) 68 end 69 70 before_each(function() 71 mkdir('unit-test-directory') 72 73 io.open('unit-test-directory/test.file', 'w'):close() 74 75 io.open('unit-test-directory/test_2.file', 'w'):close() 76 uv.fs_symlink('test.file', 'unit-test-directory/test_link.file') 77 78 uv.fs_symlink('non_existing_file.file', 'unit-test-directory/test_broken_link.file') 79 -- The tests are invoked with an absolute path to `busted` executable. 80 absolute_executable = arg[0] 81 -- Split the absolute_executable path into a directory and filename. 82 directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') 83 end) 84 85 after_each(function() 86 os.remove('unit-test-directory/test.file') 87 os.remove('unit-test-directory/test_2.file') 88 os.remove('unit-test-directory/test_link.file') 89 os.remove('unit-test-directory/test_hlink.file') 90 os.remove('unit-test-directory/test_broken_link.file') 91 uv.fs_rmdir('unit-test-directory') 92 end) 93 94 describe('os_dirname', function() 95 itp('returns OK and writes current directory to the buffer', function() 96 local length = string.len(uv.cwd()) + 1 97 local buf = cstr(length, '') 98 eq(OK, fs.os_dirname(buf, length)) 99 eq(uv.cwd(), ffi.string(buf)) 100 end) 101 102 itp('returns FAIL if the buffer is too small', function() 103 local length = string.len(uv.cwd()) + 1 104 local buf = cstr(length - 1, '') 105 eq(FAIL, fs.os_dirname(buf, length - 1)) 106 end) 107 end) 108 109 describe('os_chdir', function() 110 itp('fails with path="~"', function() 111 eq(false, os_isdir('~'), 'sanity check: no literal "~" directory') 112 local length = 4096 113 local expected_cwd = cstr(length, '') 114 local cwd = cstr(length, '') 115 eq(OK, fs.os_dirname(expected_cwd, length)) 116 117 -- os_chdir returns 0 for success, not OK (1). 118 neq(0, fs.os_chdir('~')) -- fail 119 neq(0, fs.os_chdir('~/')) -- fail 120 121 eq(OK, fs.os_dirname(cwd, length)) 122 -- CWD did not change. 123 eq(ffi.string(expected_cwd), ffi.string(cwd)) 124 end) 125 end) 126 127 describe('os_isdir', function() 128 itp('returns false if an empty string is given', function() 129 eq(false, (os_isdir(''))) 130 end) 131 132 itp('returns false if a nonexisting directory is given', function() 133 eq(false, (os_isdir('non-existing-directory'))) 134 end) 135 136 itp('returns false if a nonexisting absolute directory is given', function() 137 eq(false, (os_isdir('/non-existing-directory'))) 138 end) 139 140 itp('returns false if an existing file is given', function() 141 eq(false, (os_isdir('unit-test-directory/test.file'))) 142 end) 143 144 itp('returns true if the current directory is given', function() 145 eq(true, (os_isdir('.'))) 146 end) 147 148 itp('returns true if the parent directory is given', function() 149 eq(true, (os_isdir('..'))) 150 end) 151 152 itp('returns true if an arbitrary directory is given', function() 153 eq(true, (os_isdir('unit-test-directory'))) 154 end) 155 156 itp('returns true if an absolute directory is given', function() 157 eq(true, (os_isdir(directory))) 158 end) 159 end) 160 161 describe('os_can_exe', function() 162 local function os_can_exe(name) 163 local buf = ffi.new('char *[1]') 164 buf[0] = NULL 165 local ce_ret = fs.os_can_exe(to_cstr(name), buf, true) 166 167 -- When os_can_exe returns true, it must set the path. 168 -- When it returns false, the path must be NULL. 169 if ce_ret then 170 neq(NULL, buf[0]) 171 return internalize(buf[0]) 172 else 173 eq(NULL, buf[0]) 174 return nil 175 end 176 end 177 178 local function cant_exe(name) 179 eq(nil, os_can_exe(name)) 180 end 181 182 local function exe(name) 183 return os_can_exe(name) 184 end 185 186 itp('returns false when given a directory', function() 187 cant_exe('./unit-test-directory') 188 end) 189 190 itp('returns false when given a regular file without executable bit set', function() 191 cant_exe('unit-test-directory/test.file') 192 end) 193 194 itp('returns false when the given file does not exists', function() 195 cant_exe('does-not-exist.file') 196 end) 197 198 itp('returns the absolute path when given an executable inside $PATH', function() 199 local fullpath = exe('ls') 200 eq(true, fs.path_is_absolute(to_cstr(fullpath))) 201 end) 202 203 itp('returns the absolute path when given an executable relative to the current dir', function() 204 local old_dir = uv.cwd() 205 206 uv.chdir(directory) 207 208 -- Rely on currentdir to resolve symlinks, if any. Testing against 209 -- the absolute path taken from arg[0] may result in failure where 210 -- the path has a symlink in it. 211 local canonical = uv.cwd() .. '/' .. executable_name 212 local expected = exe(canonical) 213 local relative_executable = './' .. executable_name 214 local res = exe(relative_executable) 215 216 -- Don't test yet; we need to chdir back first. 217 uv.chdir(old_dir) 218 eq(expected, res) 219 end) 220 end) 221 222 describe('file permissions', function() 223 local function os_fchown(filename, user_id, group_id) 224 local fd = ffi.C.open(filename, 0) 225 local res = fs.os_fchown(fd, user_id, group_id) 226 ffi.C.close(fd) 227 return res 228 end 229 230 local function os_file_is_readable(filename) 231 return fs.os_file_is_readable((to_cstr(filename))) 232 end 233 234 local function os_file_is_writable(filename) 235 return fs.os_file_is_writable((to_cstr(filename))) 236 end 237 238 local function bit_set(number, check_bit) 239 return 0 ~= (bit.band(number, check_bit)) 240 end 241 242 describe('os_getperm', function() 243 itp('returns UV_ENOENT when the given file does not exist', function() 244 eq(ffi.C.UV_ENOENT, (os_getperm('non-existing-file'))) 245 end) 246 247 itp('returns a perm > 0 when given an existing file', function() 248 assert.is_true((os_getperm('unit-test-directory')) > 0) 249 end) 250 251 itp('returns S_IRUSR when the file is readable', function() 252 local perm = os_getperm('unit-test-directory') 253 assert.is_true((bit_set(perm, ffi.C.kS_IRUSR))) 254 end) 255 end) 256 257 describe('os_setperm', function() 258 itp('can set and unset the executable bit of a file', function() 259 local perm = os_getperm('unit-test-directory/test.file') 260 perm = unset_bit(perm, ffi.C.kS_IXUSR) 261 eq(OK, (os_setperm('unit-test-directory/test.file', perm))) 262 perm = os_getperm('unit-test-directory/test.file') 263 assert.is_false((bit_set(perm, ffi.C.kS_IXUSR))) 264 perm = set_bit(perm, ffi.C.kS_IXUSR) 265 eq(OK, os_setperm('unit-test-directory/test.file', perm)) 266 perm = os_getperm('unit-test-directory/test.file') 267 assert.is_true((bit_set(perm, ffi.C.kS_IXUSR))) 268 end) 269 270 itp('fails if given file does not exist', function() 271 local perm = ffi.C.kS_IXUSR 272 eq(FAIL, (os_setperm('non-existing-file', perm))) 273 end) 274 end) 275 276 describe('os_fchown', function() 277 local filename = 'unit-test-directory/test.file' 278 itp('does not change owner and group if respective IDs are equal to -1', function() 279 local uid = uv.fs_stat(filename).uid 280 local gid = uv.fs_stat(filename).gid 281 eq(0, os_fchown(filename, -1ULL, -1ULL)) 282 eq(uid, uv.fs_stat(filename).uid) 283 return eq(gid, uv.fs_stat(filename).gid) 284 end) 285 286 -- Some systems may not have `id` utility. 287 if os.execute('id -G > /dev/null 2>&1') ~= 0 then 288 pending('skipped (missing `id` utility)', function() end) 289 else 290 itp( 291 'owner of a file may change the group of the file to any group of which that owner is a member', 292 function() 293 local file_gid = uv.fs_stat(filename).gid 294 295 -- Gets ID of any group of which current user is a member except the 296 -- group that owns the file. 297 local id_fd = io.popen('id -G') 298 local new_gid = id_fd:read('*n') 299 if new_gid == file_gid then 300 new_gid = id_fd:read('*n') 301 end 302 id_fd:close() 303 304 -- User can be a member of only one group. 305 -- In that case we can not perform this test. 306 if new_gid then 307 eq(0, (os_fchown(filename, -1ULL, new_gid))) 308 eq(new_gid, uv.fs_stat(filename).gid) 309 end 310 end 311 ) 312 end 313 314 if ffi.os == 'Windows' or ffi.C.geteuid() == 0 then 315 pending('skipped (uv_fs_chown is no-op on Windows)', function() end) 316 else 317 itp('returns nonzero if process has not enough permissions', function() 318 -- chown to root 319 neq(0, os_fchown(filename, 0, 0)) 320 end) 321 end 322 end) 323 324 describe('os_file_is_readable', function() 325 itp('returns false if the file is not readable', function() 326 local perm = os_getperm('unit-test-directory/test.file') 327 perm = unset_bit(perm, ffi.C.kS_IRUSR) 328 perm = unset_bit(perm, ffi.C.kS_IRGRP) 329 perm = unset_bit(perm, ffi.C.kS_IROTH) 330 eq(OK, (os_setperm('unit-test-directory/test.file', perm))) 331 eq(false, os_file_is_readable('unit-test-directory/test.file')) 332 end) 333 334 itp('returns false if the file does not exist', function() 335 eq(false, os_file_is_readable('unit-test-directory/what_are_you_smoking.gif')) 336 end) 337 338 itp('returns true if the file is readable', function() 339 eq(true, os_file_is_readable('unit-test-directory/test.file')) 340 end) 341 end) 342 343 describe('os_file_is_writable', function() 344 itp('returns 0 if the file is readonly', function() 345 local perm = os_getperm('unit-test-directory/test.file') 346 perm = unset_bit(perm, ffi.C.kS_IWUSR) 347 perm = unset_bit(perm, ffi.C.kS_IWGRP) 348 perm = unset_bit(perm, ffi.C.kS_IWOTH) 349 eq(OK, (os_setperm('unit-test-directory/test.file', perm))) 350 eq(0, os_file_is_writable('unit-test-directory/test.file')) 351 end) 352 353 itp('returns 1 if the file is writable', function() 354 eq(1, os_file_is_writable('unit-test-directory/test.file')) 355 end) 356 357 itp('returns 2 when given a folder with rights to write into', function() 358 eq(2, os_file_is_writable('unit-test-directory')) 359 end) 360 end) 361 end) 362 363 describe('file operations', function() 364 local function os_path_exists(filename) 365 return fs.os_path_exists((to_cstr(filename))) 366 end 367 local function os_rename(path, new_path) 368 return fs.os_rename((to_cstr(path)), (to_cstr(new_path))) 369 end 370 local function os_remove(path) 371 return fs.os_remove((to_cstr(path))) 372 end 373 local function os_open(path, flags, mode) 374 return fs.os_open((to_cstr(path)), flags, mode) 375 end 376 local function os_close(fd) 377 return fs.os_close(fd) 378 end 379 -- For some reason if length of NUL-bytes-string is the same as `char[?]` 380 -- size luajit crashes. Though it does not do so in this test suite, better 381 -- be cautious and allocate more elements then needed. I only did this to 382 -- strings. 383 local function os_read(fd, size) 384 local buf = nil 385 if size == nil then 386 size = 0 387 else 388 buf = ffi.new('char[?]', size + 1, ('\0'):rep(size)) 389 end 390 local eof = ffi.new('bool[?]', 1, { true }) 391 local ret2 = fs.os_read(fd, eof, buf, size, false) 392 local ret1 = eof[0] 393 local ret3 = '' 394 if buf ~= nil then 395 ret3 = ffi.string(buf, size) 396 end 397 return ret1, ret2, ret3 398 end 399 local function os_readv(fd, sizes) 400 local bufs = {} 401 for i, size in ipairs(sizes) do 402 bufs[i] = { 403 iov_base = ffi.new('char[?]', size + 1, ('\0'):rep(size)), 404 iov_len = size, 405 } 406 end 407 local iov = ffi.new('struct iovec[?]', #sizes, bufs) 408 local eof = ffi.new('bool[?]', 1, { true }) 409 local ret2 = fs.os_readv(fd, eof, iov, #sizes, false) 410 local ret1 = eof[0] 411 local ret3 = {} 412 for i = 1, #sizes do 413 -- Warning: iov may not be used. 414 ret3[i] = ffi.string(bufs[i].iov_base, bufs[i].iov_len) 415 end 416 return ret1, ret2, ret3 417 end 418 local function os_write(fd, data) 419 return fs.os_write(fd, data, data and #data or 0, false) 420 end 421 422 describe('os_path_exists', function() 423 itp('returns false when given a non-existing file', function() 424 eq(false, (os_path_exists('non-existing-file'))) 425 end) 426 427 itp('returns true when given an existing file', function() 428 eq(true, (os_path_exists('unit-test-directory/test.file'))) 429 end) 430 431 itp('returns false when given a broken symlink', function() 432 eq(false, (os_path_exists('unit-test-directory/test_broken_link.file'))) 433 end) 434 435 itp('returns true when given a directory', function() 436 eq(true, (os_path_exists('unit-test-directory'))) 437 end) 438 end) 439 440 describe('os_rename', function() 441 local test = 'unit-test-directory/test.file' 442 local not_exist = 'unit-test-directory/not_exist.file' 443 444 itp('can rename file if destination file does not exist', function() 445 eq(OK, (os_rename(test, not_exist))) 446 eq(false, (os_path_exists(test))) 447 eq(true, (os_path_exists(not_exist))) 448 eq(OK, (os_rename(not_exist, test))) -- restore test file 449 end) 450 451 itp('fail if source file does not exist', function() 452 eq(FAIL, (os_rename(not_exist, test))) 453 end) 454 455 itp('can overwrite destination file if it exists', function() 456 local other = 'unit-test-directory/other.file' 457 local file = io.open(other, 'w') 458 file:write('other') 459 file:flush() 460 file:close() 461 462 eq(OK, (os_rename(other, test))) 463 eq(false, (os_path_exists(other))) 464 eq(true, (os_path_exists(test))) 465 file = io.open(test, 'r') 466 eq('other', (file:read('*all'))) 467 file:close() 468 end) 469 end) 470 471 describe('os_remove', function() 472 before_each(function() 473 io.open('unit-test-directory/test_remove.file', 'w'):close() 474 end) 475 476 after_each(function() 477 os.remove('unit-test-directory/test_remove.file') 478 end) 479 480 itp('returns non-zero when given a non-existing file', function() 481 neq(0, (os_remove('non-existing-file'))) 482 end) 483 484 itp('removes the given file and returns 0', function() 485 local f = 'unit-test-directory/test_remove.file' 486 assert_file_exists(f) 487 eq(0, (os_remove(f))) 488 assert_file_does_not_exist(f) 489 end) 490 end) 491 492 describe('os_dup', function() 493 itp('returns new file descriptor', function() 494 local dup0 = fs.os_dup(0) 495 local dup1 = fs.os_dup(1) 496 local dup2 = fs.os_dup(2) 497 local tbl = { 498 [0] = true, 499 [1] = true, 500 [2] = true, 501 [tonumber(dup0)] = true, 502 [tonumber(dup1)] = true, 503 [tonumber(dup2)] = true, 504 } 505 local i = 0 506 for _, _ in pairs(tbl) do 507 i = i + 1 508 end 509 eq(i, 6) -- All fds must be unique 510 end) 511 end) 512 513 describe('os_open', function() 514 local new_file = 'test_new_file' 515 local existing_file = 'unit-test-directory/test_existing.file' 516 517 before_each(function() 518 (io.open(existing_file, 'w')):close() 519 end) 520 521 after_each(function() 522 os.remove(existing_file) 523 os.remove(new_file) 524 end) 525 526 itp('returns UV_ENOENT for O_RDWR on a non-existing file', function() 527 eq(ffi.C.UV_ENOENT, (os_open('non-existing-file', ffi.C.kO_RDWR, 0))) 528 end) 529 530 itp( 531 'returns non-negative for O_CREAT on a non-existing file which then can be closed', 532 function() 533 assert_file_does_not_exist(new_file) 534 local fd = os_open(new_file, ffi.C.kO_CREAT, 0) 535 assert.is_true(0 <= fd) 536 eq(0, os_close(fd)) 537 end 538 ) 539 540 itp('returns non-negative for O_CREAT on a existing file which then can be closed', function() 541 assert_file_exists(existing_file) 542 local fd = os_open(existing_file, ffi.C.kO_CREAT, 0) 543 assert.is_true(0 <= fd) 544 eq(0, os_close(fd)) 545 end) 546 547 itp('returns UV_EEXIST for O_CREAT|O_EXCL on a existing file', function() 548 assert_file_exists(existing_file) 549 eq(ffi.C.UV_EEXIST, (os_open(existing_file, (bit.bor(ffi.C.kO_CREAT, ffi.C.kO_EXCL)), 0))) 550 end) 551 552 itp('sets `rwx` permissions for O_CREAT 700 which then can be closed', function() 553 assert_file_does_not_exist(new_file) 554 --create the file 555 local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber('700', 8)) 556 --verify permissions 557 eq(33216, uv.fs_stat(new_file).mode) 558 eq(0, os_close(fd)) 559 end) 560 561 itp('sets `rw` permissions for O_CREAT 600 which then can be closed', function() 562 assert_file_does_not_exist(new_file) 563 --create the file 564 local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber('600', 8)) 565 --verify permissions 566 eq(33152, uv.fs_stat(new_file).mode) 567 eq(0, os_close(fd)) 568 end) 569 570 itp( 571 'returns a non-negative file descriptor for an existing file which then can be closed', 572 function() 573 local fd = os_open(existing_file, ffi.C.kO_RDWR, 0) 574 assert.is_true(0 <= fd) 575 eq(0, os_close(fd)) 576 end 577 ) 578 end) 579 580 describe('os_close', function() 581 itp('returns EBADF for negative file descriptors', function() 582 eq(ffi.C.UV_EBADF, os_close(-1)) 583 eq(ffi.C.UV_EBADF, os_close(-1000)) 584 end) 585 end) 586 587 describe('os_read', function() 588 local file = 'test-unit-os-fs_spec-os_read.dat' 589 590 before_each(function() 591 local f = io.open(file, 'w') 592 f:write(fcontents) 593 f:close() 594 end) 595 596 after_each(function() 597 os.remove(file) 598 end) 599 600 itp('can read zero bytes from a file', function() 601 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 602 ok(fd >= 0) 603 eq({ false, 0, '' }, { os_read(fd, nil) }) 604 eq({ false, 0, '' }, { os_read(fd, 0) }) 605 eq(0, os_close(fd)) 606 end) 607 608 itp('can read from a file multiple times', function() 609 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 610 ok(fd >= 0) 611 eq({ false, 2, '\000\001' }, { os_read(fd, 2) }) 612 eq({ false, 2, '\002\003' }, { os_read(fd, 2) }) 613 eq(0, os_close(fd)) 614 end) 615 616 itp('can read the whole file at once and then report eof', function() 617 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 618 ok(fd >= 0) 619 eq({ false, #fcontents, fcontents }, { os_read(fd, #fcontents) }) 620 eq({ true, 0, ('\0'):rep(#fcontents) }, { os_read(fd, #fcontents) }) 621 eq(0, os_close(fd)) 622 end) 623 624 itp('can read the whole file in two calls, one partially', function() 625 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 626 ok(fd >= 0) 627 eq( 628 { false, #fcontents * 3 / 4, fcontents:sub(1, #fcontents * 3 / 4) }, 629 { os_read(fd, #fcontents * 3 / 4) } 630 ) 631 eq({ 632 true, 633 (#fcontents * 1 / 4), 634 fcontents:sub(#fcontents * 3 / 4 + 1) .. ('\0'):rep(#fcontents * 2 / 4), 635 }, { os_read(fd, #fcontents * 3 / 4) }) 636 eq(0, os_close(fd)) 637 end) 638 end) 639 640 describe('os_readv', function() 641 -- Function may be absent 642 if not pcall(function() 643 return fs.os_readv 644 end) then 645 return 646 end 647 local file = 'test-unit-os-fs_spec-os_readv.dat' 648 649 before_each(function() 650 local f = io.open(file, 'w') 651 f:write(fcontents) 652 f:close() 653 end) 654 655 after_each(function() 656 os.remove(file) 657 end) 658 659 itp('can read zero bytes from a file', function() 660 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 661 ok(fd >= 0) 662 eq({ false, 0, {} }, { os_readv(fd, {}) }) 663 eq({ false, 0, { '', '', '' } }, { os_readv(fd, { 0, 0, 0 }) }) 664 eq(0, os_close(fd)) 665 end) 666 667 itp('can read from a file multiple times to a differently-sized buffers', function() 668 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 669 ok(fd >= 0) 670 eq({ false, 2, { '\000\001' } }, { os_readv(fd, { 2 }) }) 671 eq({ false, 5, { '\002\003', '\004\005\006' } }, { os_readv(fd, { 2, 3 }) }) 672 eq(0, os_close(fd)) 673 end) 674 675 itp('can read the whole file at once and then report eof', function() 676 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 677 ok(fd >= 0) 678 eq({ 679 false, 680 #fcontents, 681 { 682 fcontents:sub(1, #fcontents * 1 / 4), 683 fcontents:sub(#fcontents * 1 / 4 + 1, #fcontents * 3 / 4), 684 fcontents:sub(#fcontents * 3 / 4 + 1, #fcontents * 15 / 16), 685 fcontents:sub(#fcontents * 15 / 16 + 1, #fcontents), 686 }, 687 }, { 688 os_readv( 689 fd, 690 { #fcontents * 1 / 4, #fcontents * 2 / 4, #fcontents * 3 / 16, #fcontents * 1 / 16 } 691 ), 692 }) 693 eq({ true, 0, { '\0' } }, { os_readv(fd, { 1 }) }) 694 eq(0, os_close(fd)) 695 end) 696 697 itp('can read the whole file in two calls, one partially', function() 698 local fd = os_open(file, ffi.C.kO_RDONLY, 0) 699 ok(fd >= 0) 700 eq( 701 { false, #fcontents * 3 / 4, { fcontents:sub(1, #fcontents * 3 / 4) } }, 702 { os_readv(fd, { #fcontents * 3 / 4 }) } 703 ) 704 eq({ 705 true, 706 (#fcontents * 1 / 4), 707 { fcontents:sub(#fcontents * 3 / 4 + 1) .. ('\0'):rep(#fcontents * 2 / 4) }, 708 }, { os_readv(fd, { #fcontents * 3 / 4 }) }) 709 eq(0, os_close(fd)) 710 end) 711 end) 712 713 describe('os_write', function() 714 -- Function may be absent 715 local file = 'test-unit-os-fs_spec-os_write.dat' 716 717 before_each(function() 718 local f = io.open(file, 'w') 719 f:write(fcontents) 720 f:close() 721 end) 722 723 after_each(function() 724 os.remove(file) 725 end) 726 727 itp('can write zero bytes to a file', function() 728 local fd = os_open(file, ffi.C.kO_WRONLY, 0) 729 ok(fd >= 0) 730 eq(0, os_write(fd, '')) 731 eq(0, os_write(fd, nil)) 732 eq(fcontents, io.open(file, 'r'):read('*a')) 733 eq(0, os_close(fd)) 734 end) 735 736 itp('can write some data to a file', function() 737 local fd = os_open(file, ffi.C.kO_WRONLY, 0) 738 ok(fd >= 0) 739 eq(3, os_write(fd, 'abc')) 740 eq(4, os_write(fd, ' def')) 741 eq('abc def' .. fcontents:sub(8), io.open(file, 'r'):read('*a')) 742 eq(0, os_close(fd)) 743 end) 744 end) 745 746 describe('os_nodetype', function() 747 before_each(function() 748 os.remove('non-existing-file') 749 end) 750 751 itp('returns NODE_NORMAL for non-existing file', function() 752 eq(NODE_NORMAL, fs.os_nodetype(to_cstr('non-existing-file'))) 753 end) 754 755 itp('returns NODE_WRITABLE for /dev/stderr', function() 756 eq(NODE_WRITABLE, fs.os_nodetype(to_cstr('/dev/stderr'))) 757 end) 758 end) 759 end) 760 761 describe('folder operations', function() 762 local function os_mkdir(path, mode) 763 return fs.os_mkdir(to_cstr(path), mode) 764 end 765 766 local function os_rmdir(path) 767 return fs.os_rmdir(to_cstr(path)) 768 end 769 770 local function os_mkdir_recurse(path, mode) 771 local failed_str = ffi.new('char *[1]', { nil }) 772 local created_str = ffi.new('char *[1]', { nil }) 773 local ret = fs.os_mkdir_recurse(path, mode, failed_str, created_str) 774 local failed_dir = failed_str[0] 775 if failed_dir ~= nil then 776 failed_dir = ffi.string(failed_dir) 777 end 778 local created_dir = created_str[0] 779 if created_dir ~= nil then 780 created_dir = ffi.string(created_dir) 781 end 782 return ret, failed_dir, created_dir 783 end 784 785 describe('os_mkdir', function() 786 itp('returns non-zero when given an already existing directory', function() 787 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 788 neq(0, (os_mkdir('unit-test-directory', mode))) 789 end) 790 791 itp('creates a directory and returns 0', function() 792 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 793 eq(false, (os_isdir('unit-test-directory/new-dir'))) 794 eq(0, (os_mkdir('unit-test-directory/new-dir', mode))) 795 eq(true, (os_isdir('unit-test-directory/new-dir'))) 796 uv.fs_rmdir('unit-test-directory/new-dir') 797 end) 798 end) 799 800 describe('os_mkdir_recurse', function() 801 itp('returns zero when given an already existing directory', function() 802 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 803 local ret, failed_dir, created_dir = os_mkdir_recurse('unit-test-directory', mode) 804 eq(0, ret) 805 eq(nil, failed_dir) 806 eq(nil, created_dir) 807 end) 808 809 itp('fails to create a directory where there is a file', function() 810 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 811 local ret, failed_dir, created_dir = os_mkdir_recurse('unit-test-directory/test.file', mode) 812 neq(0, ret) 813 eq('unit-test-directory/test.file', failed_dir) 814 eq(nil, created_dir) 815 end) 816 817 itp('fails to create a directory where there is a file in path', function() 818 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 819 local ret, failed_dir, created_dir = 820 os_mkdir_recurse('unit-test-directory/test.file/test', mode) 821 neq(0, ret) 822 eq('unit-test-directory/test.file', failed_dir) 823 eq(nil, created_dir) 824 end) 825 826 itp('succeeds to create a directory', function() 827 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 828 local ret, failed_dir, created_dir = 829 os_mkdir_recurse('unit-test-directory/new-dir-recurse', mode) 830 eq(0, ret) 831 eq(nil, failed_dir) 832 ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse')) 833 eq(true, os_isdir('unit-test-directory/new-dir-recurse')) 834 uv.fs_rmdir('unit-test-directory/new-dir-recurse') 835 eq(false, os_isdir('unit-test-directory/new-dir-recurse')) 836 end) 837 838 itp('succeeds to create a directory ending with ///', function() 839 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 840 local ret, failed_dir, created_dir = 841 os_mkdir_recurse('unit-test-directory/new-dir-recurse///', mode) 842 eq(0, ret) 843 eq(nil, failed_dir) 844 ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse')) 845 eq(true, os_isdir('unit-test-directory/new-dir-recurse')) 846 uv.fs_rmdir('unit-test-directory/new-dir-recurse') 847 eq(false, os_isdir('unit-test-directory/new-dir-recurse')) 848 end) 849 850 itp('succeeds to create a directory ending with /', function() 851 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 852 local ret, failed_dir, created_dir = 853 os_mkdir_recurse('unit-test-directory/new-dir-recurse/', mode) 854 eq(0, ret) 855 eq(nil, failed_dir) 856 ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse')) 857 eq(true, os_isdir('unit-test-directory/new-dir-recurse')) 858 uv.fs_rmdir('unit-test-directory/new-dir-recurse') 859 eq(false, os_isdir('unit-test-directory/new-dir-recurse')) 860 end) 861 862 itp('succeeds to create a directory tree', function() 863 local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR 864 local ret, failed_dir, created_dir = 865 os_mkdir_recurse('unit-test-directory/new-dir-recurse/1/2/3', mode) 866 eq(0, ret) 867 eq(nil, failed_dir) 868 ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse')) 869 eq(true, os_isdir('unit-test-directory/new-dir-recurse')) 870 eq(true, os_isdir('unit-test-directory/new-dir-recurse/1')) 871 eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2')) 872 eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2/3')) 873 uv.fs_rmdir('unit-test-directory/new-dir-recurse/1/2/3') 874 uv.fs_rmdir('unit-test-directory/new-dir-recurse/1/2') 875 uv.fs_rmdir('unit-test-directory/new-dir-recurse/1') 876 uv.fs_rmdir('unit-test-directory/new-dir-recurse') 877 eq(false, os_isdir('unit-test-directory/new-dir-recurse')) 878 end) 879 end) 880 881 describe('os_rmdir', function() 882 itp('returns non_zero when given a non-existing directory', function() 883 neq(0, (os_rmdir('non-existing-directory'))) 884 end) 885 886 itp('removes the given directory and returns 0', function() 887 mkdir('unit-test-directory/new-dir') 888 eq(0, os_rmdir('unit-test-directory/new-dir')) 889 eq(false, (os_isdir('unit-test-directory/new-dir'))) 890 end) 891 end) 892 end) 893 894 describe('FileInfo', function() 895 local function file_info_new() 896 local info = ffi.new('FileInfo[1]') 897 info[0].stat.st_ino = 0 898 info[0].stat.st_dev = 0 899 return info 900 end 901 902 -- Returns true if the FileInfo object has non-empty fields. 903 local function has_fileinfo(info) 904 return info[0].stat.st_ino > 0 and info[0].stat.st_dev > 0 905 end 906 907 local function file_id_new() 908 local info = ffi.new('FileID[1]') 909 info[0].inode = 0 910 info[0].device_id = 0 911 return info 912 end 913 914 describe('os_fileinfo', function() 915 itp('returns false if path=NULL', function() 916 local info = file_info_new() 917 assert.is_false((fs.os_fileinfo(nil, info))) 918 end) 919 920 itp('returns false if given a non-existing file', function() 921 local info = file_info_new() 922 assert.is_false((fs.os_fileinfo('/non-existent', info))) 923 end) 924 925 itp('returns true if given an existing file and fills FileInfo', function() 926 local info = file_info_new() 927 local path = 'unit-test-directory/test.file' 928 assert.is_true((fs.os_fileinfo(path, info))) 929 assert.is_true((has_fileinfo(info))) 930 end) 931 932 itp('returns the FileInfo of the linked file, not the link', function() 933 local info = file_info_new() 934 local path = 'unit-test-directory/test_link.file' 935 assert.is_true((fs.os_fileinfo(path, info))) 936 assert.is_true((has_fileinfo(info))) 937 local mode = tonumber(info[0].stat.st_mode) 938 return eq(ffi.C.kS_IFREG, (bit.band(mode, ffi.C.kS_IFMT))) 939 end) 940 end) 941 942 describe('os_fileinfo_link', function() 943 itp('returns false for non-existing file', function() 944 local info = file_info_new() 945 assert.is_false((fs.os_fileinfo_link('/non-existent', info))) 946 end) 947 948 itp('returns true and fills FileInfo for existing file', function() 949 local info = file_info_new() 950 local path = 'unit-test-directory/test.file' 951 assert.is_true((fs.os_fileinfo_link(path, info))) 952 assert.is_true((has_fileinfo(info))) 953 end) 954 955 itp('returns FileInfo of the link, not its target', function() 956 local info = file_info_new() 957 local link = 'unit-test-directory/test_link.file' 958 assert.is_true((fs.os_fileinfo_link(link, info))) 959 assert.is_true((has_fileinfo(info))) 960 local mode = tonumber(info[0].stat.st_mode) 961 eq(ffi.C.kS_IFLNK, (bit.band(mode, ffi.C.kS_IFMT))) 962 end) 963 end) 964 965 describe('os_fileinfo_fd', function() 966 itp('returns false if given an invalid file descriptor', function() 967 local info = file_info_new() 968 assert.is_false((fs.os_fileinfo_fd(-1, info))) 969 end) 970 971 itp('returns true if given a file descriptor and fills FileInfo', function() 972 local info = file_info_new() 973 local path = 'unit-test-directory/test.file' 974 local fd = ffi.C.open(path, 0) 975 assert.is_true((fs.os_fileinfo_fd(fd, info))) 976 assert.is_true((has_fileinfo(info))) 977 ffi.C.close(fd) 978 end) 979 end) 980 981 describe('os_fileinfo_id_equal', function() 982 itp('returns false if file infos represent different files', function() 983 local file_info_1 = file_info_new() 984 local file_info_2 = file_info_new() 985 local path_1 = 'unit-test-directory/test.file' 986 local path_2 = 'unit-test-directory/test_2.file' 987 assert.is_true((fs.os_fileinfo(path_1, file_info_1))) 988 assert.is_true((fs.os_fileinfo(path_2, file_info_2))) 989 assert.is_false((fs.os_fileinfo_id_equal(file_info_1, file_info_2))) 990 end) 991 992 itp('returns true if file infos represent the same file', function() 993 local file_info_1 = file_info_new() 994 local file_info_2 = file_info_new() 995 local path = 'unit-test-directory/test.file' 996 assert.is_true((fs.os_fileinfo(path, file_info_1))) 997 assert.is_true((fs.os_fileinfo(path, file_info_2))) 998 assert.is_true((fs.os_fileinfo_id_equal(file_info_1, file_info_2))) 999 end) 1000 1001 itp('returns true if file infos represent the same file (symlink)', function() 1002 local file_info_1 = file_info_new() 1003 local file_info_2 = file_info_new() 1004 local path_1 = 'unit-test-directory/test.file' 1005 local path_2 = 'unit-test-directory/test_link.file' 1006 assert.is_true((fs.os_fileinfo(path_1, file_info_1))) 1007 assert.is_true((fs.os_fileinfo(path_2, file_info_2))) 1008 assert.is_true((fs.os_fileinfo_id_equal(file_info_1, file_info_2))) 1009 end) 1010 end) 1011 1012 describe('os_fileinfo_id', function() 1013 itp('extracts ino/dev from FileInfo into file_id', function() 1014 local info = file_info_new() 1015 local file_id = file_id_new() 1016 local path = 'unit-test-directory/test.file' 1017 assert.is_true((fs.os_fileinfo(path, info))) 1018 fs.os_fileinfo_id(info, file_id) 1019 eq(info[0].stat.st_ino, file_id[0].inode) 1020 eq(info[0].stat.st_dev, file_id[0].device_id) 1021 end) 1022 end) 1023 1024 describe('os_fileinfo_inode', function() 1025 itp('returns the inode from FileInfo', function() 1026 local info = file_info_new() 1027 local path = 'unit-test-directory/test.file' 1028 assert.is_true((fs.os_fileinfo(path, info))) 1029 local inode = fs.os_fileinfo_inode(info) 1030 eq(info[0].stat.st_ino, inode) 1031 end) 1032 end) 1033 1034 describe('os_fileinfo_size', function() 1035 itp('returns the correct size of a file', function() 1036 local path = 'unit-test-directory/test.file' 1037 local file = io.open(path, 'w') 1038 file:write('some bytes to get filesize != 0') 1039 file:flush() 1040 file:close() 1041 local size = uv.fs_stat(path).size 1042 local info = file_info_new() 1043 assert.is_true(fs.os_fileinfo(path, info)) 1044 eq(size, fs.os_fileinfo_size(info)) 1045 end) 1046 end) 1047 1048 describe('os_fileinfo_hardlinks', function() 1049 itp('returns the correct number of hardlinks', function() 1050 local path = 'unit-test-directory/test.file' 1051 local path_link = 'unit-test-directory/test_hlink.file' 1052 local info = file_info_new() 1053 assert.is_true(fs.os_fileinfo(path, info)) 1054 eq(1, fs.os_fileinfo_hardlinks(info)) 1055 uv.fs_link(path, path_link) 1056 assert.is_true(fs.os_fileinfo(path, info)) 1057 eq(2, fs.os_fileinfo_hardlinks(info)) 1058 end) 1059 end) 1060 1061 describe('os_fileinfo_blocksize', function() 1062 itp('returns the correct blocksize of a file', function() 1063 local path = 'unit-test-directory/test.file' 1064 local blksize = uv.fs_stat(path).blksize 1065 local info = file_info_new() 1066 assert.is_true(fs.os_fileinfo(path, info)) 1067 if blksize then 1068 eq(blksize, fs.os_fileinfo_blocksize(info)) 1069 else 1070 -- luafs doesn't support blksize on windows 1071 -- libuv on windows returns a constant value as blocksize 1072 -- checking for this constant value should be enough 1073 eq(2048, fs.os_fileinfo_blocksize(info)) 1074 end 1075 end) 1076 end) 1077 1078 describe('os_fileid', function() 1079 itp('returns false if given an non-existing file', function() 1080 local file_id = file_id_new() 1081 assert.is_false((fs.os_fileid('/non-existent', file_id))) 1082 end) 1083 1084 itp('returns true if given an existing file and fills file_id', function() 1085 local file_id = file_id_new() 1086 local path = 'unit-test-directory/test.file' 1087 assert.is_true((fs.os_fileid(path, file_id))) 1088 assert.is_true(0 < file_id[0].inode) 1089 assert.is_true(0 < file_id[0].device_id) 1090 end) 1091 end) 1092 1093 describe('os_fileid_equal', function() 1094 itp('returns true if two FileIDs are equal', function() 1095 local file_id = file_id_new() 1096 local path = 'unit-test-directory/test.file' 1097 assert.is_true((fs.os_fileid(path, file_id))) 1098 assert.is_true((fs.os_fileid_equal(file_id, file_id))) 1099 end) 1100 1101 itp('returns false if two FileIDs are not equal', function() 1102 local file_id_1 = file_id_new() 1103 local file_id_2 = file_id_new() 1104 local path_1 = 'unit-test-directory/test.file' 1105 local path_2 = 'unit-test-directory/test_2.file' 1106 assert.is_true((fs.os_fileid(path_1, file_id_1))) 1107 assert.is_true((fs.os_fileid(path_2, file_id_2))) 1108 assert.is_false((fs.os_fileid_equal(file_id_1, file_id_2))) 1109 end) 1110 end) 1111 1112 describe('os_fileid_equal_fileinfo', function() 1113 itp('returns true if file_id and FileInfo represent the same file', function() 1114 local file_id = file_id_new() 1115 local info = file_info_new() 1116 local path = 'unit-test-directory/test.file' 1117 assert.is_true((fs.os_fileid(path, file_id))) 1118 assert.is_true((fs.os_fileinfo(path, info))) 1119 assert.is_true((fs.os_fileid_equal_fileinfo(file_id, info))) 1120 end) 1121 1122 itp('returns false if file_id and FileInfo represent different files', function() 1123 local file_id = file_id_new() 1124 local info = file_info_new() 1125 local path_1 = 'unit-test-directory/test.file' 1126 local path_2 = 'unit-test-directory/test_2.file' 1127 assert.is_true((fs.os_fileid(path_1, file_id))) 1128 assert.is_true((fs.os_fileinfo(path_2, info))) 1129 assert.is_false((fs.os_fileid_equal_fileinfo(file_id, info))) 1130 end) 1131 end) 1132 end) 1133 end)