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