mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 11:15:14 -07:00
ba7704ab4e
libuv users are only supposed to directly include uv.h. In v1.21.0, all the uv-*.h headers were renamed to uv/*.h, which caused the unit tests to fail with [123/125] Generating post/uv-errno.h FAILED: test/includes/post/uv-errno.h cd «SRCDIR»/src/neovim/build/test/includes && /usr/bin/clang -std=c99 -E -P «SRCDIR»/src/neovim/test/includes/pre/uv-errno.h -I/usr/include -I/usr/include -o «SRCDIR»/neovim/build/test/includes/post/uv-errno.h «SRCDIR»/src/neovim/test/includes/pre/uv-errno.h:1:10: error: 'uv-errno.h' file not found with <angled> include; use "quotes" instead #include <uv-errno.h> ^~~~~~~~~~~~ "uv-errno.h" The intention of the file is to extend libuv's error constants with more values used by the unit tests. This can just as easily be achieved without poking into pseudo-private header files.
1104 lines
38 KiB
Lua
1104 lines
38 KiB
Lua
local lfs = require('lfs')
|
|
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 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')
|
|
cppimport('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, lfs.attributes(filepath))
|
|
end
|
|
|
|
local function assert_file_does_not_exist(filepath)
|
|
eq(nil, lfs.attributes(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()
|
|
lfs.mkdir('unit-test-directory');
|
|
|
|
io.open('unit-test-directory/test.file', 'w').close()
|
|
|
|
io.open('unit-test-directory/test_2.file', 'w').close()
|
|
lfs.link('test.file', 'unit-test-directory/test_link.file', true)
|
|
|
|
lfs.link('non_existing_file.file', 'unit-test-directory/test_broken_link.file', true)
|
|
-- 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')
|
|
lfs.rmdir('unit-test-directory')
|
|
end)
|
|
|
|
describe('os_dirname', function()
|
|
itp('returns OK and writes current directory to the buffer', function()
|
|
local length = string.len(lfs.currentdir()) + 1
|
|
local buf = cstr(length, '')
|
|
eq(OK, fs.os_dirname(buf, length))
|
|
eq(lfs.currentdir(), ffi.string(buf))
|
|
end)
|
|
|
|
itp('returns FAIL if the buffer is too small', function()
|
|
local length = string.len(lfs.currentdir()) + 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 = lfs.currentdir()
|
|
|
|
lfs.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 = lfs.currentdir() .. '/' .. 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.
|
|
lfs.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 = lfs.attributes(filename, 'uid')
|
|
local gid = lfs.attributes(filename, 'gid')
|
|
eq(0, os_fchown(filename, -1, -1))
|
|
eq(uid, lfs.attributes(filename, 'uid'))
|
|
return eq(gid, lfs.attributes(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 = lfs.attributes(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, (lfs.attributes(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.kUV_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('rwx------', lfs.attributes(new_file)['permissions'])
|
|
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('rw-------', lfs.attributes(new_file)['permissions'])
|
|
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')))
|
|
lfs.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'))
|
|
lfs.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'))
|
|
lfs.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'))
|
|
lfs.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'))
|
|
lfs.rmdir('unit-test-directory/new-dir-recurse/1/2/3')
|
|
lfs.rmdir('unit-test-directory/new-dir-recurse/1/2')
|
|
lfs.rmdir('unit-test-directory/new-dir-recurse/1')
|
|
lfs.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()
|
|
lfs.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 file_info = ffi.new('FileInfo[1]')
|
|
file_info[0].stat.st_ino = 0
|
|
file_info[0].stat.st_dev = 0
|
|
return file_info
|
|
end
|
|
|
|
local function is_file_info_filled(file_info)
|
|
return file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0
|
|
end
|
|
|
|
local function file_id_new()
|
|
local file_info = ffi.new('FileID[1]')
|
|
file_info[0].inode = 0
|
|
file_info[0].device_id = 0
|
|
return file_info
|
|
end
|
|
|
|
describe('os_fileinfo', function()
|
|
itp('returns false if path=NULL', function()
|
|
local file_info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo(nil, file_info)))
|
|
end)
|
|
|
|
itp('returns false if given a non-existing file', function()
|
|
local file_info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo('/non-existent', file_info)))
|
|
end)
|
|
|
|
itp('returns true if given an existing file and fills file_info', function()
|
|
local file_info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, file_info)))
|
|
assert.is_true((is_file_info_filled(file_info)))
|
|
end)
|
|
|
|
itp('returns the file info of the linked file, not the link', function()
|
|
local file_info = file_info_new()
|
|
local path = 'unit-test-directory/test_link.file'
|
|
assert.is_true((fs.os_fileinfo(path, file_info)))
|
|
assert.is_true((is_file_info_filled(file_info)))
|
|
local mode = tonumber(file_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 if given a non-existing file', function()
|
|
local file_info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo_link('/non-existent', file_info)))
|
|
end)
|
|
|
|
itp('returns true if given an existing file and fills file_info', function()
|
|
local file_info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo_link(path, file_info)))
|
|
assert.is_true((is_file_info_filled(file_info)))
|
|
end)
|
|
|
|
itp('returns the file info of the link, not the linked file', function()
|
|
local file_info = file_info_new()
|
|
local path = 'unit-test-directory/test_link.file'
|
|
assert.is_true((fs.os_fileinfo_link(path, file_info)))
|
|
assert.is_true((is_file_info_filled(file_info)))
|
|
local mode = tonumber(file_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 file_info = file_info_new()
|
|
assert.is_false((fs.os_fileinfo_fd(-1, file_info)))
|
|
end)
|
|
|
|
itp('returns true if given a file descriptor and fills file_info', function()
|
|
local file_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, file_info)))
|
|
assert.is_true((is_file_info_filled(file_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 file_info into file_id', function()
|
|
local file_info = file_info_new()
|
|
local file_id = file_id_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, file_info)))
|
|
fs.os_fileinfo_id(file_info, file_id)
|
|
eq(file_info[0].stat.st_ino, file_id[0].inode)
|
|
eq(file_info[0].stat.st_dev, file_id[0].device_id)
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_inode', function()
|
|
itp('returns the inode from file_info', function()
|
|
local file_info = file_info_new()
|
|
local path = 'unit-test-directory/test.file'
|
|
assert.is_true((fs.os_fileinfo(path, file_info)))
|
|
local inode = fs.os_fileinfo_inode(file_info)
|
|
eq(file_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 = lfs.attributes(path, 'size')
|
|
local file_info = file_info_new()
|
|
assert.is_true(fs.os_fileinfo(path, file_info))
|
|
eq(size, fs.os_fileinfo_size(file_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 file_info = file_info_new()
|
|
assert.is_true(fs.os_fileinfo(path, file_info))
|
|
eq(1, fs.os_fileinfo_hardlinks(file_info))
|
|
lfs.link(path, path_link)
|
|
assert.is_true(fs.os_fileinfo(path, file_info))
|
|
eq(2, fs.os_fileinfo_hardlinks(file_info))
|
|
end)
|
|
end)
|
|
|
|
describe('os_fileinfo_blocksize', function()
|
|
itp('returns the correct blocksize of a file', function()
|
|
local path = 'unit-test-directory/test.file'
|
|
-- there is a bug in luafilesystem where
|
|
-- `lfs.attributes path, 'blksize'` returns the worng value:
|
|
-- https://github.com/keplerproject/luafilesystem/pull/44
|
|
-- using this workaround for now:
|
|
local blksize = lfs.attributes(path).blksize
|
|
local file_info = file_info_new()
|
|
assert.is_true(fs.os_fileinfo(path, file_info))
|
|
if blksize then
|
|
eq(blksize, fs.os_fileinfo_blocksize(file_info))
|
|
else
|
|
-- luafs dosn'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(file_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 file_info represent the same file', function()
|
|
local file_id = file_id_new()
|
|
local file_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, file_info)))
|
|
assert.is_true((fs.os_fileid_equal_fileinfo(file_id, file_info)))
|
|
end)
|
|
|
|
itp('returns false if file_id and file_info represent different files', function()
|
|
local file_id = file_id_new()
|
|
local file_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, file_info)))
|
|
assert.is_false((fs.os_fileid_equal_fileinfo(file_id, file_info)))
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|