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