fs_spec.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local clear = n.clear
  4. local exec_lua = n.exec_lua
  5. local eq = t.eq
  6. local mkdir_p = n.mkdir_p
  7. local rmdir = n.rmdir
  8. local nvim_dir = n.nvim_dir
  9. local command = n.command
  10. local api = n.api
  11. local test_build_dir = t.paths.test_build_dir
  12. local test_source_path = t.paths.test_source_path
  13. local nvim_prog = n.nvim_prog
  14. local is_os = t.is_os
  15. local mkdir = t.mkdir
  16. local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim'
  17. local link_limit = is_os('win') and 64 or (is_os('mac') or is_os('bsd')) and 33 or 41
  18. local test_basename_dirname_eq = {
  19. '~/foo/',
  20. '~/foo',
  21. '~/foo/bar.lua',
  22. 'foo.lua',
  23. ' ',
  24. '',
  25. '.',
  26. '..',
  27. '../',
  28. '~',
  29. '/usr/bin',
  30. '/usr/bin/gcc',
  31. '/',
  32. '/usr/',
  33. '/usr',
  34. 'c:/usr',
  35. 'c:/',
  36. 'c:',
  37. 'c:/users/foo',
  38. 'c:/users/foo/bar.lua',
  39. 'c:/users/foo/bar/../',
  40. '~/foo/bar\\baz',
  41. }
  42. local tests_windows_paths = {
  43. 'c:\\usr',
  44. 'c:\\',
  45. 'c:',
  46. 'c:\\users\\foo',
  47. 'c:\\users\\foo\\bar.lua',
  48. 'c:\\users\\foo\\bar\\..\\',
  49. }
  50. before_each(clear)
  51. describe('vim.fs', function()
  52. describe('parents()', function()
  53. it('works', function()
  54. local test_dir = nvim_dir .. '/test'
  55. mkdir_p(test_dir)
  56. local dirs = {} --- @type string[]
  57. for dir in vim.fs.parents(test_dir .. '/foo.txt') do
  58. dirs[#dirs + 1] = dir
  59. if dir == test_build_dir then
  60. break
  61. end
  62. end
  63. eq({ test_dir, nvim_dir, test_build_dir }, dirs)
  64. rmdir(test_dir)
  65. end)
  66. end)
  67. describe('dirname()', function()
  68. it('works', function()
  69. eq(test_build_dir, vim.fs.dirname(nvim_dir))
  70. ---@param paths string[]
  71. ---@param is_win? boolean
  72. local function test_paths(paths, is_win)
  73. local gsub = is_win and [[:gsub('\\', '/')]] or ''
  74. local code = string.format(
  75. [[
  76. local path = ...
  77. return vim.fn.fnamemodify(path,':h')%s
  78. ]],
  79. gsub
  80. )
  81. for _, path in ipairs(paths) do
  82. eq(exec_lua(code, path), vim.fs.dirname(path), path)
  83. end
  84. end
  85. test_paths(test_basename_dirname_eq)
  86. if is_os('win') then
  87. test_paths(tests_windows_paths, true)
  88. end
  89. end)
  90. end)
  91. describe('basename()', function()
  92. it('works', function()
  93. eq(nvim_prog_basename, vim.fs.basename(nvim_prog))
  94. ---@param paths string[]
  95. ---@param is_win? boolean
  96. local function test_paths(paths, is_win)
  97. local gsub = is_win and [[:gsub('\\', '/')]] or ''
  98. local code = string.format(
  99. [[
  100. local path = ...
  101. return vim.fn.fnamemodify(path,':t')%s
  102. ]],
  103. gsub
  104. )
  105. for _, path in ipairs(paths) do
  106. eq(exec_lua(code, path), vim.fs.basename(path), path)
  107. end
  108. end
  109. test_paths(test_basename_dirname_eq)
  110. if is_os('win') then
  111. test_paths(tests_windows_paths, true)
  112. end
  113. end)
  114. end)
  115. describe('dir()', function()
  116. before_each(function()
  117. mkdir('testd')
  118. mkdir('testd/a')
  119. mkdir('testd/a/b')
  120. mkdir('testd/a/b/c')
  121. end)
  122. after_each(function()
  123. rmdir('testd')
  124. end)
  125. it('works', function()
  126. eq(
  127. true,
  128. exec_lua(function()
  129. for name, type in vim.fs.dir(nvim_dir) do
  130. if name == nvim_prog_basename and type == 'file' then
  131. return true
  132. end
  133. end
  134. return false
  135. end)
  136. )
  137. end)
  138. it('works with opts.depth, opts.skip and opts.follow', function()
  139. io.open('testd/a1', 'w'):close()
  140. io.open('testd/b1', 'w'):close()
  141. io.open('testd/c1', 'w'):close()
  142. io.open('testd/a/a2', 'w'):close()
  143. io.open('testd/a/b2', 'w'):close()
  144. io.open('testd/a/c2', 'w'):close()
  145. io.open('testd/a/b/a3', 'w'):close()
  146. io.open('testd/a/b/b3', 'w'):close()
  147. io.open('testd/a/b/c3', 'w'):close()
  148. io.open('testd/a/b/c/a4', 'w'):close()
  149. io.open('testd/a/b/c/b4', 'w'):close()
  150. io.open('testd/a/b/c/c4', 'w'):close()
  151. local function run(dir, depth, skip, follow)
  152. return exec_lua(function(follow_)
  153. local r = {} --- @type table<string, string>
  154. local skip_f --- @type function
  155. if skip then
  156. skip_f = function(n0)
  157. if vim.tbl_contains(skip or {}, n0) then
  158. return false
  159. end
  160. end
  161. end
  162. for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f, follow = follow_ }) do
  163. r[name] = type_
  164. end
  165. return r
  166. end, follow)
  167. end
  168. local exp = {}
  169. exp['a1'] = 'file'
  170. exp['b1'] = 'file'
  171. exp['c1'] = 'file'
  172. exp['a'] = 'directory'
  173. eq(exp, run('testd', 1))
  174. exp['a/a2'] = 'file'
  175. exp['a/b2'] = 'file'
  176. exp['a/c2'] = 'file'
  177. exp['a/b'] = 'directory'
  178. local lexp = vim.deepcopy(exp)
  179. eq(exp, run('testd', 2))
  180. exp['a/b/a3'] = 'file'
  181. exp['a/b/b3'] = 'file'
  182. exp['a/b/c3'] = 'file'
  183. exp['a/b/c'] = 'directory'
  184. eq(exp, run('testd', 3))
  185. eq(exp, run('testd', 999, { 'a/b/c' }))
  186. exp['a/b/c/a4'] = 'file'
  187. exp['a/b/c/b4'] = 'file'
  188. exp['a/b/c/c4'] = 'file'
  189. eq(exp, run('testd', 999))
  190. vim.uv.fs_symlink(vim.uv.fs_realpath('testd/a'), 'testd/l', { junction = true, dir = true })
  191. lexp['l'] = 'link'
  192. eq(lexp, run('testd', 2, nil, false))
  193. lexp['l/a2'] = 'file'
  194. lexp['l/b2'] = 'file'
  195. lexp['l/c2'] = 'file'
  196. lexp['l/b'] = 'directory'
  197. eq(lexp, run('testd', 2, nil, true))
  198. end)
  199. it('follow=true handles symlink loop', function()
  200. local cwd = 'testd/a/b/c'
  201. local symlink = cwd .. '/link_loop' ---@type string
  202. vim.uv.fs_symlink(vim.uv.fs_realpath(cwd), symlink, { junction = true, dir = true })
  203. eq(
  204. link_limit,
  205. exec_lua(function()
  206. return #vim.iter(vim.fs.dir(cwd, { depth = math.huge, follow = true })):totable()
  207. end)
  208. )
  209. end)
  210. end)
  211. describe('find()', function()
  212. it('works', function()
  213. eq(
  214. { test_build_dir .. '/build' },
  215. vim.fs.find('build', { path = nvim_dir, upward = true, type = 'directory' })
  216. )
  217. eq({ nvim_prog }, vim.fs.find(nvim_prog_basename, { path = test_build_dir, type = 'file' }))
  218. local parent, name = nvim_dir:match('^(.*/)([^/]+)$')
  219. eq({ nvim_dir }, vim.fs.find(name, { path = parent, upward = true, type = 'directory' }))
  220. end)
  221. it('follows symlinks', function()
  222. local build_dir = test_source_path .. '/build' ---@type string
  223. local symlink = test_source_path .. '/build_link' ---@type string
  224. vim.uv.fs_symlink(build_dir, symlink, { junction = true, dir = true })
  225. finally(function()
  226. vim.uv.fs_unlink(symlink)
  227. end)
  228. eq(
  229. { nvim_prog, symlink .. '/bin/' .. nvim_prog_basename },
  230. vim.fs.find(nvim_prog_basename, {
  231. path = test_source_path,
  232. type = 'file',
  233. limit = 2,
  234. follow = true,
  235. })
  236. )
  237. eq(
  238. { nvim_prog },
  239. vim.fs.find(nvim_prog_basename, {
  240. path = test_source_path,
  241. type = 'file',
  242. limit = 2,
  243. follow = false,
  244. })
  245. )
  246. end)
  247. it('follow=true handles symlink loop', function()
  248. local cwd = test_source_path ---@type string
  249. local symlink = test_source_path .. '/loop_link' ---@type string
  250. vim.uv.fs_symlink(cwd, symlink, { junction = true, dir = true })
  251. finally(function()
  252. vim.uv.fs_unlink(symlink)
  253. end)
  254. eq(link_limit, #vim.fs.find(nvim_prog_basename, {
  255. path = test_source_path,
  256. type = 'file',
  257. limit = math.huge,
  258. follow = true,
  259. }))
  260. end)
  261. it('accepts predicate as names', function()
  262. local opts = { path = nvim_dir, upward = true, type = 'directory' }
  263. eq(
  264. { test_build_dir .. '/build' },
  265. vim.fs.find(function(x)
  266. return x == 'build'
  267. end, opts)
  268. )
  269. eq(
  270. { nvim_prog },
  271. vim.fs.find(function(x)
  272. return x == nvim_prog_basename
  273. end, { path = test_build_dir, type = 'file' })
  274. )
  275. eq(
  276. {},
  277. vim.fs.find(function(x)
  278. return x == 'no-match'
  279. end, opts)
  280. )
  281. opts = { path = test_source_path .. '/contrib', limit = math.huge }
  282. eq(
  283. exec_lua(function()
  284. return vim.tbl_map(
  285. vim.fs.basename,
  286. vim.fn.glob(test_source_path .. '/contrib/*', false, true)
  287. )
  288. end),
  289. vim.tbl_map(
  290. vim.fs.basename,
  291. vim.fs.find(function(_, d)
  292. return d:match('[\\/]contrib$')
  293. end, opts)
  294. )
  295. )
  296. end)
  297. end)
  298. describe('root()', function()
  299. before_each(function()
  300. command('edit test/functional/fixtures/tty-test.c')
  301. end)
  302. it('works with a single marker', function()
  303. eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
  304. end)
  305. it('works with multiple markers', function()
  306. local bufnr = api.nvim_get_current_buf()
  307. eq(
  308. vim.fs.joinpath(test_source_path, 'test/functional/fixtures'),
  309. exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', 'CMakePresets.json'})]], bufnr)
  310. )
  311. end)
  312. it('works with a function', function()
  313. ---@type string
  314. local result = exec_lua(function()
  315. return vim.fs.root(0, function(name, _)
  316. return name:match('%.txt$')
  317. end)
  318. end)
  319. eq(vim.fs.joinpath(test_source_path, 'test/functional/fixtures'), result)
  320. end)
  321. it('works with a filename argument', function()
  322. eq(test_source_path, exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], nvim_prog))
  323. end)
  324. it('works with a relative path', function()
  325. eq(
  326. test_source_path,
  327. exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], vim.fs.basename(nvim_prog))
  328. )
  329. end)
  330. it('uses cwd for unnamed buffers', function()
  331. command('new')
  332. eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
  333. end)
  334. it("uses cwd for buffers with non-empty 'buftype'", function()
  335. command('new')
  336. command('set buftype=nofile')
  337. command('file lua://')
  338. eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
  339. end)
  340. end)
  341. describe('joinpath()', function()
  342. it('works', function()
  343. eq('foo/bar/baz', vim.fs.joinpath('foo', 'bar', 'baz'))
  344. eq('foo/bar/baz', vim.fs.joinpath('foo', '/bar/', '/baz'))
  345. end)
  346. it('rewrites backslashes on Windows', function()
  347. if is_os('win') then
  348. eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]]))
  349. else
  350. eq([[foo/\\bar\\\\baz/zub\]], vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]]))
  351. end
  352. end)
  353. it('strips redundant slashes', function()
  354. if is_os('win') then
  355. eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo//]], [[\\bar\\\\baz]], [[zub\]]))
  356. else
  357. eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[//bar////baz]], [[zub/]]))
  358. end
  359. end)
  360. end)
  361. describe('normalize()', function()
  362. it('removes trailing /', function()
  363. eq('/home/user', vim.fs.normalize('/home/user/'))
  364. end)
  365. it('works with /', function()
  366. eq('/', vim.fs.normalize('/'))
  367. end)
  368. it('works with ~', function()
  369. eq(vim.fs.normalize(assert(vim.uv.os_homedir())) .. '/src/foo', vim.fs.normalize('~/src/foo'))
  370. end)
  371. it('works with environment variables', function()
  372. local xdg_config_home = test_build_dir .. '/.config'
  373. eq(
  374. xdg_config_home .. '/nvim',
  375. exec_lua(function()
  376. vim.env.XDG_CONFIG_HOME = xdg_config_home
  377. return vim.fs.normalize('$XDG_CONFIG_HOME/nvim')
  378. end)
  379. )
  380. end)
  381. -- Opts required for testing posix paths and win paths
  382. local posix_opts = { win = false }
  383. local win_opts = { win = true }
  384. it('preserves leading double slashes in POSIX paths', function()
  385. eq('//foo', vim.fs.normalize('//foo', posix_opts))
  386. eq('//foo/bar', vim.fs.normalize('//foo//bar////', posix_opts))
  387. eq('/foo', vim.fs.normalize('///foo', posix_opts))
  388. eq('//', vim.fs.normalize('//', posix_opts))
  389. eq('/', vim.fs.normalize('///', posix_opts))
  390. eq('/foo/bar', vim.fs.normalize('/foo//bar////', posix_opts))
  391. end)
  392. it('normalizes drive letter', function()
  393. eq('C:/', vim.fs.normalize('C:/', win_opts))
  394. eq('C:/', vim.fs.normalize('c:/', win_opts))
  395. eq('D:/', vim.fs.normalize('d:/', win_opts))
  396. eq('C:', vim.fs.normalize('C:', win_opts))
  397. eq('C:', vim.fs.normalize('c:', win_opts))
  398. eq('D:', vim.fs.normalize('d:', win_opts))
  399. eq('C:/foo/test', vim.fs.normalize('C:/foo/test/', win_opts))
  400. eq('C:/foo/test', vim.fs.normalize('c:/foo/test/', win_opts))
  401. eq('D:foo/test', vim.fs.normalize('D:foo/test/', win_opts))
  402. eq('D:foo/test', vim.fs.normalize('d:foo/test/', win_opts))
  403. end)
  404. it('always treats paths as case-sensitive #31833', function()
  405. eq('TEST', vim.fs.normalize('TEST', win_opts))
  406. eq('test', vim.fs.normalize('test', win_opts))
  407. eq('C:/FOO/test', vim.fs.normalize('C:/FOO/test', win_opts))
  408. eq('C:/foo/test', vim.fs.normalize('C:/foo/test', win_opts))
  409. eq('//SERVER/SHARE/FOO/BAR', vim.fs.normalize('//SERVER/SHARE/FOO/BAR', win_opts))
  410. eq('//server/share/foo/bar', vim.fs.normalize('//server/share/foo/bar', win_opts))
  411. eq('C:/FOO/test', vim.fs.normalize('c:/FOO/test', win_opts))
  412. end)
  413. it('allows backslashes on unix-based os', function()
  414. eq('/home/user/hello\\world', vim.fs.normalize('/home/user/hello\\world', posix_opts))
  415. end)
  416. it('preserves / after drive letters', function()
  417. eq('C:/', vim.fs.normalize([[C:\]], win_opts))
  418. end)
  419. it('works with UNC and DOS device paths', function()
  420. eq('//server/share/foo/bar', vim.fs.normalize([[\\server\\share\\\foo\bar\\\]], win_opts))
  421. eq('//system07/C$/', vim.fs.normalize([[\\system07\C$\\\\]], win_opts))
  422. eq('//./C:/foo/bar', vim.fs.normalize([[\\.\\C:\foo\\\\bar]], win_opts))
  423. eq('//?/C:/foo/bar', vim.fs.normalize([[\\?\C:\\\foo\bar\\\\]], win_opts))
  424. eq(
  425. '//?/UNC/server/share/foo/bar',
  426. vim.fs.normalize([[\\?\UNC\server\\\share\\\\foo\\\bar]], win_opts)
  427. )
  428. eq('//./BootPartition/foo/bar', vim.fs.normalize([[\\.\BootPartition\\foo\bar]], win_opts))
  429. eq(
  430. '//./Volume{12345678-1234-1234-1234-1234567890AB}/foo/bar',
  431. vim.fs.normalize([[\\.\Volume{12345678-1234-1234-1234-1234567890AB}\\\foo\bar\\]], win_opts)
  432. )
  433. end)
  434. it('handles invalid UNC and DOS device paths', function()
  435. eq('//server/share', vim.fs.normalize([[\\server\share]], win_opts))
  436. eq('//server/', vim.fs.normalize([[\\server\]], win_opts))
  437. eq('//./UNC/server/share', vim.fs.normalize([[\\.\UNC\server\share]], win_opts))
  438. eq('//?/UNC/server/', vim.fs.normalize([[\\?\UNC\server\]], win_opts))
  439. eq('//?/UNC/server/..', vim.fs.normalize([[\\?\UNC\server\..]], win_opts))
  440. eq('//./', vim.fs.normalize([[\\.\]], win_opts))
  441. eq('//./foo', vim.fs.normalize([[\\.\foo]], win_opts))
  442. eq('//./BootPartition', vim.fs.normalize([[\\.\BootPartition]], win_opts))
  443. end)
  444. it('converts backward slashes', function()
  445. eq('C:/Users/jdoe', vim.fs.normalize([[C:\Users\jdoe]], win_opts))
  446. end)
  447. describe('. and .. component resolving', function()
  448. it('works', function()
  449. -- Windows paths
  450. eq('C:/Users', vim.fs.normalize([[C:\Users\jdoe\Downloads\.\..\..\]], win_opts))
  451. eq('C:/Users/jdoe', vim.fs.normalize([[C:\Users\jdoe\Downloads\.\..\.\.\]], win_opts))
  452. eq('C:/', vim.fs.normalize('C:/Users/jdoe/Downloads/./../../../', win_opts))
  453. eq('C:foo', vim.fs.normalize([[C:foo\bar\.\..\.]], win_opts))
  454. -- POSIX paths
  455. eq('/home', vim.fs.normalize('/home/jdoe/Downloads/./../..', posix_opts))
  456. eq('/home/jdoe', vim.fs.normalize('/home/jdoe/Downloads/./../././', posix_opts))
  457. eq('/', vim.fs.normalize('/home/jdoe/Downloads/./../../../', posix_opts))
  458. -- OS-agnostic relative paths
  459. eq('foo/bar/baz', vim.fs.normalize('foo/bar/foobar/../baz/./'))
  460. eq('foo/bar', vim.fs.normalize('foo/bar/foobar/../baz/./../../bar/./.'))
  461. end)
  462. it('works when relative path reaches current directory', function()
  463. eq('C:', vim.fs.normalize('C:foo/bar/../../.', win_opts))
  464. eq('.', vim.fs.normalize('.'))
  465. eq('.', vim.fs.normalize('././././'))
  466. eq('.', vim.fs.normalize('foo/bar/../../.'))
  467. end)
  468. it('works when relative path goes outside current directory', function()
  469. eq('../../foo/bar', vim.fs.normalize('../../foo/bar'))
  470. eq('../foo', vim.fs.normalize('foo/bar/../../../foo'))
  471. eq('C:../foo', vim.fs.normalize('C:../foo', win_opts))
  472. eq('C:../../foo/bar', vim.fs.normalize('C:foo/../../../foo/bar', win_opts))
  473. end)
  474. it('.. in root directory resolves to itself', function()
  475. eq('C:/', vim.fs.normalize('C:/../../', win_opts))
  476. eq('C:/foo', vim.fs.normalize('C:/foo/../../foo', win_opts))
  477. eq('//server/share/', vim.fs.normalize([[\\server\share\..\..]], win_opts))
  478. eq('//server/share/foo', vim.fs.normalize([[\\server\\share\foo\..\..\foo]], win_opts))
  479. eq('//./C:/', vim.fs.normalize([[\\.\C:\..\..]], win_opts))
  480. eq('//?/C:/foo', vim.fs.normalize([[\\?\C:\..\..\foo]], win_opts))
  481. eq('//./UNC/server/share/', vim.fs.normalize([[\\.\UNC\\server\share\..\..\]], win_opts))
  482. eq(
  483. '//?/UNC/server/share/foo',
  484. vim.fs.normalize([[\\?\UNC\server\\share\..\..\foo]], win_opts)
  485. )
  486. eq('//?/BootPartition/', vim.fs.normalize([[\\?\BootPartition\..\..]], win_opts))
  487. eq('//./BootPartition/foo', vim.fs.normalize([[\\.\BootPartition\..\..\foo]], win_opts))
  488. eq('/', vim.fs.normalize('/../../', posix_opts))
  489. eq('/foo', vim.fs.normalize('/foo/../../foo', posix_opts))
  490. end)
  491. end)
  492. end)
  493. describe('abspath()', function()
  494. local cwd = assert(t.fix_slashes(assert(vim.uv.cwd())))
  495. local home = t.fix_slashes(assert(vim.uv.os_homedir()))
  496. it('works', function()
  497. eq(cwd .. '/foo', vim.fs.abspath('foo'))
  498. eq(cwd .. '/././foo', vim.fs.abspath('././foo'))
  499. eq(cwd .. '/.././../foo', vim.fs.abspath('.././../foo'))
  500. end)
  501. it('works with absolute paths', function()
  502. if is_os('win') then
  503. eq([[C:/foo]], vim.fs.abspath([[C:\foo]]))
  504. eq([[C:/foo/../.]], vim.fs.abspath([[C:\foo\..\.]]))
  505. eq('//foo/bar', vim.fs.abspath('\\\\foo\\bar'))
  506. else
  507. eq('/foo/../.', vim.fs.abspath('/foo/../.'))
  508. eq('/foo/bar', vim.fs.abspath('/foo/bar'))
  509. end
  510. end)
  511. it('expands ~', function()
  512. eq(home .. '/foo', vim.fs.abspath('~/foo'))
  513. eq(home .. '/./.././foo', vim.fs.abspath('~/./.././foo'))
  514. end)
  515. if is_os('win') then
  516. it('works with drive-specific cwd on Windows', function()
  517. local cwd_drive = cwd:match('^%w:')
  518. eq(cwd .. '/foo', vim.fs.abspath(cwd_drive .. 'foo'))
  519. end)
  520. end
  521. end)
  522. describe('relpath()', function()
  523. it('works', function()
  524. local cwd = assert(t.fix_slashes(assert(vim.uv.cwd())))
  525. local my_dir = vim.fs.joinpath(cwd, 'foo')
  526. eq(nil, vim.fs.relpath('/var/lib', '/var'))
  527. eq(nil, vim.fs.relpath('/var/lib', '/bin'))
  528. eq(nil, vim.fs.relpath(my_dir, 'bin'))
  529. eq(nil, vim.fs.relpath(my_dir, './bin'))
  530. eq(nil, vim.fs.relpath(my_dir, '././'))
  531. eq(nil, vim.fs.relpath(my_dir, '../'))
  532. eq(nil, vim.fs.relpath('/var/lib', '/'))
  533. eq(nil, vim.fs.relpath('/var/lib', '//'))
  534. eq(nil, vim.fs.relpath(' ', '/var'))
  535. eq(nil, vim.fs.relpath(' ', '/var'))
  536. eq('.', vim.fs.relpath('/var/lib', '/var/lib'))
  537. eq('lib', vim.fs.relpath('/var/', '/var/lib'))
  538. eq('var/lib', vim.fs.relpath('/', '/var/lib'))
  539. eq('bar/package.json', vim.fs.relpath('/foo/test', '/foo/test/bar/package.json'))
  540. eq('foo/bar', vim.fs.relpath(cwd, 'foo/bar'))
  541. eq('foo/bar', vim.fs.relpath('.', vim.fs.joinpath(cwd, 'foo/bar')))
  542. eq('bar', vim.fs.relpath('foo', 'foo/bar'))
  543. eq(nil, vim.fs.relpath('/var/lib', '/var/library/foo'))
  544. if is_os('win') then
  545. eq(nil, vim.fs.relpath('/', ' '))
  546. eq(nil, vim.fs.relpath('/', 'var'))
  547. else
  548. local cwd_rel_root = cwd:sub(2)
  549. eq(cwd_rel_root .. '/ ', vim.fs.relpath('/', ' '))
  550. eq(cwd_rel_root .. '/var', vim.fs.relpath('/', 'var'))
  551. end
  552. if is_os('win') then
  553. eq(nil, vim.fs.relpath('c:/aaaa/', '/aaaa/cccc'))
  554. eq(nil, vim.fs.relpath('c:/aaaa/', './aaaa/cccc'))
  555. eq(nil, vim.fs.relpath('c:/aaaa/', 'aaaa/cccc'))
  556. eq(nil, vim.fs.relpath('c:/blah\\blah', 'd:/games'))
  557. eq(nil, vim.fs.relpath('c:/games', 'd:/games'))
  558. eq(nil, vim.fs.relpath('c:/games', 'd:/games/foo'))
  559. eq(nil, vim.fs.relpath('c:/aaaa/bbbb', 'c:/aaaa'))
  560. eq('cccc', vim.fs.relpath('c:/aaaa/', 'c:/aaaa/cccc'))
  561. eq('aaaa/bbbb', vim.fs.relpath('C:/', 'c:\\aaaa\\bbbb'))
  562. eq('bar/package.json', vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json'))
  563. eq('baz', vim.fs.relpath('\\\\foo\\bar', '\\\\foo\\bar\\baz'))
  564. eq(nil, vim.fs.relpath('a/b/c', 'a\\b'))
  565. eq('d', vim.fs.relpath('a/b/c', 'a\\b\\c\\d'))
  566. eq('.', vim.fs.relpath('\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'))
  567. eq(nil, vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\Test\\bar\\package.json'))
  568. end
  569. end)
  570. end)
  571. end)