fs_spec.lua 38 KB

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