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.min = 0 40 let proc.max = 0 41 42 func proc.op() abort 43 " Check the last 200ms. 44 let val = s:memory_usage(self.pid) 45 if self.min > val 46 let self.min = val 47 elseif self.max < val 48 let self.max = val 49 endif 50 call add(self.hist, val) 51 if len(self.hist) < 20 52 return 0 53 endif 54 let sample = remove(self.hist, 0) 55 return len(uniq([sample] + self.hist)) == 1 56 endfunc 57 58 call WaitFor({-> proc.op()}, 10000) 59 return {'last': get(proc.hist, -1), 'min': proc.min, 'max': proc.max} 60endfunc 61 62let s:term_vim = {} 63 64func s:term_vim.start(...) abort 65 let self.buf = term_start([GetVimProg()] + a:000) 66 let self.job = term_getjob(self.buf) 67 call WaitFor({-> job_status(self.job) ==# 'run'}) 68 let self.pid = job_info(self.job).process 69endfunc 70 71func s:term_vim.stop() abort 72 call term_sendkeys(self.buf, ":qall!\<CR>") 73 call WaitFor({-> job_status(self.job) ==# 'dead'}) 74 exe self.buf . 'bwipe!' 75endfunc 76 77func s:vim_new() abort 78 return copy(s:term_vim) 79endfunc 80 81func Test_memory_func_capture_vargs() 82 " Case: if a local variable captures a:000, funccall object will be free 83 " just after it finishes. 84 let testfile = 'Xtest.vim' 85 call writefile([ 86 \ 'func s:f(...)', 87 \ ' let x = a:000', 88 \ 'endfunc', 89 \ 'for _ in range(10000)', 90 \ ' call s:f(0)', 91 \ 'endfor', 92 \ ], 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 call assert_inrange(before, 2 * before, after.max) 104 " In this case, garbase collecting is not needed. 105 call assert_equal(after.last, after.max) 106 107 call vim.stop() 108 call delete(testfile) 109endfunc 110 111func Test_memory_func_capture_lvars() 112 " Case: if a local variable captures l: dict, funccall object will not be 113 " free until garbage collector runs, but after that memory usage doesn't 114 " increase so much even when rerun Xtest.vim since system memory caches. 115 let testfile = 'Xtest.vim' 116 call writefile([ 117 \ 'func s:f()', 118 \ ' let x = l:', 119 \ 'endfunc', 120 \ 'for _ in range(10000)', 121 \ ' call s:f()', 122 \ 'endfor', 123 \ ], testfile) 124 125 let vim = s:vim_new() 126 call vim.start('--clean', '-c', 'set noswapfile', testfile) 127 let before = s:monitor_memory_usage(vim.pid).last 128 129 call term_sendkeys(vim.buf, ":so %\<CR>") 130 call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 131 let after = s:monitor_memory_usage(vim.pid) 132 133 " Rerun Xtest.vim. 134 for _ in range(3) 135 call term_sendkeys(vim.buf, ":so %\<CR>") 136 call WaitFor({-> term_getcursor(vim.buf)[0] == 1}) 137 let last = s:monitor_memory_usage(vim.pid).last 138 endfor 139 140 " The usage may be a bit less than the last value 141 let lower = before * 8 / 10 142 call assert_inrange(lower, after.max + (after.last - before), last) 143 144 call vim.stop() 145 call delete(testfile) 146endfunc 147