fileio_spec.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. local lfs = require('lfs')
  2. local helpers = require('test.unit.helpers')(after_each)
  3. local itp = helpers.gen_itp(it)
  4. local eq = helpers.eq
  5. local ffi = helpers.ffi
  6. local cimport = helpers.cimport
  7. local cppimport = helpers.cppimport
  8. local m = cimport('./src/nvim/os/os.h', './src/nvim/os/fileio.h')
  9. cppimport('fcntl.h')
  10. local fcontents = ''
  11. for i = 0, 255 do
  12. fcontents = fcontents .. (i == 0 and '\0' or ('%c'):format(i))
  13. end
  14. fcontents = fcontents:rep(16)
  15. local dir = 'Xtest-unit-file_spec.d'
  16. local file1 = dir .. '/file1.dat'
  17. local file2 = dir .. '/file2.dat'
  18. local linkf = dir .. '/file.lnk'
  19. local linkb = dir .. '/broken.lnk'
  20. local filec = dir .. '/created-file.dat'
  21. before_each(function()
  22. lfs.mkdir(dir);
  23. local f1 = io.open(file1, 'w')
  24. f1:write(fcontents)
  25. f1:close()
  26. local f2 = io.open(file2, 'w')
  27. f2:write(fcontents)
  28. f2:close()
  29. lfs.link('file1.dat', linkf, true)
  30. lfs.link('broken.dat', linkb, true)
  31. end)
  32. after_each(function()
  33. os.remove(file1)
  34. os.remove(file2)
  35. os.remove(linkf)
  36. os.remove(linkb)
  37. os.remove(filec)
  38. lfs.rmdir(dir)
  39. end)
  40. local function file_open(fname, flags, mode)
  41. local ret2 = ffi.new('FileDescriptor')
  42. local ret1 = m.file_open(ret2, fname, flags, mode)
  43. return ret1, ret2
  44. end
  45. local function file_open_new(fname, flags, mode)
  46. local ret1 = ffi.new('int[?]', 1, {0})
  47. local ret2 = ffi.gc(m.file_open_new(ret1, fname, flags, mode), nil)
  48. return ret1[0], ret2
  49. end
  50. local function file_open_fd(fd, flags)
  51. local ret2 = ffi.new('FileDescriptor')
  52. local ret1 = m.file_open_fd(ret2, fd, flags)
  53. return ret1, ret2
  54. end
  55. local function file_open_fd_new(fd, flags)
  56. local ret1 = ffi.new('int[?]', 1, {0})
  57. local ret2 = ffi.gc(m.file_open_fd_new(ret1, fd, flags), nil)
  58. return ret1[0], ret2
  59. end
  60. local function file_write(fp, buf)
  61. return m.file_write(fp, buf, #buf)
  62. end
  63. local function msgpack_file_write(fp, buf)
  64. return m.msgpack_file_write(fp, buf, #buf)
  65. end
  66. local function file_read(fp, size)
  67. local buf = nil
  68. if size == nil then
  69. size = 0
  70. else
  71. -- For some reason if length of NUL-bytes-string is the same as `char[?]`
  72. -- size luajit garbage collector crashes. But it does not do so in
  73. -- os_read[v] tests in os/fs_spec.lua.
  74. buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
  75. end
  76. local ret1 = m.file_read(fp, buf, size)
  77. local ret2 = ''
  78. if buf ~= nil then
  79. ret2 = ffi.string(buf, size)
  80. end
  81. return ret1, ret2
  82. end
  83. local function file_flush(fp)
  84. return m.file_flush(fp)
  85. end
  86. local function file_fsync(fp)
  87. return m.file_fsync(fp)
  88. end
  89. local function file_skip(fp, size)
  90. return m.file_skip(fp, size)
  91. end
  92. describe('file_open_fd', function()
  93. itp('can use file descriptor returned by os_open for reading', function()
  94. local fd = m.os_open(file1, m.kO_RDONLY, 0)
  95. local err, fp = file_open_fd(fd, m.kFileReadOnly)
  96. eq(0, err)
  97. eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
  98. eq(0, m.file_close(fp, false))
  99. end)
  100. itp('can use file descriptor returned by os_open for writing', function()
  101. eq(nil, lfs.attributes(filec))
  102. local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
  103. local err, fp = file_open_fd(fd, m.kFileWriteOnly)
  104. eq(0, err)
  105. eq(4, file_write(fp, 'test'))
  106. eq(0, m.file_close(fp, false))
  107. eq(4, lfs.attributes(filec).size)
  108. eq('test', io.open(filec):read('*a'))
  109. end)
  110. end)
  111. describe('file_open_fd_new', function()
  112. itp('can use file descriptor returned by os_open for reading', function()
  113. local fd = m.os_open(file1, m.kO_RDONLY, 0)
  114. local err, fp = file_open_fd_new(fd, m.kFileReadOnly)
  115. eq(0, err)
  116. eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
  117. eq(0, m.file_free(fp, false))
  118. end)
  119. itp('can use file descriptor returned by os_open for writing', function()
  120. eq(nil, lfs.attributes(filec))
  121. local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
  122. local err, fp = file_open_fd_new(fd, m.kFileWriteOnly)
  123. eq(0, err)
  124. eq(4, file_write(fp, 'test'))
  125. eq(0, m.file_free(fp, false))
  126. eq(4, lfs.attributes(filec).size)
  127. eq('test', io.open(filec):read('*a'))
  128. end)
  129. end)
  130. describe('file_open', function()
  131. itp('can create a rwx------ file with kFileCreate', function()
  132. local err, fp = file_open(filec, m.kFileCreate, 448)
  133. eq(0, err)
  134. local attrs = lfs.attributes(filec)
  135. eq('rwx------', attrs.permissions)
  136. eq(0, m.file_close(fp, false))
  137. end)
  138. itp('can create a rw------- file with kFileCreate', function()
  139. local err, fp = file_open(filec, m.kFileCreate, 384)
  140. eq(0, err)
  141. local attrs = lfs.attributes(filec)
  142. eq('rw-------', attrs.permissions)
  143. eq(0, m.file_close(fp, false))
  144. end)
  145. itp('can create a rwx------ file with kFileCreateOnly', function()
  146. local err, fp = file_open(filec, m.kFileCreateOnly, 448)
  147. eq(0, err)
  148. local attrs = lfs.attributes(filec)
  149. eq('rwx------', attrs.permissions)
  150. eq(0, m.file_close(fp, false))
  151. end)
  152. itp('can create a rw------- file with kFileCreateOnly', function()
  153. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  154. eq(0, err)
  155. local attrs = lfs.attributes(filec)
  156. eq('rw-------', attrs.permissions)
  157. eq(0, m.file_close(fp, false))
  158. end)
  159. itp('fails to open an existing file with kFileCreateOnly', function()
  160. local err, _ = file_open(file1, m.kFileCreateOnly, 384)
  161. eq(m.UV_EEXIST, err)
  162. end)
  163. itp('fails to open an symlink with kFileNoSymlink', function()
  164. local err, _ = file_open(linkf, m.kFileNoSymlink, 384)
  165. -- err is UV_EMLINK in FreeBSD, but if I use `ok(err == m.UV_ELOOP or err ==
  166. -- m.UV_EMLINK)`, then I loose the ability to see actual `err` value.
  167. if err ~= m.UV_ELOOP then eq(m.UV_EMLINK, err) end
  168. end)
  169. itp('can open an existing file write-only with kFileCreate', function()
  170. local err, fp = file_open(file1, m.kFileCreate, 384)
  171. eq(0, err)
  172. eq(true, fp.wr)
  173. eq(0, m.file_close(fp, false))
  174. end)
  175. itp('can open an existing file read-only with zero', function()
  176. local err, fp = file_open(file1, 0, 384)
  177. eq(0, err)
  178. eq(false, fp.wr)
  179. eq(0, m.file_close(fp, false))
  180. end)
  181. itp('can open an existing file read-only with kFileReadOnly', function()
  182. local err, fp = file_open(file1, m.kFileReadOnly, 384)
  183. eq(0, err)
  184. eq(false, fp.wr)
  185. eq(0, m.file_close(fp, false))
  186. end)
  187. itp('can open an existing file read-only with kFileNoSymlink', function()
  188. local err, fp = file_open(file1, m.kFileNoSymlink, 384)
  189. eq(0, err)
  190. eq(false, fp.wr)
  191. eq(0, m.file_close(fp, false))
  192. end)
  193. itp('can truncate an existing file with kFileTruncate', function()
  194. local err, fp = file_open(file1, m.kFileTruncate, 384)
  195. eq(0, err)
  196. eq(true, fp.wr)
  197. eq(0, m.file_close(fp, false))
  198. local attrs = lfs.attributes(file1)
  199. eq(0, attrs.size)
  200. end)
  201. itp('can open an existing file write-only with kFileWriteOnly', function()
  202. local err, fp = file_open(file1, m.kFileWriteOnly, 384)
  203. eq(0, err)
  204. eq(true, fp.wr)
  205. eq(0, m.file_close(fp, false))
  206. local attrs = lfs.attributes(file1)
  207. eq(4096, attrs.size)
  208. end)
  209. itp('fails to create a file with just kFileWriteOnly', function()
  210. local err, _ = file_open(filec, m.kFileWriteOnly, 384)
  211. eq(m.UV_ENOENT, err)
  212. local attrs = lfs.attributes(filec)
  213. eq(nil, attrs)
  214. end)
  215. itp('can truncate an existing file with kFileTruncate when opening a symlink',
  216. function()
  217. local err, fp = file_open(linkf, m.kFileTruncate, 384)
  218. eq(0, err)
  219. eq(true, fp.wr)
  220. eq(0, m.file_close(fp, false))
  221. local attrs = lfs.attributes(file1)
  222. eq(0, attrs.size)
  223. end)
  224. itp('fails to open a directory write-only', function()
  225. local err, _ = file_open(dir, m.kFileWriteOnly, 384)
  226. eq(m.UV_EISDIR, err)
  227. end)
  228. itp('fails to open a broken symbolic link write-only', function()
  229. local err, _ = file_open(linkb, m.kFileWriteOnly, 384)
  230. eq(m.UV_ENOENT, err)
  231. end)
  232. itp('fails to open a broken symbolic link read-only', function()
  233. local err, _ = file_open(linkb, m.kFileReadOnly, 384)
  234. eq(m.UV_ENOENT, err)
  235. end)
  236. end)
  237. describe('file_open_new', function()
  238. itp('can open a file read-only', function()
  239. local err, fp = file_open_new(file1, 0, 384)
  240. eq(0, err)
  241. eq(false, fp.wr)
  242. eq(0, m.file_free(fp, false))
  243. end)
  244. itp('fails to open an existing file with kFileCreateOnly', function()
  245. local err, fp = file_open_new(file1, m.kFileCreateOnly, 384)
  246. eq(m.UV_EEXIST, err)
  247. eq(nil, fp)
  248. end)
  249. end)
  250. describe('file_close', function()
  251. itp('can flush writes to disk also with true argument', function()
  252. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  253. eq(0, err)
  254. local wsize = file_write(fp, 'test')
  255. eq(4, wsize)
  256. eq(0, lfs.attributes(filec).size)
  257. eq(0, m.file_close(fp, true))
  258. eq(wsize, lfs.attributes(filec).size)
  259. end)
  260. end)
  261. describe('file_free', function()
  262. itp('can flush writes to disk also with true argument', function()
  263. local err, fp = file_open_new(filec, m.kFileCreateOnly, 384)
  264. eq(0, err)
  265. local wsize = file_write(fp, 'test')
  266. eq(4, wsize)
  267. eq(0, lfs.attributes(filec).size)
  268. eq(0, m.file_free(fp, true))
  269. eq(wsize, lfs.attributes(filec).size)
  270. end)
  271. end)
  272. describe('file_fsync', function()
  273. itp('can flush writes to disk', function()
  274. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  275. eq(0, file_fsync(fp))
  276. eq(0, err)
  277. eq(0, lfs.attributes(filec).size)
  278. local wsize = file_write(fp, 'test')
  279. eq(4, wsize)
  280. eq(0, lfs.attributes(filec).size)
  281. eq(0, file_fsync(fp))
  282. eq(wsize, lfs.attributes(filec).size)
  283. eq(0, m.file_close(fp, false))
  284. end)
  285. end)
  286. describe('file_flush', function()
  287. itp('can flush writes to disk', function()
  288. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  289. eq(0, file_flush(fp))
  290. eq(0, err)
  291. eq(0, lfs.attributes(filec).size)
  292. local wsize = file_write(fp, 'test')
  293. eq(4, wsize)
  294. eq(0, lfs.attributes(filec).size)
  295. eq(0, file_flush(fp))
  296. eq(wsize, lfs.attributes(filec).size)
  297. eq(0, m.file_close(fp, false))
  298. end)
  299. end)
  300. describe('file_read', function()
  301. itp('can read small chunks of input until eof', function()
  302. local err, fp = file_open(file1, 0, 384)
  303. eq(0, err)
  304. eq(false, fp.wr)
  305. local shift = 0
  306. while shift < #fcontents do
  307. local size = 3
  308. local exp_err = size
  309. local exp_s = fcontents:sub(shift + 1, shift + size)
  310. if shift + size >= #fcontents then
  311. exp_err = #fcontents - shift
  312. exp_s = (fcontents:sub(shift + 1, shift + size)
  313. .. (('\0'):rep(size - exp_err)))
  314. end
  315. eq({exp_err, exp_s}, {file_read(fp, size)})
  316. shift = shift + size
  317. end
  318. eq(0, m.file_close(fp, false))
  319. end)
  320. itp('can read the whole file at once', function()
  321. local err, fp = file_open(file1, 0, 384)
  322. eq(0, err)
  323. eq(false, fp.wr)
  324. eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
  325. eq({0, ('\0'):rep(#fcontents)}, {file_read(fp, #fcontents)})
  326. eq(0, m.file_close(fp, false))
  327. end)
  328. itp('can read more then 1024 bytes after reading a small chunk', function()
  329. local err, fp = file_open(file1, 0, 384)
  330. eq(0, err)
  331. eq(false, fp.wr)
  332. eq({5, fcontents:sub(1, 5)}, {file_read(fp, 5)})
  333. eq({#fcontents - 5, fcontents:sub(6) .. (('\0'):rep(5))},
  334. {file_read(fp, #fcontents)})
  335. eq(0, m.file_close(fp, false))
  336. end)
  337. itp('can read file by 768-byte-chunks', function()
  338. local err, fp = file_open(file1, 0, 384)
  339. eq(0, err)
  340. eq(false, fp.wr)
  341. local shift = 0
  342. while shift < #fcontents do
  343. local size = 768
  344. local exp_err = size
  345. local exp_s = fcontents:sub(shift + 1, shift + size)
  346. if shift + size >= #fcontents then
  347. exp_err = #fcontents - shift
  348. exp_s = (fcontents:sub(shift + 1, shift + size)
  349. .. (('\0'):rep(size - exp_err)))
  350. end
  351. eq({exp_err, exp_s}, {file_read(fp, size)})
  352. shift = shift + size
  353. end
  354. eq(0, m.file_close(fp, false))
  355. end)
  356. end)
  357. describe('file_write', function()
  358. itp('can write the whole file at once', function()
  359. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  360. eq(0, err)
  361. eq(true, fp.wr)
  362. local wr = file_write(fp, fcontents)
  363. eq(#fcontents, wr)
  364. eq(0, m.file_close(fp, false))
  365. eq(wr, lfs.attributes(filec).size)
  366. eq(fcontents, io.open(filec):read('*a'))
  367. end)
  368. itp('can write the whole file by small chunks', function()
  369. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  370. eq(0, err)
  371. eq(true, fp.wr)
  372. local shift = 0
  373. while shift < #fcontents do
  374. local size = 3
  375. local s = fcontents:sub(shift + 1, shift + size)
  376. local wr = file_write(fp, s)
  377. eq(wr, #s)
  378. shift = shift + size
  379. end
  380. eq(0, m.file_close(fp, false))
  381. eq(#fcontents, lfs.attributes(filec).size)
  382. eq(fcontents, io.open(filec):read('*a'))
  383. end)
  384. itp('can write the whole file by 768-byte-chunks', function()
  385. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  386. eq(0, err)
  387. eq(true, fp.wr)
  388. local shift = 0
  389. while shift < #fcontents do
  390. local size = 768
  391. local s = fcontents:sub(shift + 1, shift + size)
  392. local wr = file_write(fp, s)
  393. eq(wr, #s)
  394. shift = shift + size
  395. end
  396. eq(0, m.file_close(fp, false))
  397. eq(#fcontents, lfs.attributes(filec).size)
  398. eq(fcontents, io.open(filec):read('*a'))
  399. end)
  400. end)
  401. describe('msgpack_file_write', function()
  402. itp('can write the whole file at once', function()
  403. local err, fp = file_open(filec, m.kFileCreateOnly, 384)
  404. eq(0, err)
  405. eq(true, fp.wr)
  406. local wr = msgpack_file_write(fp, fcontents)
  407. eq(0, wr)
  408. eq(0, m.file_close(fp, false))
  409. eq(fcontents, io.open(filec):read('*a'))
  410. end)
  411. end)
  412. describe('file_skip', function()
  413. itp('can skip 3 bytes', function()
  414. local err, fp = file_open(file1, 0, 384)
  415. eq(0, err)
  416. eq(false, fp.wr)
  417. eq(3, file_skip(fp, 3))
  418. local rd, s = file_read(fp, 3)
  419. eq(3, rd)
  420. eq(fcontents:sub(4, 6), s)
  421. eq(0, m.file_close(fp, false))
  422. end)
  423. end)