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 call writefile([ 89 \ 'func s:f(...)', 90 \ ' let x = a:000', 91 \ 'endfunc', 92 \ 'for _ in range(10000)', 93 \ ' call s:f(0)', 94 \ 'endfor', 95 \ ], testfile) 96 97 let vim = s:vim_new() 98 call vim.start('--clean', '-c', 'set noswapfile', testfile) 99 let before = s:monitor_memory_usage(vim.pid).last 100 101 call term_sendkeys(vim.buf, ":so %\<CR>") 102 call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 103 let after = s:monitor_memory_usage(vim.pid) 104 105 " Estimate the limit of max usage as 2x initial usage. 106 " The lower limit can fluctuate a bit, use 97%. 107 call assert_inrange(before * 97 / 100, 2 * before, after.max) 108 109 " In this case, garbage collecting is not needed. 110 " The value might fluctuate a bit, allow for 3% tolerance below and 5% above. 111 " Based on various test runs. 112 let lower = after.last * 97 / 100 113 let upper = after.last * 105 / 100 114 call assert_inrange(lower, upper, after.max) 115 116 call vim.stop() 117 call delete(testfile) 118endfunc 119 120func Test_memory_func_capture_lvars() 121 " Case: if a local variable captures l: dict, funccall object will not be 122 " free until garbage collector runs, but after that memory usage doesn't 123 " increase so much even when rerun Xtest.vim since system memory caches. 124 let testfile = 'Xtest.vim' 125 call writefile([ 126 \ 'func s:f()', 127 \ ' let x = l:', 128 \ 'endfunc', 129 \ 'for _ in range(10000)', 130 \ ' call s:f()', 131 \ 'endfor', 132 \ ], 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. 152 let lower = before * 8 / 10 153 let upper = (after.max + (after.last - before)) * 12 / 10 154 call assert_inrange(lower, upper, last) 155 156 call vim.stop() 157 call delete(testfile) 158endfunc 159