2019-09-01 02:44:14 -07:00
|
|
|
local helpers = require('test.functional.helpers')(after_each)
|
|
|
|
local clear = helpers.clear
|
|
|
|
local eval = helpers.eval
|
|
|
|
local eq = helpers.eq
|
|
|
|
local feed_command = helpers.feed_command
|
|
|
|
local iswin = helpers.iswin
|
|
|
|
local retry = helpers.retry
|
|
|
|
local ok = helpers.ok
|
|
|
|
local source = helpers.source
|
2020-10-19 11:17:51 -07:00
|
|
|
local poke_eventloop = helpers.poke_eventloop
|
2020-07-18 19:15:54 -07:00
|
|
|
local uname = helpers.uname
|
|
|
|
local load_adjust = helpers.load_adjust
|
2021-01-01 13:07:42 -07:00
|
|
|
local isCI = helpers.isCI
|
|
|
|
|
|
|
|
local function isasan()
|
|
|
|
local version = eval('execute("version")')
|
|
|
|
return version:match('-fsanitize=[a-z,]*address')
|
|
|
|
end
|
|
|
|
|
|
|
|
clear()
|
|
|
|
if isasan() then
|
|
|
|
pending('ASAN build is difficult to estimate memory usage', function() end)
|
|
|
|
return
|
|
|
|
elseif iswin() then
|
|
|
|
if isCI('github') then
|
|
|
|
pending('Windows runners in Github Actions do not have a stable environment to estimate memory usage', function() end)
|
|
|
|
return
|
|
|
|
elseif eval("executable('wmic')") == 0 then
|
|
|
|
pending('missing "wmic" command', function() end)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
elseif eval("executable('ps')") == 0 then
|
|
|
|
pending('missing "ps" command', function() end)
|
|
|
|
return
|
|
|
|
end
|
2019-09-01 02:44:14 -07:00
|
|
|
|
|
|
|
local monitor_memory_usage = {
|
|
|
|
memory_usage = function(self)
|
|
|
|
local handle
|
|
|
|
if iswin() then
|
|
|
|
handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize')
|
|
|
|
else
|
|
|
|
handle = io.popen('ps -o rss= -p '..self.pid)
|
|
|
|
end
|
|
|
|
return tonumber(handle:read('*a'):match('%d+'))
|
|
|
|
end,
|
|
|
|
op = function(self)
|
|
|
|
retry(nil, 10000, function()
|
|
|
|
local val = self.memory_usage(self)
|
2019-09-05 08:22:10 -07:00
|
|
|
if self.max < val then
|
2019-09-01 02:44:14 -07:00
|
|
|
self.max = val
|
|
|
|
end
|
|
|
|
table.insert(self.hist, val)
|
|
|
|
ok(#self.hist > 20)
|
|
|
|
local result = {}
|
|
|
|
for key,value in ipairs(self.hist) do
|
|
|
|
if value ~= self.hist[key + 1] then
|
|
|
|
table.insert(result, value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
table.remove(self.hist, 1)
|
|
|
|
self.last = self.hist[#self.hist]
|
|
|
|
eq(#result, 1)
|
|
|
|
end)
|
|
|
|
end,
|
|
|
|
dump = function(self)
|
|
|
|
return 'max: '..self.max ..', last: '..self.last
|
|
|
|
end,
|
|
|
|
monitor_memory_usage = function(self, pid)
|
|
|
|
local obj = {
|
|
|
|
pid = pid,
|
|
|
|
max = 0,
|
|
|
|
last = 0,
|
|
|
|
hist = {},
|
|
|
|
}
|
|
|
|
setmetatable(obj, { __index = self })
|
|
|
|
obj:op()
|
|
|
|
return obj
|
|
|
|
end
|
|
|
|
}
|
|
|
|
setmetatable(monitor_memory_usage,
|
|
|
|
{__call = function(self, pid)
|
|
|
|
return monitor_memory_usage.monitor_memory_usage(self, pid)
|
|
|
|
end})
|
|
|
|
|
|
|
|
describe('memory usage', function()
|
|
|
|
local function check_result(tbl, status, result)
|
|
|
|
if not status then
|
|
|
|
print('')
|
|
|
|
for key, val in pairs(tbl) do
|
|
|
|
print(key, val:dump())
|
|
|
|
end
|
|
|
|
error(result)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
before_each(clear)
|
|
|
|
|
|
|
|
--[[
|
|
|
|
Case: if a local variable captures a:000, funccall object will be free
|
|
|
|
just after it finishes.
|
|
|
|
]]--
|
|
|
|
it('function capture vargs', function()
|
|
|
|
local pid = eval('getpid()')
|
|
|
|
local before = monitor_memory_usage(pid)
|
|
|
|
source([[
|
|
|
|
func s:f(...)
|
|
|
|
let x = a:000
|
|
|
|
endfunc
|
|
|
|
for _ in range(10000)
|
|
|
|
call s:f(0)
|
|
|
|
endfor
|
|
|
|
]])
|
2020-10-19 11:17:51 -07:00
|
|
|
poke_eventloop()
|
2019-09-01 02:44:14 -07:00
|
|
|
local after = monitor_memory_usage(pid)
|
|
|
|
-- Estimate the limit of max usage as 2x initial usage.
|
2019-09-05 08:37:03 -07:00
|
|
|
-- The lower limit can fluctuate a bit, use 97%.
|
2019-09-05 08:32:43 -07:00
|
|
|
check_result({before=before, after=after},
|
2019-09-05 08:37:03 -07:00
|
|
|
pcall(ok, before.last * 97 / 100 < after.max))
|
2019-09-05 08:32:43 -07:00
|
|
|
check_result({before=before, after=after},
|
|
|
|
pcall(ok, before.last * 2 > after.max))
|
2019-09-05 08:22:10 -07:00
|
|
|
-- In this case, garbage collecting is not needed.
|
2019-09-05 08:37:03 -07:00
|
|
|
-- The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
|
|
|
|
-- Based on various test runs.
|
2019-09-05 08:22:10 -07:00
|
|
|
local lower = after.last * 97 / 100
|
2019-09-05 08:37:03 -07:00
|
|
|
local upper = after.last * 105 / 100
|
2019-09-05 08:22:10 -07:00
|
|
|
check_result({before=before, after=after}, pcall(ok, lower < after.max))
|
|
|
|
check_result({before=before, after=after}, pcall(ok, after.max < upper))
|
2019-09-01 02:44:14 -07:00
|
|
|
end)
|
|
|
|
|
|
|
|
--[[
|
|
|
|
Case: if a local variable captures l: dict, funccall object will not be
|
|
|
|
free until garbage collector runs, but after that memory usage doesn't
|
|
|
|
increase so much even when rerun Xtest.vim since system memory caches.
|
|
|
|
]]--
|
|
|
|
it('function capture lvars', function()
|
|
|
|
local pid = eval('getpid()')
|
|
|
|
local before = monitor_memory_usage(pid)
|
|
|
|
local fname = source([[
|
|
|
|
if !exists('s:defined_func')
|
|
|
|
func s:f()
|
|
|
|
let x = l:
|
|
|
|
endfunc
|
|
|
|
endif
|
|
|
|
let s:defined_func = 1
|
|
|
|
for _ in range(10000)
|
|
|
|
call s:f()
|
|
|
|
endfor
|
|
|
|
]])
|
2020-10-19 11:17:51 -07:00
|
|
|
poke_eventloop()
|
2019-09-01 02:44:14 -07:00
|
|
|
local after = monitor_memory_usage(pid)
|
|
|
|
for _ = 1, 3 do
|
|
|
|
feed_command('so '..fname)
|
2020-10-19 11:17:51 -07:00
|
|
|
poke_eventloop()
|
2019-09-01 02:44:14 -07:00
|
|
|
end
|
|
|
|
local last = monitor_memory_usage(pid)
|
2019-09-05 08:22:10 -07:00
|
|
|
-- The usage may be a bit less than the last value, use 80%.
|
2019-09-05 08:30:30 -07:00
|
|
|
-- Allow for 20% tolerance at the upper limit. That's very permissive, but
|
2020-07-18 19:15:54 -07:00
|
|
|
-- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to
|
2021-03-13 18:02:58 -07:00
|
|
|
-- be even much more permissive.
|
|
|
|
local upper_multiplier = uname() == 'freebsd' and 19 or 12
|
2019-09-05 08:19:02 -07:00
|
|
|
local lower = before.last * 8 / 10
|
2020-07-18 19:15:54 -07:00
|
|
|
local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10)
|
2019-09-01 02:44:14 -07:00
|
|
|
check_result({before=before, after=after, last=last},
|
2019-09-05 08:19:02 -07:00
|
|
|
pcall(ok, lower < last.last))
|
2019-09-01 02:44:14 -07:00
|
|
|
check_result({before=before, after=after, last=last},
|
2019-09-05 08:22:10 -07:00
|
|
|
pcall(ok, last.last < upper))
|
2019-09-01 02:44:14 -07:00
|
|
|
end)
|
2021-06-30 18:24:50 -07:00
|
|
|
|
|
|
|
it('releases memory when closing windows when folds exist', function()
|
2021-07-10 20:15:38 -07:00
|
|
|
if helpers.is_os('mac') then
|
|
|
|
pending('macOS memory compression causes flakiness')
|
|
|
|
end
|
2021-06-30 18:24:50 -07:00
|
|
|
local pid = eval('getpid()')
|
|
|
|
source([[
|
|
|
|
new
|
|
|
|
" Insert lines
|
|
|
|
call nvim_buf_set_lines(0, 0, 0, v:false, repeat([''], 999))
|
|
|
|
" Create folds
|
|
|
|
normal! gg
|
|
|
|
for _ in range(500)
|
|
|
|
normal! zfjj
|
|
|
|
endfor
|
|
|
|
]])
|
|
|
|
poke_eventloop()
|
|
|
|
local before = monitor_memory_usage(pid)
|
|
|
|
source([[
|
|
|
|
" Split and close window multiple times
|
|
|
|
for _ in range(1000)
|
|
|
|
split
|
|
|
|
close
|
|
|
|
endfor
|
|
|
|
]])
|
|
|
|
poke_eventloop()
|
|
|
|
local after = monitor_memory_usage(pid)
|
|
|
|
source('bwipe!')
|
|
|
|
poke_eventloop()
|
|
|
|
-- Allow for an increase of 5% in memory usage, which accommodates minor fluctuation,
|
|
|
|
-- but is small enough that if memory were not released (prior to PR #14884), the test
|
|
|
|
-- would fail.
|
|
|
|
local upper = before.last * 1.05
|
|
|
|
check_result({before=before, after=after}, pcall(ok, after.last <= upper))
|
|
|
|
end)
|
2019-09-01 02:44:14 -07:00
|
|
|
end)
|