aboutsummaryrefslogtreecommitdiff
path: root/test/unit/os
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/os')
-rw-r--r--test/unit/os/env_spec.lua67
-rw-r--r--test/unit/os/fileio_spec.lua365
-rw-r--r--test/unit/os/fs_spec.lua282
-rw-r--r--test/unit/os/shell_spec.lua65
4 files changed, 706 insertions, 73 deletions
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index 9e00a3e8f8..64bbaaa8c2 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -10,19 +10,19 @@ local NULL = helpers.NULL
require('lfs')
-local env = cimport('./src/nvim/os/os.h')
+local cimp = cimport('./src/nvim/os/os.h')
describe('env function', function()
local function os_setenv(name, value, override)
- return env.os_setenv((to_cstr(name)), (to_cstr(value)), override)
+ return cimp.os_setenv((to_cstr(name)), (to_cstr(value)), override)
end
local function os_unsetenv(name, _, _)
- return env.os_unsetenv((to_cstr(name)))
+ return cimp.os_unsetenv((to_cstr(name)))
end
local function os_getenv(name)
- local rval = env.os_getenv((to_cstr(name)))
+ local rval = cimp.os_getenv((to_cstr(name)))
if rval ~= NULL then
return ffi.string(rval)
else
@@ -88,14 +88,14 @@ describe('env function', function()
local i = 0
local names = { }
local found_name = false
- local name = env.os_getenvname_at_index(i)
+ local name = cimp.os_getenvname_at_index(i)
while name ~= NULL do
table.insert(names, ffi.string(name))
if (ffi.string(name)) == test_name then
found_name = true
end
i = i + 1
- name = env.os_getenvname_at_index(i)
+ name = cimp.os_getenvname_at_index(i)
end
eq(true, (table.getn(names)) > 0)
eq(true, found_name)
@@ -104,15 +104,15 @@ describe('env function', function()
it('returns NULL if the index is out of bounds', function()
local huge = ffi.new('size_t', 10000)
local maxuint32 = ffi.new('size_t', 4294967295)
- eq(NULL, env.os_getenvname_at_index(huge))
- eq(NULL, env.os_getenvname_at_index(maxuint32))
+ eq(NULL, cimp.os_getenvname_at_index(huge))
+ eq(NULL, cimp.os_getenvname_at_index(maxuint32))
if ffi.abi('64bit') then
-- couldn't use a bigger number because it gets converted to
-- double somewere, should be big enough anyway
-- maxuint64 = ffi.new 'size_t', 18446744073709551615
local maxuint64 = ffi.new('size_t', 18446744073709000000)
- eq(NULL, env.os_getenvname_at_index(maxuint64))
+ eq(NULL, cimp.os_getenvname_at_index(maxuint64))
end
end)
end)
@@ -124,10 +124,10 @@ describe('env function', function()
local stat_str = stat_file:read('*l')
stat_file:close()
local pid = tonumber((stat_str:match('%d+')))
- eq(pid, tonumber(env.os_get_pid()))
+ eq(pid, tonumber(cimp.os_get_pid()))
else
-- /proc is not available on all systems, test if pid is nonzero.
- eq(true, (env.os_get_pid() > 0))
+ eq(true, (cimp.os_get_pid() > 0))
end
end)
end)
@@ -138,7 +138,7 @@ describe('env function', function()
local hostname = handle:read('*l')
handle:close()
local hostname_buf = cstr(255, '')
- env.os_get_hostname(hostname_buf, 255)
+ cimp.os_get_hostname(hostname_buf, 255)
eq(hostname, (ffi.string(hostname_buf)))
end)
end)
@@ -155,39 +155,52 @@ describe('env function', function()
local output_buff1 = cstr(255, '')
local output_buff2 = cstr(255, '')
local output_expected = 'NEOVIM_UNIT_TEST_EXPAND_ENV_ESCV/test'
- env.expand_env_esc(input1, output_buff1, 255, false, true, NULL)
- env.expand_env_esc(input2, output_buff2, 255, false, true, NULL)
+ cimp.expand_env_esc(input1, output_buff1, 255, false, true, NULL)
+ cimp.expand_env_esc(input2, output_buff2, 255, false, true, NULL)
eq(output_expected, ffi.string(output_buff1))
eq(output_expected, ffi.string(output_buff2))
end)
- it('expands ~ once when one is true', function()
+ it('expands ~ once when `one` is true', function()
local input = '~/foo ~ foo'
local homedir = cstr(255, '')
- env.expand_env_esc(to_cstr('~'), homedir, 255, false, true, NULL)
+ cimp.expand_env_esc(to_cstr('~'), homedir, 255, false, true, NULL)
local output_expected = ffi.string(homedir) .. "/foo ~ foo"
local output = cstr(255, '')
- env.expand_env_esc(to_cstr(input), output, 255, false, true, NULL)
+ cimp.expand_env_esc(to_cstr(input), output, 255, false, true, NULL)
eq(ffi.string(output), ffi.string(output_expected))
end)
- it('expands ~ every time when one is false', function()
+ it('expands ~ every time when `one` is false', function()
local input = to_cstr('~/foo ~ foo')
- local homedir = cstr(255, '')
- env.expand_env_esc(to_cstr('~'), homedir, 255, false, true, NULL)
- homedir = ffi.string(homedir)
+ local dst = cstr(255, '')
+ cimp.expand_env_esc(to_cstr('~'), dst, 255, false, true, NULL)
+ local homedir = ffi.string(dst)
local output_expected = homedir .. "/foo " .. homedir .. " foo"
local output = cstr(255, '')
- env.expand_env_esc(input, output, 255, false, false, NULL)
+ cimp.expand_env_esc(input, output, 255, false, false, NULL)
eq(output_expected, ffi.string(output))
end)
- it('respects the dstlen parameter without expansion', function()
+ it('does not crash #3725', function()
+ local name_out = ffi.new('char[100]')
+ cimp.os_get_user_name(name_out, 100)
+ local curuser = ffi.string(name_out)
+
+ local src = to_cstr("~"..curuser.."/Vcs/django-rest-framework/rest_framework/renderers.py")
+ local dst = cstr(256, "~"..curuser)
+ cimp.expand_env_esc(src, dst, 1024, false, false, NULL)
+ local len = string.len(ffi.string(dst))
+ assert.True(len > 56)
+ assert.True(len < 99)
+ end)
+
+ it('respects `dstlen` without expansion', function()
local input = to_cstr('this is a very long thing that will not fit')
-- The buffer is long enough to actually contain the full input in case the
-- test fails, but we don't tell expand_env_esc that
local output = cstr(255, '')
- env.expand_env_esc(input, output, 5, false, true, NULL)
+ cimp.expand_env_esc(input, output, 5, false, true, NULL)
-- Make sure the first few characters are copied properly and that there is a
-- terminating null character
for i=0,3 do
@@ -196,17 +209,17 @@ describe('env function', function()
eq(0, output[4])
end)
- it('respects the dstlen parameter with expansion', function()
+ it('respects `dstlen` with expansion', function()
local varname = to_cstr('NVIM_UNIT_TEST_EXPAND_ENV_ESC_DSTLENN')
local varval = to_cstr('NVIM_UNIT_TEST_EXPAND_ENV_ESC_DSTLENV')
- env.os_setenv(varname, varval, 1)
+ cimp.os_setenv(varname, varval, 1)
-- TODO(bobtwinkles) This test uses unix-specific environment variable accessing,
-- should have some alternative for windows
local input = to_cstr('$NVIM_UNIT_TEST_EXPAND_ENV_ESC_DSTLENN/even more stuff')
-- The buffer is long enough to actually contain the full input in case the
-- test fails, but we don't tell expand_env_esc that
local output = cstr(255, '')
- env.expand_env_esc(input, output, 5, false, true, NULL)
+ cimp.expand_env_esc(input, output, 5, false, true, NULL)
-- Make sure the first few characters are copied properly and that there is a
-- terminating null character
-- expand_env_esc SHOULD NOT expand the variable if there is not enough space to
diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua
new file mode 100644
index 0000000000..5358022422
--- /dev/null
+++ b/test/unit/os/fileio_spec.lua
@@ -0,0 +1,365 @@
+local lfs = require('lfs')
+
+local helpers = require('test.unit.helpers')
+
+local eq = helpers.eq
+local ffi = helpers.ffi
+local cimport = helpers.cimport
+
+local m = cimport('./src/nvim/os/fileio.h')
+
+local fcontents = ''
+for i = 0, 255 do
+ fcontents = fcontents .. (i == 0 and '\0' or ('%c'):format(i))
+end
+fcontents = fcontents:rep(16)
+
+local dir = 'Xtest-unit-file_spec.d'
+local file1 = dir .. '/file1.dat'
+local file2 = dir .. '/file2.dat'
+local linkf = dir .. '/file.lnk'
+local linkb = dir .. '/broken.lnk'
+local filec = dir .. '/created-file.dat'
+
+before_each(function()
+ lfs.mkdir(dir);
+
+ local f1 = io.open(file1, 'w')
+ f1:write(fcontents)
+ f1:close()
+
+ local f2 = io.open(file2, 'w')
+ f2:write(fcontents)
+ f2:close()
+
+ lfs.link('file1.dat', linkf, true)
+ lfs.link('broken.dat', linkb, true)
+end)
+
+after_each(function()
+ os.remove(file1)
+ os.remove(file2)
+ os.remove(linkf)
+ os.remove(linkb)
+ os.remove(filec)
+ lfs.rmdir(dir)
+end)
+
+local function file_open(fname, flags, mode)
+ local ret2 = ffi.new('FileDescriptor')
+ local ret1 = m.file_open(ret2, fname, flags, mode)
+ return ret1, ret2
+end
+
+local function file_open_new(fname, flags, mode)
+ local ret1 = ffi.new('int[?]', 1, {0})
+ local ret2 = ffi.gc(m.file_open_new(ret1, fname, flags, mode), nil)
+ return ret1[0], ret2
+end
+
+local function file_write(fp, buf)
+ return m.file_write(fp, buf, #buf)
+end
+
+local function file_read(fp, size)
+ local buf = nil
+ if size == nil then
+ size = 0
+ else
+ -- For some reason if length of NUL-bytes-string is the same as `char[?]`
+ -- size luajit garbage collector crashes. But it does not do so in
+ -- os_read[v] tests in os/fs_spec.lua.
+ buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
+ end
+ local ret1 = m.file_read(fp, buf, size)
+ local ret2 = ''
+ if buf ~= nil then
+ ret2 = ffi.string(buf, size)
+ end
+ return ret1, ret2
+end
+
+local function file_fsync(fp)
+ return m.file_fsync(fp)
+end
+
+local function file_skip(fp, size)
+ return m.file_skip(fp, size)
+end
+
+describe('file_open', function()
+ it('can create a rwx------ file with kFileCreate', function()
+ local err, fp = file_open(filec, m.kFileCreate, 448)
+ eq(0, err)
+ local attrs = lfs.attributes(filec)
+ eq('rwx------', attrs.permissions)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can create a rw------- file with kFileCreate', function()
+ local err, fp = file_open(filec, m.kFileCreate, 384)
+ eq(0, err)
+ local attrs = lfs.attributes(filec)
+ eq('rw-------', attrs.permissions)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can create a rwx------ file with kFileCreateOnly', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 448)
+ eq(0, err)
+ local attrs = lfs.attributes(filec)
+ eq('rwx------', attrs.permissions)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can create a rw------- file with kFileCreateOnly', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, err)
+ local attrs = lfs.attributes(filec)
+ eq('rw-------', attrs.permissions)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('fails to open an existing file with kFileCreateOnly', function()
+ local err, _ = file_open(file1, m.kFileCreateOnly, 384)
+ eq(m.UV_EEXIST, err)
+ end)
+
+ it('fails to open an symlink with kFileNoSymlink', function()
+ local err, _ = file_open(linkf, m.kFileNoSymlink, 384)
+ -- err is UV_EMLINK in FreeBSD, but if I use `ok(err == m.UV_ELOOP or err ==
+ -- m.UV_EMLINK)`, then I loose the ability to see actual `err` value.
+ if err ~= m.UV_ELOOP then eq(m.UV_EMLINK, err) end
+ end)
+
+ it('can open an existing file write-only with kFileCreate', function()
+ local err, fp = file_open(file1, m.kFileCreate, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can open an existing file read-only with zero', function()
+ local err, fp = file_open(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can open an existing file read-only with kFileReadOnly', function()
+ local err, fp = file_open(file1, m.kFileReadOnly, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can open an existing file read-only with kFileNoSymlink', function()
+ local err, fp = file_open(file1, m.kFileNoSymlink, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can truncate an existing file with kFileTruncate', function()
+ local err, fp = file_open(file1, m.kFileTruncate, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ eq(0, m.file_close(fp))
+ local attrs = lfs.attributes(file1)
+ eq(0, attrs.size)
+ end)
+
+ it('can open an existing file write-only with kFileWriteOnly', function()
+ local err, fp = file_open(file1, m.kFileWriteOnly, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ eq(0, m.file_close(fp))
+ local attrs = lfs.attributes(file1)
+ eq(4096, attrs.size)
+ end)
+
+ it('fails to create a file with just kFileWriteOnly', function()
+ local err, _ = file_open(filec, m.kFileWriteOnly, 384)
+ eq(m.UV_ENOENT, err)
+ local attrs = lfs.attributes(filec)
+ eq(nil, attrs)
+ end)
+
+ it('can truncate an existing file with kFileTruncate when opening a symlink',
+ function()
+ local err, fp = file_open(linkf, m.kFileTruncate, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ eq(0, m.file_close(fp))
+ local attrs = lfs.attributes(file1)
+ eq(0, attrs.size)
+ end)
+
+ it('fails to open a directory write-only', function()
+ local err, _ = file_open(dir, m.kFileWriteOnly, 384)
+ eq(m.UV_EISDIR, err)
+ end)
+
+ it('fails to open a broken symbolic link write-only', function()
+ local err, _ = file_open(linkb, m.kFileWriteOnly, 384)
+ eq(m.UV_ENOENT, err)
+ end)
+
+ it('fails to open a broken symbolic link read-only', function()
+ local err, _ = file_open(linkb, m.kFileReadOnly, 384)
+ eq(m.UV_ENOENT, err)
+ end)
+end)
+
+describe('file_open_new', function()
+ it('can open a file read-only', function()
+ local err, fp = file_open_new(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq(0, m.file_free(fp))
+ end)
+
+ it('fails to open an existing file with kFileCreateOnly', function()
+ local err, fp = file_open_new(file1, m.kFileCreateOnly, 384)
+ eq(m.UV_EEXIST, err)
+ eq(nil, fp)
+ end)
+end)
+
+-- file_close is called above, so it is not tested directly
+
+describe('file_fsync', function()
+ it('can flush writes to disk', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, file_fsync(fp))
+ eq(0, err)
+ eq(0, lfs.attributes(filec).size)
+ local wsize = file_write(fp, 'test')
+ eq(4, wsize)
+ eq(0, lfs.attributes(filec).size)
+ eq(0, file_fsync(fp))
+ eq(wsize, lfs.attributes(filec).size)
+ eq(0, m.file_close(fp))
+ end)
+end)
+
+describe('file_read', function()
+ it('can read small chunks of input until eof', function()
+ local err, fp = file_open(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ local shift = 0
+ while shift < #fcontents do
+ local size = 3
+ local exp_err = size
+ local exp_s = fcontents:sub(shift + 1, shift + size)
+ if shift + size >= #fcontents then
+ exp_err = #fcontents - shift
+ exp_s = (fcontents:sub(shift + 1, shift + size)
+ .. (('\0'):rep(size - exp_err)))
+ end
+ eq({exp_err, exp_s}, {file_read(fp, size)})
+ shift = shift + size
+ end
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can read the whole file at once', function()
+ local err, fp = file_open(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
+ eq({0, ('\0'):rep(#fcontents)}, {file_read(fp, #fcontents)})
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can read more then 1024 bytes after reading a small chunk', function()
+ local err, fp = file_open(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq({5, fcontents:sub(1, 5)}, {file_read(fp, 5)})
+ eq({#fcontents - 5, fcontents:sub(6) .. (('\0'):rep(5))},
+ {file_read(fp, #fcontents)})
+ eq(0, m.file_close(fp))
+ end)
+
+ it('can read file by 768-byte-chunks', function()
+ local err, fp = file_open(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ local shift = 0
+ while shift < #fcontents do
+ local size = 768
+ local exp_err = size
+ local exp_s = fcontents:sub(shift + 1, shift + size)
+ if shift + size >= #fcontents then
+ exp_err = #fcontents - shift
+ exp_s = (fcontents:sub(shift + 1, shift + size)
+ .. (('\0'):rep(size - exp_err)))
+ end
+ eq({exp_err, exp_s}, {file_read(fp, size)})
+ shift = shift + size
+ end
+ eq(0, m.file_close(fp))
+ end)
+end)
+
+describe('file_write', function()
+ it('can write the whole file at once', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ local wr = file_write(fp, fcontents)
+ eq(#fcontents, wr)
+ eq(0, m.file_close(fp))
+ eq(wr, lfs.attributes(filec).size)
+ eq(fcontents, io.open(filec):read('*a'))
+ end)
+
+ it('can write the whole file by small chunks', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ local shift = 0
+ while shift < #fcontents do
+ local size = 3
+ local s = fcontents:sub(shift + 1, shift + size)
+ local wr = file_write(fp, s)
+ eq(wr, #s)
+ shift = shift + size
+ end
+ eq(0, m.file_close(fp))
+ eq(#fcontents, lfs.attributes(filec).size)
+ eq(fcontents, io.open(filec):read('*a'))
+ end)
+
+ it('can write the whole file by 768-byte-chunks', function()
+ local err, fp = file_open(filec, m.kFileCreateOnly, 384)
+ eq(0, err)
+ eq(true, fp.wr)
+ local shift = 0
+ while shift < #fcontents do
+ local size = 768
+ local s = fcontents:sub(shift + 1, shift + size)
+ local wr = file_write(fp, s)
+ eq(wr, #s)
+ shift = shift + size
+ end
+ eq(0, m.file_close(fp))
+ eq(#fcontents, lfs.attributes(filec).size)
+ eq(fcontents, io.open(filec):read('*a'))
+ end)
+end)
+
+describe('file_skip', function()
+ it('can skip 3 bytes', function()
+ local err, fp = file_open(file1, 0, 384)
+ eq(0, err)
+ eq(false, fp.wr)
+ eq(3, file_skip(fp, 3))
+ local rd, s = file_read(fp, 3)
+ eq(3, rd)
+ eq(fcontents:sub(4, 6), s)
+ eq(0, m.file_close(fp))
+ end)
+end)
diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua
index 857a5001f1..5d889d6e33 100644
--- a/test/unit/os/fs_spec.lua
+++ b/test/unit/os/fs_spec.lua
@@ -6,6 +6,7 @@ local helpers = require('test.unit.helpers')
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
@@ -27,6 +28,12 @@ cppimport('sys/stat.h')
cppimport('fcntl.h')
cppimport('uv-errno.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 buffer = ""
local directory = nil
local absolute_executable = nil
@@ -68,6 +75,8 @@ describe('fs function', function()
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)
-- Since the tests are executed, they are called by an executable. We use
-- that executable for several asserts.
absolute_executable = arg[0]
@@ -81,6 +90,7 @@ describe('fs function', function()
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)
@@ -150,11 +160,11 @@ describe('fs function', function()
local function os_can_exe(name)
local buf = ffi.new('char *[1]')
buf[0] = NULL
- local ok = fs.os_can_exe(to_cstr(name), buf, true)
+ 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 ok then
+ if ce_ret then
neq(NULL, buf[0])
return internalize(buf[0])
else
@@ -196,8 +206,8 @@ describe('fs function', function()
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
+ -- 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)
@@ -356,8 +366,8 @@ describe('fs function', function()
end)
describe('file operations', function()
- local function os_file_exists(filename)
- return fs.os_file_exists((to_cstr(filename)))
+ 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)))
@@ -368,14 +378,67 @@ describe('fs function', function()
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)
+ 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)
+ 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)
+ end
- describe('os_file_exists', function()
+ describe('os_path_exists', function()
it('returns false when given a non-existing file', function()
- eq(false, (os_file_exists('non-existing-file')))
+ eq(false, (os_path_exists('non-existing-file')))
end)
it('returns true when given an existing file', function()
- eq(true, (os_file_exists('unit-test-directory/test.file')))
+ eq(true, (os_path_exists('unit-test-directory/test.file')))
+ end)
+
+ it('returns false when given a broken symlink', function()
+ eq(false, (os_path_exists('unit-test-directory/test_broken_link.file')))
+ end)
+
+ it('returns true when given a directory', function()
+ eq(true, (os_path_exists('unit-test-directory')))
end)
end)
@@ -385,8 +448,8 @@ describe('fs function', function()
it('can rename file if destination file does not exist', function()
eq(OK, (os_rename(test, not_exist)))
- eq(false, (os_file_exists(test)))
- eq(true, (os_file_exists(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)
@@ -402,8 +465,8 @@ describe('fs function', function()
file:close()
eq(OK, (os_rename(other, test)))
- eq(false, (os_file_exists(other)))
- eq(true, (os_file_exists(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()
@@ -432,30 +495,34 @@ describe('fs function', function()
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('unit-test-directory/test_existing.file', 'w').close()
+ (io.open(existing_file, 'w')):close()
end)
after_each(function()
- os.remove('unit-test-directory/test_existing.file')
- os.remove('test_new_file')
+ os.remove(existing_file)
+ os.remove(new_file)
end)
- local new_file = 'test_new_file'
- local existing_file = 'unit-test-directory/test_existing.file'
-
it('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)
- it('returns non-negative for O_CREAT on a non-existing file', function()
+ it('returns non-negative for O_CREAT on a non-existing file which then can be closed', function()
assert_file_does_not_exist(new_file)
- assert.is_true(0 <= (os_open(new_file, ffi.C.kO_CREAT, 0)))
+ local fd = os_open(new_file, ffi.C.kO_CREAT, 0)
+ assert.is_true(0 <= fd)
+ eq(0, os_close(fd))
end)
- it('returns non-negative for O_CREAT on a existing file', function()
+ it('returns non-negative for O_CREAT on a existing file which then can be closed', function()
assert_file_exists(existing_file)
- assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_CREAT, 0)))
+ local fd = os_open(existing_file, ffi.C.kO_CREAT, 0)
+ assert.is_true(0 <= fd)
+ eq(0, os_close(fd))
end)
it('returns UV_EEXIST for O_CREAT|O_EXCL on a existing file', function()
@@ -463,24 +530,181 @@ describe('fs function', function()
eq(ffi.C.kUV_EEXIST, (os_open(existing_file, (bit.bor(ffi.C.kO_CREAT, ffi.C.kO_EXCL)), 0)))
end)
- it('sets `rwx` permissions for O_CREAT 700', function()
+ it('sets `rwx` permissions for O_CREAT 700 which then can be closed', function()
assert_file_does_not_exist(new_file)
--create the file
- os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8))
+ 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)
- it('sets `rw` permissions for O_CREAT 600', function()
+ it('sets `rw` permissions for O_CREAT 600 which then can be closed', function()
assert_file_does_not_exist(new_file)
--create the file
- os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8))
+ 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)
+
+ it('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()
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
+
+ it('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)
- it('returns a non-negative file descriptor for an existing file', function()
- assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_RDWR, 0)))
+ it('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)
diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua
index 93103e4e8c..3603403daf 100644
--- a/test/unit/os/shell_spec.lua
+++ b/test/unit/os/shell_spec.lua
@@ -1,15 +1,3 @@
--- not all operating systems support the system()-tests, as of yet.
-local allowed_os = {
- Linux = true,
- OSX = true,
- BSD = true,
- POSIX = true
-}
-
-if allowed_os[jit.os] ~= true then
- return
-end
-
local helpers = require('test.unit.helpers')
local cimported = helpers.cimport(
'./src/nvim/os/shell.h',
@@ -24,14 +12,12 @@ local to_cstr = helpers.to_cstr
local NULL = ffi.cast('void *', 0)
describe('shell functions', function()
- setup(function()
+ before_each(function()
-- os_system() can't work when the p_sh and p_shcf variables are unset
cimported.p_sh = to_cstr('/bin/bash')
cimported.p_shcf = to_cstr('-c')
- end)
-
- teardown(function()
- cimported.event_teardown()
+ cimported.p_sxq = to_cstr('')
+ cimported.p_sxe = to_cstr('')
end)
local function shell_build_argv(cmd, extra_args)
@@ -130,5 +116,50 @@ describe('shell functions', function()
'-c', 'abc def'},
shell_build_argv('abc def', 'ghi jkl'))
end)
+
+ it('applies shellxescape (p_sxe) and shellxquote (p_sxq)', function()
+ cimported.p_sxq = to_cstr('(')
+ cimported.p_sxe = to_cstr('"&|<>()@^')
+
+ local argv = ffi.cast('char**',
+ cimported.shell_build_argv(to_cstr('echo &|<>()@^'), nil))
+ eq(ffi.string(argv[0]), '/bin/bash')
+ eq(ffi.string(argv[1]), '-c')
+ eq(ffi.string(argv[2]), '(echo ^&^|^<^>^(^)^@^^)')
+ eq(nil, argv[3])
+ end)
+
+ it('applies shellxquote="(', function()
+ cimported.p_sxq = to_cstr('"(')
+ cimported.p_sxe = to_cstr('"&|<>()@^')
+
+ local argv = ffi.cast('char**', cimported.shell_build_argv(
+ to_cstr('echo -n some text'), nil))
+ eq(ffi.string(argv[0]), '/bin/bash')
+ eq(ffi.string(argv[1]), '-c')
+ eq(ffi.string(argv[2]), '"(echo -n some text)"')
+ eq(nil, argv[3])
+ end)
+
+ it('applies shellxquote="', function()
+ cimported.p_sxq = to_cstr('"')
+ cimported.p_sxe = to_cstr('')
+
+ local argv = ffi.cast('char**', cimported.shell_build_argv(
+ to_cstr('echo -n some text'), nil))
+ eq(ffi.string(argv[0]), '/bin/bash')
+ eq(ffi.string(argv[1]), '-c')
+ eq(ffi.string(argv[2]), '"echo -n some text"')
+ eq(nil, argv[3])
+ end)
+
+ it('with empty shellxquote/shellxescape', function()
+ local argv = ffi.cast('char**', cimported.shell_build_argv(
+ to_cstr('echo -n some text'), nil))
+ eq(ffi.string(argv[0]), '/bin/bash')
+ eq(ffi.string(argv[1]), '-c')
+ eq(ffi.string(argv[2]), 'echo -n some text')
+ eq(nil, argv[3])
+ end)
end)
end)