env_spec.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. local helpers = require('test.unit.helpers')(after_each)
  2. local itp = helpers.gen_itp(it)
  3. local cimport = helpers.cimport
  4. local eq = helpers.eq
  5. local neq = helpers.neq
  6. local ffi = helpers.ffi
  7. local cstr = helpers.cstr
  8. local to_cstr = helpers.to_cstr
  9. local NULL = helpers.NULL
  10. local OK = 0
  11. require('lfs')
  12. local cimp = cimport('./src/nvim/os/os.h')
  13. describe('env.c', function()
  14. local function os_env_exists(name)
  15. return cimp.os_env_exists(to_cstr(name))
  16. end
  17. local function os_setenv(name, value, override)
  18. return cimp.os_setenv(to_cstr(name), to_cstr(value), override)
  19. end
  20. local function os_unsetenv(name)
  21. return cimp.os_unsetenv(to_cstr(name))
  22. end
  23. local function os_getenv(name)
  24. local rval = cimp.os_getenv(to_cstr(name))
  25. if rval ~= NULL then
  26. return ffi.string(rval)
  27. else
  28. return NULL
  29. end
  30. end
  31. itp('os_env_exists', function()
  32. eq(false, os_env_exists(''))
  33. eq(false, os_env_exists(' '))
  34. eq(false, os_env_exists('\t'))
  35. eq(false, os_env_exists('\n'))
  36. eq(false, os_env_exists('AaあB <= very weird name...'))
  37. local varname = 'NVIM_UNIT_TEST_os_env_exists'
  38. eq(false, os_env_exists(varname))
  39. eq(OK, os_setenv(varname, 'foo bar baz ...', 1))
  40. eq(true, os_env_exists(varname))
  41. end)
  42. describe('os_setenv', function()
  43. itp('sets an env var and returns success', function()
  44. local name = 'NVIM_UNIT_TEST_SETENV_1N'
  45. local value = 'NVIM_UNIT_TEST_SETENV_1V'
  46. eq(nil, os.getenv(name))
  47. eq(OK, os_setenv(name, value, 1))
  48. eq(value, os.getenv(name))
  49. -- Set empty, then set non-empty, then retrieve.
  50. eq(OK, os_setenv(name, '', 1))
  51. eq('', os.getenv(name))
  52. eq(OK, os_setenv(name, 'non-empty', 1))
  53. eq('non-empty', os.getenv(name))
  54. end)
  55. itp("`overwrite` behavior", function()
  56. local name = 'NVIM_UNIT_TEST_SETENV_2N'
  57. local value = 'NVIM_UNIT_TEST_SETENV_2V'
  58. local value_updated = 'NVIM_UNIT_TEST_SETENV_2V_UPDATED'
  59. eq(OK, os_setenv(name, value, 0))
  60. eq(value, os.getenv(name))
  61. eq(OK, os_setenv(name, value_updated, 0))
  62. eq(value, os.getenv(name))
  63. eq(OK, os_setenv(name, value_updated, 1))
  64. eq(value_updated, os.getenv(name))
  65. end)
  66. end)
  67. describe('os_setenv_append_path', function()
  68. itp('appends :/foo/bar to $PATH', function()
  69. local original_path = os.getenv('PATH')
  70. eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz.exe')))
  71. eq(original_path..':/foo/bar', os.getenv('PATH'))
  72. end)
  73. itp('avoids redundant separator when appending to $PATH #7377', function()
  74. os_setenv('PATH', '/a/b/c:', true)
  75. eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz.exe')))
  76. -- Must not have duplicate separators. #7377
  77. eq('/a/b/c:/foo/bar', os.getenv('PATH'))
  78. end)
  79. itp('returns false if `fname` is not absolute', function()
  80. local original_path = os.getenv('PATH')
  81. eq(false, cimp.os_setenv_append_path(to_cstr('foo/bar/baz.exe')))
  82. eq(original_path, os.getenv('PATH'))
  83. end)
  84. end)
  85. describe('os_shell_is_cmdexe', function()
  86. itp('returns true for expected names', function()
  87. eq(true, cimp.os_shell_is_cmdexe(to_cstr('cmd.exe')))
  88. eq(true, cimp.os_shell_is_cmdexe(to_cstr('cmd')))
  89. eq(true, cimp.os_shell_is_cmdexe(to_cstr('CMD.EXE')))
  90. eq(true, cimp.os_shell_is_cmdexe(to_cstr('CMD')))
  91. os_setenv('COMSPEC', '/foo/bar/cmd.exe', 0)
  92. eq(true, cimp.os_shell_is_cmdexe(to_cstr('$COMSPEC')))
  93. os_setenv('COMSPEC', [[C:\system32\cmd.exe]], 0)
  94. eq(true, cimp.os_shell_is_cmdexe(to_cstr('$COMSPEC')))
  95. end)
  96. itp('returns false for unexpected names', function()
  97. eq(false, cimp.os_shell_is_cmdexe(to_cstr('')))
  98. eq(false, cimp.os_shell_is_cmdexe(to_cstr('powershell')))
  99. eq(false, cimp.os_shell_is_cmdexe(to_cstr(' cmd.exe ')))
  100. eq(false, cimp.os_shell_is_cmdexe(to_cstr('cm')))
  101. eq(false, cimp.os_shell_is_cmdexe(to_cstr('md')))
  102. eq(false, cimp.os_shell_is_cmdexe(to_cstr('cmd.ex')))
  103. os_setenv('COMSPEC', '/foo/bar/cmd', 0)
  104. eq(false, cimp.os_shell_is_cmdexe(to_cstr('$COMSPEC')))
  105. end)
  106. end)
  107. describe('os_getenv', function()
  108. itp('reads an env var', function()
  109. local name = 'NVIM_UNIT_TEST_GETENV_1N'
  110. local value = 'NVIM_UNIT_TEST_GETENV_1V'
  111. eq(NULL, os_getenv(name))
  112. -- Use os_setenv because Lua doesn't have setenv.
  113. os_setenv(name, value, 1)
  114. eq(value, os_getenv(name))
  115. -- Get a big value.
  116. local bigval = ('x'):rep(256)
  117. eq(OK, os_setenv(name, bigval, 1))
  118. eq(bigval, os_getenv(name))
  119. -- Set non-empty, then set empty.
  120. eq(OK, os_setenv(name, 'non-empty', 1))
  121. eq('non-empty', os_getenv(name))
  122. eq(OK, os_setenv(name, '', 1))
  123. eq(NULL, os_getenv(name))
  124. end)
  125. itp('returns NULL if the env var is not found', function()
  126. eq(NULL, os_getenv('NVIM_UNIT_TEST_GETENV_NOTFOUND'))
  127. end)
  128. end)
  129. itp('os_unsetenv', function()
  130. local name = 'TEST_UNSETENV'
  131. local value = 'TESTVALUE'
  132. os_setenv(name, value, 1)
  133. eq(OK, os_unsetenv(name))
  134. neq(value, os_getenv(name))
  135. -- Depending on the platform the var might be unset or set as ''
  136. assert.True(os_getenv(name) == nil or os_getenv(name) == '')
  137. if os_getenv(name) == nil then
  138. eq(false, os_env_exists(name))
  139. end
  140. end)
  141. describe('os_getenvname_at_index', function()
  142. itp('returns names of environment variables', function()
  143. local test_name = 'NVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N'
  144. local test_value = 'NVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V'
  145. os_setenv(test_name, test_value, 1)
  146. local i = 0
  147. local names = { }
  148. local found_name = false
  149. local name = cimp.os_getenvname_at_index(i)
  150. while name ~= NULL do
  151. table.insert(names, ffi.string(name))
  152. if (ffi.string(name)) == test_name then
  153. found_name = true
  154. end
  155. i = i + 1
  156. name = cimp.os_getenvname_at_index(i)
  157. end
  158. eq(true, #names > 0)
  159. eq(true, found_name)
  160. end)
  161. itp('returns NULL if the index is out of bounds', function()
  162. local huge = ffi.new('size_t', 10000)
  163. local maxuint32 = ffi.new('size_t', 4294967295)
  164. eq(NULL, cimp.os_getenvname_at_index(huge))
  165. eq(NULL, cimp.os_getenvname_at_index(maxuint32))
  166. if ffi.abi('64bit') then
  167. -- couldn't use a bigger number because it gets converted to
  168. -- double somewhere, should be big enough anyway
  169. -- maxuint64 = ffi.new 'size_t', 18446744073709551615
  170. local maxuint64 = ffi.new('size_t', 18446744073709000000)
  171. eq(NULL, cimp.os_getenvname_at_index(maxuint64))
  172. end
  173. end)
  174. end)
  175. describe('os_get_pid', function()
  176. itp('returns the process ID', function()
  177. local stat_file = io.open('/proc/self/stat')
  178. if stat_file then
  179. local stat_str = stat_file:read('*l')
  180. stat_file:close()
  181. local pid = tonumber((stat_str:match('%d+')))
  182. eq(pid, tonumber(cimp.os_get_pid()))
  183. else
  184. -- /proc is not available on all systems, test if pid is nonzero.
  185. eq(true, (cimp.os_get_pid() > 0))
  186. end
  187. end)
  188. end)
  189. describe('os_get_hostname', function()
  190. itp('returns the hostname', function()
  191. local handle = io.popen('hostname')
  192. local hostname = handle:read('*l')
  193. handle:close()
  194. local hostname_buf = cstr(255, '')
  195. cimp.os_get_hostname(hostname_buf, 255)
  196. eq(hostname, (ffi.string(hostname_buf)))
  197. end)
  198. end)
  199. describe('expand_env_esc', function()
  200. itp('expands environment variables', function()
  201. local name = 'NVIM_UNIT_TEST_EXPAND_ENV_ESCN'
  202. local value = 'NVIM_UNIT_TEST_EXPAND_ENV_ESCV'
  203. os_setenv(name, value, 1)
  204. -- TODO(bobtwinkles) This only tests Unix expansions. There should be a
  205. -- test for Windows as well
  206. local input1 = to_cstr('$NVIM_UNIT_TEST_EXPAND_ENV_ESCN/test')
  207. local input2 = to_cstr('${NVIM_UNIT_TEST_EXPAND_ENV_ESCN}/test')
  208. local output_buff1 = cstr(255, '')
  209. local output_buff2 = cstr(255, '')
  210. local output_expected = 'NVIM_UNIT_TEST_EXPAND_ENV_ESCV/test'
  211. cimp.expand_env_esc(input1, output_buff1, 255, false, true, NULL)
  212. cimp.expand_env_esc(input2, output_buff2, 255, false, true, NULL)
  213. eq(output_expected, ffi.string(output_buff1))
  214. eq(output_expected, ffi.string(output_buff2))
  215. end)
  216. itp('expands ~ once when `one` is true', function()
  217. local input = '~/foo ~ foo'
  218. local homedir = cstr(255, '')
  219. cimp.expand_env_esc(to_cstr('~'), homedir, 255, false, true, NULL)
  220. local output_expected = ffi.string(homedir) .. "/foo ~ foo"
  221. local output = cstr(255, '')
  222. cimp.expand_env_esc(to_cstr(input), output, 255, false, true, NULL)
  223. eq(ffi.string(output), ffi.string(output_expected))
  224. end)
  225. itp('expands ~ every time when `one` is false', function()
  226. local input = to_cstr('~/foo ~ foo')
  227. local dst = cstr(255, '')
  228. cimp.expand_env_esc(to_cstr('~'), dst, 255, false, true, NULL)
  229. local homedir = ffi.string(dst)
  230. local output_expected = homedir .. "/foo " .. homedir .. " foo"
  231. local output = cstr(255, '')
  232. cimp.expand_env_esc(input, output, 255, false, false, NULL)
  233. eq(output_expected, ffi.string(output))
  234. end)
  235. itp('does not crash #3725', function()
  236. local name_out = ffi.new('char[100]')
  237. cimp.os_get_username(name_out, 100)
  238. local curuser = ffi.string(name_out)
  239. local src = to_cstr("~"..curuser.."/Vcs/django-rest-framework/rest_framework/renderers.py")
  240. local dst = cstr(256, "~"..curuser)
  241. cimp.expand_env_esc(src, dst, 256, false, false, NULL)
  242. local len = string.len(ffi.string(dst))
  243. assert.True(len > 56)
  244. assert.True(len < 256)
  245. end)
  246. itp('respects `dstlen` without expansion', function()
  247. local input = to_cstr('this is a very long thing that will not fit')
  248. -- The buffer is long enough to actually contain the full input in case the
  249. -- test fails, but we don't tell expand_env_esc that
  250. local output = cstr(255, '')
  251. cimp.expand_env_esc(input, output, 5, false, true, NULL)
  252. -- Make sure the first few characters are copied properly and that there is a
  253. -- terminating null character
  254. for i=0,3 do
  255. eq(input[i], output[i])
  256. end
  257. eq(0, output[4])
  258. end)
  259. itp('respects `dstlen` with expansion', function()
  260. local varname = to_cstr('NVIM_UNIT_TEST_EXPAND_ENV_ESC_DSTLENN')
  261. local varval = to_cstr('NVIM_UNIT_TEST_EXPAND_ENV_ESC_DSTLENV')
  262. cimp.os_setenv(varname, varval, 1)
  263. -- TODO(bobtwinkles) This test uses unix-specific environment variable accessing,
  264. -- should have some alternative for windows
  265. local input = to_cstr('$NVIM_UNIT_TEST_EXPAND_ENV_ESC_DSTLENN/even more stuff')
  266. -- The buffer is long enough to actually contain the full input in case the
  267. -- test fails, but we don't tell expand_env_esc that
  268. local output = cstr(255, '')
  269. cimp.expand_env_esc(input, output, 5, false, true, NULL)
  270. -- Make sure the first few characters are copied properly and that there is a
  271. -- terminating null character
  272. -- expand_env_esc SHOULD NOT expand the variable if there is not enough space to
  273. -- contain the result
  274. for i=0,3 do
  275. eq(output[i], input[i])
  276. end
  277. eq(output[4], 0)
  278. end)
  279. end)
  280. end)