mirror of
https://github.com/neovim/neovim.git
synced 2025-09-08 12:28:18 +00:00

test: replace lfs with luv luv already pretty much does everything lfs does, so this duplication of dependencies isn't needed.
1102 lines
38 KiB
Lua
1102 lines
38 KiB
Lua
local luv = require('luv')
|
|
local bit = require('bit')
|
|
|
|
local helpers = require('test.unit.helpers')(after_each)
|
|
local itp = helpers.gen_itp(it)
|
|
|
|
local cimport = helpers.cimport
|
|
local cppimport = helpers.cppimport
|
|
local internalize = helpers.internalize
|
|
local ok = helpers.ok
|
|
local eq = helpers.eq
|
|
local neq = helpers.neq
|
|
local ffi = helpers.ffi
|
|
local cstr = helpers.cstr
|
|
local to_cstr = helpers.to_cstr
|
|
local OK = helpers.OK
|
|
local FAIL = helpers.FAIL
|
|
local NULL = helpers.NULL
|
|
local mkdir = helpers.mkdir
|
|
|
|
local NODE_NORMAL = 0
|
|
local NODE_WRITABLE = 1
|
|
|
|
cimport('./src/nvim/os/shell.h')
|
|
cimport('./src/nvim/option_defs.h')
|
|
cimport('./src/nvim/main.h')
|
|
cimport('./src/nvim/fileio.h')
|
|
local fs = cimport('./src/nvim/os/os.h', './src/nvim/path.h')
|
|
cppimport('sys/stat.h')
|
|
cppimport('fcntl.h')
|
|
cimport('uv.h')
|
|
|
|
local s = ''
|
|
for i = 0, 255 do
|
|
s = s .. (i == 0 and '\0' or ('%c'):format(i))
|
|
end
|
|
local fcontents = s:rep(16)
|
|
|
|
local directory = nil
|
|
local absolute_executable = nil
|
|
local executable_name = nil
|
|
|
|
local function set_bit(number, to_set)
|
|
return bit.bor(number, to_set)
|
|
end
|
|
|
|
local function unset_bit(number, to_unset)
|
|
return bit.band(number, (bit.bnot(to_unset)))
|
|
end
|
|
|
|
local function assert_file_exists(filepath)
|
|
neq(nil, luv.fs_stat(filepath))
|
|
end
|
|
|
|
local function assert_file_does_not_exist(filepath)
|
|
eq(nil, luv.fs_stat(filepath))
|
|
end
|
|
|
|
local function os_setperm(filename, perm)
|
|
return fs.os_setperm((to_cstr(filename)), perm)
|
|
end
|
|
|
|
local function os_getperm(filename)
|
|
local perm = fs.os_getperm((to_cstr(filename)))
|
|
return tonumber(perm)
|
|
end
|
|
|
|
describe('fs.c', function()
|
|
local function os_isdir(name)
|
|
return fs.os_isdir(to_cstr(name))
|
|
end
|
|
|
|
before_each(function()
|
|
mkdir('unit-test-directory');
|
|
|
|
io.open('unit-test-directory/test.file', 'w'):close()
|
|
|
|
io.open('unit-test-directory/test_2.file', 'w'):close()
|
|
luv.fs_symlink('test.file', 'unit-test-directory/test_link.file')
|
|
|
|
luv.fs_symlink('non_existing_file.file', 'unit-test-directory/test_broken_link.file')
|
|
-- The tests are invoked with an absolute path to `busted` executable.
|
|
absolute_executable = arg[0]
|
|
-- Split the absolute_executable path into a directory and filename.
|
|
directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$')
|
|
end)
|
|
|
|
after_each(function()
|
|
os.remove('unit-test-directory/test.file')
|
|
os.remove('unit-test-directory/test_2.file')
|
|
os.remove('unit-test-directory/test_link.file')
|
|
os.remove('unit-test-directory/test_hlink.file')
|
|
os.remove('unit-test-directory/test_broken_link.file')
|
|
luv.fs_rmdir('unit-test-directory')
|
|
end)
|
|
|
|
describe('os_dirname', function()
|
|
itp('returns OK and writes current directory to the buffer', function()
|
|
local length = string.len(luv.cwd()) + 1
|
|
local buf = cstr(length, '')
|
|
eq(OK, fs.os_dirname(buf, length))
|
|
eq(luv.cwd(), ffi.string(buf))
|
|
end)
|
|
|
|
itp('returns FAIL if the buffer is too small', function()
|
|
local length = string.len(luv.cwd()) + 1
|
|
local buf = cstr(length - 1, '')
|
|
eq(FAIL, fs.os_dirname(buf, length - 1))
|
|
end)
|
|
end)
|
|
|
|
describe('os_chdir', function()
|
|
itp('fails with path="~"', function()
|
|
eq(false, os_isdir('~'), 'sanity check: no literal "~" directory')
|
|
local length = 4096
|
|
local expected_cwd = cstr(length, '')
|
|
local cwd = cstr(length, '')
|
|
eq(OK, fs.os_dirname(expected_cwd, length))
|
|
|
|
-- os_chdir returns 0 for success, not OK (1).
|
|
neq(0, fs.os_chdir('~')) -- fail
|
|
neq(0, fs.os_chdir('~/')) -- fail
|
|
|
|
eq(OK, fs.os_dirname(cwd, length))
|
|
-- CWD did not change.
|
|
eq(ffi.string(expected_cwd), ffi.string(cwd))
|
|
end)
|
|
end)
|
|
|
|
describe('os_isdir', function()
|
|
itp('returns false if an empty string is given', function()
|
|
eq(false, (os_isdir('')))
|
|
end)
|
|
|
|
itp('returns false if a nonexisting directory is given', function()
|
|
eq(false, (os_isdir('non-existing-directory')))
|
|
end)
|
|
|
|
itp('returns false if a nonexisting absolute directory is given', function()
|
|
eq(false, (os_isdir('/non-existing-directory')))
|
|
end)
|
|
|
|
itp('returns false if an existing file is given', function()
|
|
eq(false, (os_isdir('unit-test-directory/test.file')))
|
|
end)
|
|
|
|
itp('returns true if the current directory is given', function()
|
|
eq(true, (os_isdir('.')))
|
|
end)
|
|
|
|
itp('returns true if the parent directory is given', function()
|
|
eq(true, (os_isdir('..')))
|
|
end)
|
|
|
|
itp('returns true if an arbitrary directory is given', function()
|
|
eq(true, (os_isdir('unit-test-directory')))
|
|
end)
|
|
|
|
itp('returns true if an absolute directory is given', function()
|
|
eq(true, (os_isdir(directory)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_can_exe', function()
|
|
local function os_can_exe(name)
|
|
local buf = ffi.new('char *[1]')
|
|
buf[0] = NULL
|
|
local ce_ret = fs.os_can_exe(to_cstr(name), buf, true)
|
|
|
|
-- When os_can_exe returns true, it must set the path.
|
|
-- When it returns false, the path must be NULL.
|
|
if ce_ret then
|
|
neq(NULL, buf[0])
|
|
return internalize(buf[0])
|
|
else
|
|
eq(NULL, buf[0])
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function cant_exe(name)
|
|
eq(nil, os_can_exe(name))
|
|
end
|
|
|
|
local function exe(name)
|
|
return os_can_exe(name)
|
|
end
|
|
|
|
itp('returns false when given a directory', function()
|
|
cant_exe('./unit-test-directory')
|
|
end)
|
|
|
|
itp('returns false when given a regular file without executable bit set', function()
|
|
cant_exe('unit-test-directory/test.file')
|
|
end)
|
|
|
|
itp('returns false when the given file does not exists', function()
|
|
cant_exe('does-not-exist.file')
|
|
end)
|
|
|
|
itp('returns the absolute path when given an executable inside $PATH', function()
|
|
local fullpath = exe('ls')
|
|
eq(1, fs.path_is_absolute(to_cstr(fullpath)))
|
|
end)
|
|
|
|
itp('returns the absolute path when given an executable relative to the current dir', function()
|
|
local old_dir = luv.cwd()
|
|
|
|
luv.chdir(directory)
|
|
|
|
-- Rely on currentdir to resolve symlinks, if any. Testing against
|
|
-- the absolute path taken from arg[0] may result in failure where
|
|
-- the path has a symlink in it.
|
|
local canonical = luv.cwd() .. '/' .. executable_name
|
|
local expected = exe(canonical)
|
|
local relative_executable = './' .. executable_name
|
|
local res = exe(relative_executable)
|
|
|
|
-- Don't test yet; we need to chdir back first.
|
|
luv.chdir(old_dir)
|
|
eq(expected, res)
|
|
end)
|
|
end)
|
|
|
|
describe('file permissions', function()
|
|
local function os_fchown(filename, user_id, group_id)
|
|
local fd = ffi.C.open(filename, 0)
|
|
local res = fs.os_fchown(fd, user_id, group_id)
|
|
ffi.C.close(fd)
|
|
return res
|
|
end
|
|
|
|
local function os_file_is_readable(filename)
|
|
return fs.os_file_is_readable((to_cstr(filename)))
|
|
end
|
|
|
|
local function os_file_is_writable(filename)
|
|
return fs.os_file_is_writable((to_cstr(filename)))
|
|
end
|
|
|
|
local function bit_set(number, check_bit)
|
|
return 0 ~= (bit.band(number, check_bit))
|
|
end
|
|
|
|
describe('os_getperm', function()
|
|
itp('returns UV_ENOENT when the given file does not exist', function()
|
|
eq(ffi.C.UV_ENOENT, (os_getperm('non-existing-file')))
|
|
end)
|
|
|
|
itp('returns a perm > 0 when given an existing file', function()
|
|
assert.is_true((os_getperm('unit-test-directory')) > 0)
|
|
end)
|
|
|
|
itp('returns S_IRUSR when the file is readable', function()
|
|
local perm = os_getperm('unit-test-directory')
|
|
assert.is_true((bit_set(perm, ffi.C.kS_IRUSR)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_setperm', function()
|
|
itp('can set and unset the executable bit of a file', function()
|
|
local perm = os_getperm('unit-test-directory/test.file')
|
|
perm = unset_bit(perm, ffi.C.kS_IXUSR)
|
|
eq(OK, (os_setperm('unit-test-directory/test.file', perm)))
|
|
perm = os_getperm('unit-test-directory/test.file')
|
|
assert.is_false((bit_set(perm, ffi.C.kS_IXUSR)))
|
|
perm = set_bit(perm, ffi.C.kS_IXUSR)
|
|
eq(OK, os_setperm('unit-test-directory/test.file', perm))
|
|
perm = os_getperm('unit-test-directory/test.file')
|
|
assert.is_true((bit_set(perm, ffi.C.kS_IXUSR)))
|
|
end)
|
|
|
|
itp('fails if given file does not exist', function()
|
|
local perm = ffi.C.kS_IXUSR
|
|
eq(FAIL, (os_setperm('non-existing-file', perm)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fchown', function()
|
|
local filename = 'unit-test-directory/test.file'
|
|
itp('does not change owner and group if respective IDs are equal to -1', function()
|
|
local uid = luv.fs_stat(filename).uid
|
|
local gid = luv.fs_stat(filename).gid
|
|
eq(0, os_fchown(filename, -1, -1))
|
|
eq(uid, luv.fs_stat(filename).uid)
|
|
return eq(gid, luv.fs_stat(filename).gid)
|
|
end)
|
|
|
|
-- Some systems may not have `id` utility.
|
|
if (os.execute('id -G > /dev/null 2>&1') ~= 0) then
|
|
pending('skipped (missing `id` utility)', function() end)
|
|
else
|
|
itp('owner of a file may change the group of the file to any group of which that owner is a member', function()
|
|
local file_gid = luv.fs_stat(filename).gid
|
|
|
|
-- Gets ID of any group of which current user is a member except the
|
|
-- group that owns the file.
|
|
local id_fd = io.popen('id -G')
|
|
local new_gid = id_fd:read('*n')
|
|
if (new_gid == file_gid) then
|
|
new_gid = id_fd:read('*n')
|
|
end
|
|
id_fd:close()
|
|
|
|
-- User can be a member of only one group.
|
|
-- In that case we can not perform this test.
|
|
if new_gid then
|
|
eq(0, (os_fchown(filename, -1, new_gid)))
|
|
eq(new_gid, luv.fs_stat(filename).gid)
|
|
end
|
|
end)
|
|
end
|
|
|
|
if (ffi.os == 'Windows' or ffi.C.geteuid() == 0) then
|
|
pending('skipped (uv_fs_chown is no-op on Windows)', function() end)
|
|
else
|
|
itp('returns nonzero if process has not enough permissions', function()
|
|
-- chown to root
|
|
neq(0, os_fchown(filename, 0, 0))
|
|
end)
|
|
end
|
|
end)
|
|
|
|
|
|
describe('os_file_is_readable', function()
|
|
itp('returns false if the file is not readable', function()
|
|
local perm = os_getperm('unit-test-directory/test.file')
|
|
perm = unset_bit(perm, ffi.C.kS_IRUSR)
|
|
perm = unset_bit(perm, ffi.C.kS_IRGRP)
|
|
perm = unset_bit(perm, ffi.C.kS_IROTH)
|
|
eq(OK, (os_setperm('unit-test-directory/test.file', perm)))
|
|
eq(false, os_file_is_readable('unit-test-directory/test.file'))
|
|
end)
|
|
|
|
itp('returns false if the file does not exist', function()
|
|
eq(false, os_file_is_readable(
|
|
'unit-test-directory/what_are_you_smoking.gif'))
|
|
end)
|
|
|
|
itp('returns true if the file is readable', function()
|
|
eq(true, os_file_is_readable(
|
|
'unit-test-directory/test.file'))
|
|
end)
|
|
end)
|
|
|
|
describe('os_file_is_writable', function()
|
|
itp('returns 0 if the file is readonly', function()
|
|
local perm = os_getperm('unit-test-directory/test.file')
|
|
perm = unset_bit(perm, ffi.C.kS_IWUSR)
|
|
perm = unset_bit(perm, ffi.C.kS_IWGRP)
|
|
perm = unset_bit(perm, ffi.C.kS_IWOTH)
|
|
eq(OK, (os_setperm('unit-test-directory/test.file', perm)))
|
|
eq(0, os_file_is_writable('unit-test-directory/test.file'))
|
|
end)
|
|
|
|
itp('returns 1 if the file is writable', function()
|
|
eq(1, os_file_is_writable('unit-test-directory/test.file'))
|
|
end)
|
|
|
|
itp('returns 2 when given a folder with rights to write into', function()
|
|
eq(2, os_file_is_writable('unit-test-directory'))
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('file operations', function()
|
|
local function os_path_exists(filename)
|
|
return fs.os_path_exists((to_cstr(filename)))
|
|
end
|
|
local function os_rename(path, new_path)
|
|
return fs.os_rename((to_cstr(path)), (to_cstr(new_path)))
|
|
end
|
|
local function os_remove(path)
|
|
return fs.os_remove((to_cstr(path)))
|
|
end
|
|
local function os_open(path, flags, mode)
|
|
return fs.os_open((to_cstr(path)), flags, mode)
|
|
end
|
|
local function os_close(fd)
|
|
return fs.os_close(fd)
|
|
end
|
|
-- For some reason if length of NUL-bytes-string is the same as `char[?]`
|
|
-- size luajit crashes. Though it does not do so in this test suite, better
|
|
-- be cautios and allocate more elements then needed. I only did this to
|
|
-- strings.
|
|
local function os_read(fd, size)
|
|
local buf = nil
|
|
if size == nil then
|
|
size = 0
|
|
else
|
|
buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
|
|
end
|
|
local eof = ffi.new('bool[?]', 1, {true})
|
|
local ret2 = fs.os_read(fd, eof, buf, size, false)
|
|
local ret1 = eof[0]
|
|
local ret3 = ''
|
|
if buf ~= nil then
|
|
ret3 = ffi.string(buf, size)
|
|
end
|
|
return ret1, ret2, ret3
|
|
end
|
|
local function os_readv(fd, sizes)
|
|
local bufs = {}
|
|
for i, size in ipairs(sizes) do
|
|
bufs[i] = {
|
|
iov_base=ffi.new('char[?]', size + 1, ('\0'):rep(size)),
|
|
iov_len=size,
|
|
}
|
|
end
|
|
local iov = ffi.new('struct iovec[?]', #sizes, bufs)
|
|
local eof = ffi.new('bool[?]', 1, {true})
|
|
local ret2 = fs.os_readv(fd, eof, iov, #sizes, false)
|
|
local ret1 = eof[0]
|
|
local ret3 = {}
|
|
for i = 1,#sizes do
|
|
-- Warning: iov may not be used.
|
|
ret3[i] = ffi.string(bufs[i].iov_base, bufs[i].iov_len)
|
|
end
|
|
return ret1, ret2, ret3
|
|
end
|
|
local function os_write(fd, data)
|
|
return fs.os_write(fd, data, data and #data or 0, false)
|
|
end
|
|
|
|
describe('os_path_exists', function()
|
|
itp('returns false when given a non-existing file', function()
|
|
eq(false, (os_path_exists('non-existing-file')))
|
|
end)
|
|
|
|
itp('returns true when given an existing file', function()
|
|
eq(true, (os_path_exists('unit-test-directory/test.file')))
|
|
end)
|
|
|
|
itp('returns false when given a broken symlink', function()
|
|
eq(false, (os_path_exists('unit-test-directory/test_broken_link.file')))
|
|
end)
|
|
|
|
itp('returns true when given a directory', function()
|
|
eq(true, (os_path_exists('unit-test-directory')))
|
|
end)
|
|
end)
|
|
|
|
describe('os_rename', function()
|
|
local test = 'unit-test-directory/test.file'
|
|
local not_exist = 'unit-test-directory/not_exist.file'
|
|
|
|
itp('can rename file if destination file does not exist', function()
|
|
eq(OK, (os_rename(test, not_exist)))
|
|
eq(false, (os_path_exists(test)))
|
|
eq(true, (os_path_exists(not_exist)))
|
|
eq(OK, (os_rename(not_exist, test))) -- restore test file
|
|
end)
|
|
|
|
itp('fail if source file does not exist', function()
|
|
eq(FAIL, (os_rename(not_exist, test)))
|
|
end)
|
|
|
|
itp('can overwrite destination file if it exists', function()
|
|
local other = 'unit-test-directory/other.file'
|
|
local file = io.open(other, 'w')
|
|
file:write('other')
|
|
file:flush()
|
|
file:close()
|
|
|
|
eq(OK, (os_rename(other, test)))
|
|
eq(false, (os_path_exists(other)))
|
|
eq(true, (os_path_exists(test)))
|
|
file = io.open(test, 'r')
|
|
eq('other', (file:read('*all')))
|
|
file:close()
|
|
end)
|
|
end)
|
|
|
|
describe('os_remove', function()
|
|
before_each(function()
|
|
io.open('unit-test-directory/test_remove.file', 'w'):close()
|
|
end)
|
|
|
|
after_each(function()
|
|
os.remove('unit-test-directory/test_remove.file')
|
|
end)
|
|
|
|
itp('returns non-zero when given a non-existing file', function()
|
|
neq(0, (os_remove('non-existing-file')))
|
|
end)
|
|
|
|
itp('removes the given file and returns 0', function()
|
|
local f = 'unit-test-directory/test_remove.file'
|
|
assert_file_exists(f)
|
|
eq(0, (os_remove(f)))
|
|
assert_file_does_not_exist(f)
|
|
end)
|
|
end)
|
|
|
|
describe('os_dup', function()
|
|
itp('returns new file descriptor', function()
|
|
local dup0 = fs.os_dup(0)
|
|
local dup1 = fs.os_dup(1)
|
|
local dup2 = fs.os_dup(2)
|
|
local tbl = {[0]=true, [1]=true, [2]=true,
|
|
[tonumber(dup0)]=true, [tonumber(dup1)]=true,
|
|
[tonumber(dup2)]=true}
|
|
local i = 0
|
|
for _, _ in pairs(tbl) do
|
|
i = i + 1
|
|
end
|
|
eq(i, 6) -- All fds must be unique
|
|
end)
|
|
end)
|
|
|
|
describe('os_open', function()
|
|
local new_file = 'test_new_file'
|
|
local existing_file = 'unit-test-directory/test_existing.file'
|
|
|
|
before_each(function()
|
|
(io.open(existing_file, 'w')):close()
|
|
end)
|
|
|
|
after_each(function()
|
|
os.remove(existing_file)
|
|
os.remove(new_file)
|
|
end)
|
|
|
|
itp('returns UV_ENOENT for O_RDWR on a non-existing file', function()
|
|
eq(ffi.C.UV_ENOENT, (os_open('non-existing-file', ffi.C.kO_RDWR, 0)))
|
|
end)
|
|
|
|
itp('returns non-negative for O_CREAT on a non-existing file which then can be closed', function()
|
|
assert_file_does_not_exist(new_file)
|
|
local fd = os_open(new_file, ffi.C.kO_CREAT, 0)
|
|
assert.is_true(0 <= fd)
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('returns non-negative for O_CREAT on a existing file which then can be closed', function()
|
|
assert_file_exists(existing_file)
|
|
local fd = os_open(existing_file, ffi.C.kO_CREAT, 0)
|
|
assert.is_true(0 <= fd)
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('returns UV_EEXIST for O_CREAT|O_EXCL on a existing file', function()
|
|
assert_file_exists(existing_file)
|
|
eq(ffi.C.UV_EEXIST, (os_open(existing_file, (bit.bor(ffi.C.kO_CREAT, ffi.C.kO_EXCL)), 0)))
|
|
end)
|
|
|
|
itp('sets `rwx` permissions for O_CREAT 700 which then can be closed', function()
|
|
assert_file_does_not_exist(new_file)
|
|
--create the file
|
|
local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8))
|
|
--verify permissions
|
|
eq(33216, luv.fs_stat(new_file).mode)
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('sets `rw` permissions for O_CREAT 600 which then can be closed', function()
|
|
assert_file_does_not_exist(new_file)
|
|
--create the file
|
|
local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8))
|
|
--verify permissions
|
|
eq(33152, luv.fs_stat(new_file).mode)
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('returns a non-negative file descriptor for an existing file which then can be closed', function()
|
|
local fd = os_open(existing_file, ffi.C.kO_RDWR, 0)
|
|
assert.is_true(0 <= fd)
|
|
eq(0, os_close(fd))
|
|
end)
|
|
end)
|
|
|
|
describe('os_close', function()
|
|
itp('returns EBADF for negative file descriptors', function()
|
|
eq(ffi.C.UV_EBADF, os_close(-1))
|
|
eq(ffi.C.UV_EBADF, os_close(-1000))
|
|
end)
|
|
end)
|
|
|
|
describe('os_read', function()
|
|
local file = 'test-unit-os-fs_spec-os_read.dat'
|
|
|
|
before_each(function()
|
|
local f = io.open(file, 'w')
|
|
f:write(fcontents)
|
|
f:close()
|
|
end)
|
|
|
|
after_each(function()
|
|
os.remove(file)
|
|
end)
|
|
|
|
itp('can read zero bytes from a file', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, 0, ''}, {os_read(fd, nil)})
|
|
eq({false, 0, ''}, {os_read(fd, 0)})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can read from a file multiple times', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, 2, '\000\001'}, {os_read(fd, 2)})
|
|
eq({false, 2, '\002\003'}, {os_read(fd, 2)})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can read the whole file at once and then report eof', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, #fcontents, fcontents}, {os_read(fd, #fcontents)})
|
|
eq({true, 0, ('\0'):rep(#fcontents)}, {os_read(fd, #fcontents)})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can read the whole file in two calls, one partially', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, #fcontents * 3/4, fcontents:sub(1, #fcontents * 3/4)},
|
|
{os_read(fd, #fcontents * 3/4)})
|
|
eq({true,
|
|
(#fcontents * 1/4),
|
|
fcontents:sub(#fcontents * 3/4 + 1) .. ('\0'):rep(#fcontents * 2/4)},
|
|
{os_read(fd, #fcontents * 3/4)})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
end)
|
|
|
|
describe('os_readv', function()
|
|
-- Function may be absent
|
|
if not pcall(function() return fs.os_readv end) then
|
|
return
|
|
end
|
|
local file = 'test-unit-os-fs_spec-os_readv.dat'
|
|
|
|
before_each(function()
|
|
local f = io.open(file, 'w')
|
|
f:write(fcontents)
|
|
f:close()
|
|
end)
|
|
|
|
after_each(function()
|
|
os.remove(file)
|
|
end)
|
|
|
|
itp('can read zero bytes from a file', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, 0, {}}, {os_readv(fd, {})})
|
|
eq({false, 0, {'', '', ''}}, {os_readv(fd, {0, 0, 0})})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can read from a file multiple times to a differently-sized buffers', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, 2, {'\000\001'}}, {os_readv(fd, {2})})
|
|
eq({false, 5, {'\002\003', '\004\005\006'}}, {os_readv(fd, {2, 3})})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can read the whole file at once and then report eof', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false,
|
|
#fcontents,
|
|
{fcontents:sub(1, #fcontents * 1/4),
|
|
fcontents:sub(#fcontents * 1/4 + 1, #fcontents * 3/4),
|
|
fcontents:sub(#fcontents * 3/4 + 1, #fcontents * 15/16),
|
|
fcontents:sub(#fcontents * 15/16 + 1, #fcontents)}},
|
|
{os_readv(fd, {#fcontents * 1/4,
|
|
#fcontents * 2/4,
|
|
#fcontents * 3/16,
|
|
#fcontents * 1/16})})
|
|
eq({true, 0, {'\0'}}, {os_readv(fd, {1})})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can read the whole file in two calls, one partially', function()
|
|
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
|
ok(fd >= 0)
|
|
eq({false, #fcontents * 3/4, {fcontents:sub(1, #fcontents * 3/4)}},
|
|
{os_readv(fd, {#fcontents * 3/4})})
|
|
eq({true,
|
|
(#fcontents * 1/4),
|
|
{fcontents:sub(#fcontents * 3/4 + 1) .. ('\0'):rep(#fcontents * 2/4)}},
|
|
{os_readv(fd, {#fcontents * 3/4})})
|
|
eq(0, os_close(fd))
|
|
end)
|
|
end)
|
|
|
|
describe('os_write', function()
|
|
-- Function may be absent
|
|
local file = 'test-unit-os-fs_spec-os_write.dat'
|
|
|
|
before_each(function()
|
|
local f = io.open(file, 'w')
|
|
f:write(fcontents)
|
|
f:close()
|
|
end)
|
|
|
|
after_each(function()
|
|
os.remove(file)
|
|
end)
|
|
|
|
itp('can write zero bytes to a file', function()
|
|
local fd = os_open(file, ffi.C.kO_WRONLY, 0)
|
|
ok(fd >= 0)
|
|
eq(0, os_write(fd, ''))
|
|
eq(0, os_write(fd, nil))
|
|
eq(fcontents, io.open(file, 'r'):read('*a'))
|
|
eq(0, os_close(fd))
|
|
end)
|
|
|
|
itp('can write some data to a file', function()
|
|
local fd = os_open(file, ffi.C.kO_WRONLY, 0)
|
|
ok(fd >= 0)
|
|
eq(3, os_write(fd, 'abc'))
|
|
eq(4, os_write(fd, ' def'))
|
|
eq('abc def' .. fcontents:sub(8), io.open(file, 'r'):read('*a'))
|
|
eq(0, os_close(fd))
|
|
end)
|
|
end)
|
|
|
|
describe('os_nodetype', function()
|
|
before_each(function()
|
|
os.remove('non-existing-file')
|
|
end)
|
|
|
|
itp('returns NODE_NORMAL for non-existing file', function()
|
|
eq(NODE_NORMAL, fs.os_nodetype(to_cstr('non-existing-file')))
|
|
end)
|
|
|
|
itp('returns NODE_WRITABLE for /dev/stderr', function()
|
|
eq(NODE_WRITABLE, fs.os_nodetype(to_cstr('/dev/stderr')))
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('folder operations', function()
|
|
local function os_mkdir(path, mode)
|
|
return fs.os_mkdir(to_cstr(path), mode)
|
|
end
|
|
|
|
local function os_rmdir(path)
|
|
return fs.os_rmdir(to_cstr(path))
|
|
end
|
|
|
|
local function os_mkdir_recurse(path, mode)
|
|
local failed_str = ffi.new('char *[1]', {nil})
|
|
local ret = fs.os_mkdir_recurse(path, mode, failed_str)
|
|
local str = failed_str[0]
|
|
if str ~= nil then
|
|
str = ffi.string(str)
|
|
end
|
|
return ret, str
|
|
end
|
|
|
|
describe('os_mkdir', function()
|
|
itp('returns non-zero when given an already existing directory', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
neq(0, (os_mkdir('unit-test-directory', mode)))
|
|
end)
|
|
|
|
itp('creates a directory and returns 0', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
eq(false, (os_isdir('unit-test-directory/new-dir')))
|
|
eq(0, (os_mkdir('unit-test-directory/new-dir', mode)))
|
|
eq(true, (os_isdir('unit-test-directory/new-dir')))
|
|
luv.fs_rmdir('unit-test-directory/new-dir')
|
|
end)
|
|
end)
|
|
|
|
describe('os_mkdir_recurse', function()
|
|
itp('returns zero when given an already existing directory', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse('unit-test-directory', mode)
|
|
eq(0, ret)
|
|
eq(nil, failed_str)
|
|
end)
|
|
|
|
itp('fails to create a directory where there is a file', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse(
|
|
'unit-test-directory/test.file', mode)
|
|
neq(0, ret)
|
|
eq('unit-test-directory/test.file', failed_str)
|
|
end)
|
|
|
|
itp('fails to create a directory where there is a file in path', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse(
|
|
'unit-test-directory/test.file/test', mode)
|
|
neq(0, ret)
|
|
eq('unit-test-directory/test.file', failed_str)
|
|
end)
|
|
|
|
itp('succeeds to create a directory', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse(
|
|
'unit-test-directory/new-dir-recurse', mode)
|
|
eq(0, ret)
|
|
eq(nil, failed_str)
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
|
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
end)
|
|
|
|
itp('succeeds to create a directory ending with ///', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse(
|
|
'unit-test-directory/new-dir-recurse///', mode)
|
|
eq(0, ret)
|
|
eq(nil, failed_str)
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
|
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
end)
|
|
|
|
itp('succeeds to create a directory ending with /', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse(
|
|
'unit-test-directory/new-dir-recurse/', mode)
|
|
eq(0, ret)
|
|
eq(nil, failed_str)
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
|
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
end)
|
|
|
|
itp('succeeds to create a directory tree', function()
|
|
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
|
local ret, failed_str = os_mkdir_recurse(
|
|
'unit-test-directory/new-dir-recurse/1/2/3', mode)
|
|
eq(0, ret)
|
|
eq(nil, failed_str)
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1'))
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2'))
|
|
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2/3'))
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse/1/2/3')
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse/1/2')
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse/1')
|
|
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
|
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
|
end)
|
|
end)
|
|
|
|
describe('os_rmdir', function()
|
|
itp('returns non_zero when given a non-existing directory', function()
|
|
neq(0, (os_rmdir('non-existing-directory')))
|
|
end)
|
|
|
|
itp('removes the given directory and returns 0', function()
|
|
mkdir('unit-test-directory/new-dir')
|
|
eq(0, os_rmdir('unit-test-directory/new-dir'))
|
|
eq(false, (os_isdir('unit-test-directory/new-dir')))
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('FileInfo', function()
|
|
local function file_info_new()
|
|
local info = ffi.new('FileInfo[1]')
|
|
info[0].stat.st_ino = 0
|
|
info[0].stat.st_dev = 0
|
|
return info
|
|
end
|
|
|
|
-- Returns true if the FileInfo object has non-empty fields.
|
|
local function has_fileinfo(info)
|
|
return info[0].stat.st_ino > 0 and info[0].stat.st_dev > 0
|
|
end
|
|
|
|
local function file_id_new()
|
|
local info = ffi.new('FileID[1]')
|
|
info[0].inode = 0
|
|
info[0].device_id = 0
|
|
return info
|
|
end
|
|
|
|
describe('os_fileinfo', function()
|
|
itp('returns false if path=NULL', function()
|
|
local info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo(nil, info)))
|
|
end)
|
|
|
|
itp('returns false if given a non-existing file', function()
|
|
local info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo('/non-existent', info)))
|
|
end)
|
|
|
|
itp('returns true if given an existing file and fills FileInfo', function()
|
|
local info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, info)))
|
|
assert.is_true((has_fileinfo(info)))
|
|
end)
|
|
|
|
itp('returns the FileInfo of the linked file, not the link', function()
|
|
local info = file_info_new()
|
|
local path = 'unit-test-directory/test_link.file'
|
|
assert.is_true((fs.os_fileinfo(path, info)))
|
|
assert.is_true((has_fileinfo(info)))
|
|
local mode = tonumber(info[0].stat.st_mode)
|
|
return eq(ffi.C.kS_IFREG, (bit.band(mode, ffi.C.kS_IFMT)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_link', function()
|
|
itp('returns false for non-existing file', function()
|
|
local info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo_link('/non-existent', info)))
|
|
end)
|
|
|
|
itp('returns true and fills FileInfo for existing file', function()
|
|
local info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo_link(path, info)))
|
|
assert.is_true((has_fileinfo(info)))
|
|
end)
|
|
|
|
itp('returns FileInfo of the link, not its target', function()
|
|
local info = file_info_new()
|
|
local link = 'unit-test-directory/test_link.file'
|
|
assert.is_true((fs.os_fileinfo_link(link, info)))
|
|
assert.is_true((has_fileinfo(info)))
|
|
local mode = tonumber(info[0].stat.st_mode)
|
|
eq(ffi.C.kS_IFLNK, (bit.band(mode, ffi.C.kS_IFMT)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_fd', function()
|
|
itp('returns false if given an invalid file descriptor', function()
|
|
local info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo_fd(-1, info)))
|
|
end)
|
|
|
|
itp('returns true if given a file descriptor and fills FileInfo', function()
|
|
local info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
local fd = ffi.C.open(path, 0)
|
|
assert.is_true((fs.os_fileinfo_fd(fd, info)))
|
|
assert.is_true((has_fileinfo(info)))
|
|
ffi.C.close(fd)
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_id_equal', function()
|
|
itp('returns false if file infos represent different files', function()
|
|
local file_info_1 = file_info_new()
|
|
local file_info_2 = file_info_new()
|
|
local path_1 = 'unit-test-directory/test.file'
|
|
local path_2 = 'unit-test-directory/test_2.file'
|
|
assert.is_true((fs.os_fileinfo(path_1, file_info_1)))
|
|
assert.is_true((fs.os_fileinfo(path_2, file_info_2)))
|
|
assert.is_false((fs.os_fileinfo_id_equal(file_info_1, file_info_2)))
|
|
end)
|
|
|
|
itp('returns true if file infos represent the same file', function()
|
|
local file_info_1 = file_info_new()
|
|
local file_info_2 = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, file_info_1)))
|
|
assert.is_true((fs.os_fileinfo(path, file_info_2)))
|
|
assert.is_true((fs.os_fileinfo_id_equal(file_info_1, file_info_2)))
|
|
end)
|
|
|
|
itp('returns true if file infos represent the same file (symlink)', function()
|
|
local file_info_1 = file_info_new()
|
|
local file_info_2 = file_info_new()
|
|
local path_1 = 'unit-test-directory/test.file'
|
|
local path_2 = 'unit-test-directory/test_link.file'
|
|
assert.is_true((fs.os_fileinfo(path_1, file_info_1)))
|
|
assert.is_true((fs.os_fileinfo(path_2, file_info_2)))
|
|
assert.is_true((fs.os_fileinfo_id_equal(file_info_1, file_info_2)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_id', function()
|
|
itp('extracts ino/dev from FileInfo into file_id', function()
|
|
local info = file_info_new()
|
|
local file_id = file_id_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, info)))
|
|
fs.os_fileinfo_id(info, file_id)
|
|
eq(info[0].stat.st_ino, file_id[0].inode)
|
|
eq(info[0].stat.st_dev, file_id[0].device_id)
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_inode', function()
|
|
itp('returns the inode from FileInfo', function()
|
|
local info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, info)))
|
|
local inode = fs.os_fileinfo_inode(info)
|
|
eq(info[0].stat.st_ino, inode)
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_size', function()
|
|
itp('returns the correct size of a file', function()
|
|
local path = 'unit-test-directory/test.file'
|
|
local file = io.open(path, 'w')
|
|
file:write('some bytes to get filesize != 0')
|
|
file:flush()
|
|
file:close()
|
|
local size = luv.fs_stat(path).size
|
|
local info = file_info_new()
|
|
assert.is_true(fs.os_fileinfo(path, info))
|
|
eq(size, fs.os_fileinfo_size(info))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_hardlinks', function()
|
|
itp('returns the correct number of hardlinks', function()
|
|
local path = 'unit-test-directory/test.file'
|
|
local path_link = 'unit-test-directory/test_hlink.file'
|
|
local info = file_info_new()
|
|
assert.is_true(fs.os_fileinfo(path, info))
|
|
eq(1, fs.os_fileinfo_hardlinks(info))
|
|
luv.fs_link(path, path_link)
|
|
assert.is_true(fs.os_fileinfo(path, info))
|
|
eq(2, fs.os_fileinfo_hardlinks(info))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_blocksize', function()
|
|
itp('returns the correct blocksize of a file', function()
|
|
local path = 'unit-test-directory/test.file'
|
|
local blksize = luv.fs_stat(path).blksize
|
|
local info = file_info_new()
|
|
assert.is_true(fs.os_fileinfo(path, info))
|
|
if blksize then
|
|
eq(blksize, fs.os_fileinfo_blocksize(info))
|
|
else
|
|
-- luafs doesn't support blksize on windows
|
|
-- libuv on windows returns a constant value as blocksize
|
|
-- checking for this constant value should be enough
|
|
eq(2048, fs.os_fileinfo_blocksize(info))
|
|
end
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileid', function()
|
|
itp('returns false if given an non-existing file', function()
|
|
local file_id = file_id_new()
|
|
assert.is_false((fs.os_fileid('/non-existent', file_id)))
|
|
end)
|
|
|
|
itp('returns true if given an existing file and fills file_id', function()
|
|
local file_id = file_id_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileid(path, file_id)))
|
|
assert.is_true(0 < file_id[0].inode)
|
|
assert.is_true(0 < file_id[0].device_id)
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileid_equal', function()
|
|
itp('returns true if two FileIDs are equal', function()
|
|
local file_id = file_id_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileid(path, file_id)))
|
|
assert.is_true((fs.os_fileid_equal(file_id, file_id)))
|
|
end)
|
|
|
|
itp('returns false if two FileIDs are not equal', function()
|
|
local file_id_1 = file_id_new()
|
|
local file_id_2 = file_id_new()
|
|
local path_1 = 'unit-test-directory/test.file'
|
|
local path_2 = 'unit-test-directory/test_2.file'
|
|
assert.is_true((fs.os_fileid(path_1, file_id_1)))
|
|
assert.is_true((fs.os_fileid(path_2, file_id_2)))
|
|
assert.is_false((fs.os_fileid_equal(file_id_1, file_id_2)))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileid_equal_fileinfo', function()
|
|
itp('returns true if file_id and FileInfo represent the same file', function()
|
|
local file_id = file_id_new()
|
|
local info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileid(path, file_id)))
|
|
assert.is_true((fs.os_fileinfo(path, info)))
|
|
assert.is_true((fs.os_fileid_equal_fileinfo(file_id, info)))
|
|
end)
|
|
|
|
itp('returns false if file_id and FileInfo represent different files', function()
|
|
local file_id = file_id_new()
|
|
local info = file_info_new()
|
|
local path_1 = 'unit-test-directory/test.file'
|
|
local path_2 = 'unit-test-directory/test_2.file'
|
|
assert.is_true((fs.os_fileid(path_1, file_id)))
|
|
assert.is_true((fs.os_fileinfo(path_2, info)))
|
|
assert.is_false((fs.os_fileid_equal_fileinfo(file_id, info)))
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|