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