diff options
Diffstat (limited to 'test/unit/os')
-rw-r--r-- | test/unit/os/env_spec.lua | 67 | ||||
-rw-r--r-- | test/unit/os/fileio_spec.lua | 365 | ||||
-rw-r--r-- | test/unit/os/fs_spec.lua | 282 | ||||
-rw-r--r-- | test/unit/os/shell_spec.lua | 65 |
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) |