1" Tests for memory usage. 2 3source check.vim 4CheckFeature terminal 5CheckNotGui 6 7if execute('version') =~# '-fsanitize=[a-z,]*\<address\>' 8 " Skip tests on Travis CI ASAN build because it's difficult to estimate 9 " memory usage. 10 throw 'Skipped: does not work with ASAN' 11endif 12 13source shared.vim 14 15func s:pick_nr(str) abort 16 return substitute(a:str, '[^0-9]', '', 'g') * 1 17endfunc 18 19if has('win32') 20 if !executable('wmic') 21 throw 'Skipped: wmic program missing' 22 endif 23 func s:memory_usage(pid) abort 24 let cmd = printf('wmic process where processid=%d get WorkingSetSize', a:pid) 25 return s:pick_nr(system(cmd)) / 1024 26 endfunc 27elseif has('unix') 28 if !executable('ps') 29 throw 'Skipped: ps program missing' 30 endif 31 func s:memory_usage(pid) abort 32 return s:pick_nr(system('ps -o rss= -p ' . a:pid)) 33 endfunc 34else 35 throw 'Skipped: not win32 or unix' 36endif 37 38" Wait for memory usage to level off. 39func s:monitor_memory_usage(pid) abort 40 let proc = {} 41 let proc.pid = a:pid 42 let proc.hist = [] 43 let proc.max = 0 44 45 func proc.op() abort 46 " Check the last 200ms. 47 let val = s:memory_usage(self.pid) 48 if self.max < val 49 let self.max = val 50 endif 51 call add(self.hist, val) 52 if len(self.hist) < 20 53 return 0 54 endif 55 let sample = remove(self.hist, 0) 56 return len(uniq([sample] + self.hist)) == 1 57 endfunc 58 59 call WaitFor({-> proc.op()}, 10000) 60 return {'last': get(proc.hist, -1), 'max': proc.max} 61endfunc 62 63let s:term_vim = {} 64 65func s:term_vim.start(...) abort 66 let self.buf = term_start([GetVimProg()] + a:000) 67 let self.job = term_getjob(self.buf) 68 call WaitFor({-> job_status(self.job) ==# 'run'}) 69 let self.pid = job_info(self.job).process 70endfunc 71 72func s:term_vim.stop() abort 73 call term_sendkeys(self.buf, ":qall!\<CR>") 74 call WaitFor({-> job_status(self.job) ==# 'dead'}) 75 exe self.buf . 'bwipe!' 76endfunc 77 78func s:vim_new() abort 79 return copy(s:term_vim) 80endfunc 81 82func Test_memory_func_capture_vargs() 83 " Case: if a local variable captures a:000, funccall object will be free 84 " just after it finishes. 85 let testfile = 'Xtest.vim' 86 let lines =<< trim END 87 func s:f(...) 88 let x = a:000 89 endfunc 90 for _ in range(10000) 91 call s:f(0) 92 endfor 93 END 94 call writefile(lines, testfile) 95 96 let vim = s:vim_new() 97 call vim.start('--clean', '-c', 'set noswapfile', testfile) 98 let before = s:monitor_memory_usage(vim.pid).last 99 100 call term_sendkeys(vim.buf, ":so %\<CR>") 101 call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 102 let after = s:monitor_memory_usage(vim.pid) 103 104 " Estimate the limit of max usage as 2x initial usage. 105 " The lower limit can fluctuate a bit, use 97%. 106 call assert_inrange(before * 97 / 100, 2 * before, after.max) 107 108 " In this case, garbage collecting is not needed. 109 " The value might fluctuate a bit, allow for 3% tolerance below and 5% above. 110 " Based on various test runs. 111 let lower = after.last * 97 / 100 112 let upper = after.last * 105 / 100 113 call assert_inrange(lower, upper, after.max) 114 115 call vim.stop() 116 call delete(testfile) 117endfunc 118 119func Test_memory_func_capture_lvars() 120 " Case: if a local variable captures l: dict, funccall object will not be 121 " free until garbage collector runs, but after that memory usage doesn't 122 " increase so much even when rerun Xtest.vim since system memory caches. 123 let testfile = 'Xtest.vim' 124 let lines =<< trim END 125 func s:f() 126 let x = l: 127 endfunc 128 for _ in range(10000) 129 call s:f() 130 endfor 131 END 132 call writefile(lines, testfile) 133 134 let vim = s:vim_new() 135 call vim.start('--clean', '-c', 'set noswapfile', testfile) 136 let before = s:monitor_memory_usage(vim.pid).last 137 138 call term_sendkeys(vim.buf, ":so %\<CR>") 139 call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 140 let after = s:monitor_memory_usage(vim.pid) 141 142 " Rerun Xtest.vim. 143 for _ in range(3) 144 call term_sendkeys(vim.buf, ":so %\<CR>") 145 call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 146 let last = s:monitor_memory_usage(vim.pid).last 147 endfor 148 149 " The usage may be a bit less than the last value, use 80%. 150 " Allow for 20% tolerance at the upper limit. That's very permissive, but 151 " otherwise the test fails sometimes. On Cirrus CI with FreeBSD we need to 152 " be even more permissive. 153 if has('bsd') 154 let multiplier = 15 155 else 156 let multiplier = 12 157 endif 158 let lower = before * 8 / 10 159 let upper = (after.max + (after.last - before)) * multiplier / 10 160 call assert_inrange(lower, upper, last) 161 162 call vim.stop() 163 call delete(testfile) 164endfunc 165