" Author: w0rp " Description: Functions for making testing ALE easier. " " This file should not typically be loaded during the normal execution of ALE. " Change the directory for checking things in particular test directories " " This function will set the g:dir variable, which represents the working " directory after changing the path. This variable allows a test to change " directories, and then switch back to a directory at the start of the test " run. " " This function should be run in a Vader Before: block. function! ale#test#SetDirectory(docker_path) abort if a:docker_path[:len('/testplugin/') - 1] isnot# '/testplugin/' throw 'docker_path must start with /testplugin/!' endif " Try to switch directory, which will fail when running tests directly, " and not through the Docker image. silent! execute 'cd ' . fnameescape(a:docker_path) let g:dir = getcwd() " no-custom-checks endfunction " When g:dir is defined, switch back to the directory we saved, and then " delete that variable. " " The filename will be reset to dummy.txt " " This function should be run in a Vader After: block. function! ale#test#RestoreDirectory() abort call ale#test#SetFilename('dummy.txt') silent execute 'cd ' . fnameescape(g:dir) unlet! g:dir endfunction " Get a filename for the current buffer using a relative path to the script. " " If a g:dir variable is set, it will be used as the path to the directory " containing the test file. function! ale#test#GetFilename(path) abort let l:dir = get(g:, 'dir', '') if empty(l:dir) let l:dir = getcwd() " no-custom-checks endif let l:full_path = ale#path#IsAbsolute(a:path) \ ? a:path \ : l:dir . '/' . a:path return ale#path#Simplify(l:full_path) endfunction " Change the filename for the current buffer using a relative path to " the script without running autocmd commands. " " If a g:dir variable is set, it will be used as the path to the directory " containing the test file. function! ale#test#SetFilename(path) abort let l:full_path = ale#test#GetFilename(a:path) silent! noautocmd execute 'file ' . fnameescape(l:full_path) endfunction function! RemoveNewerKeys(results) abort for l:item in a:results if has_key(l:item, 'module') call remove(l:item, 'module') endif if has_key(l:item, 'end_col') call remove(l:item, 'end_col') endif if has_key(l:item, 'end_lnum') call remove(l:item, 'end_lnum') endif endfor endfunction " Return loclist data with only the keys supported by the lowest Vim versions. function! ale#test#GetLoclistWithoutNewerKeys() abort let l:results = getloclist(0) call RemoveNewerKeys(l:results) return l:results endfunction " Return quickfix data with only the keys supported by the lowest Vim versions. function! ale#test#GetQflistWithoutNewerKeys() abort let l:results = getqflist() call RemoveNewerKeys(l:results) return l:results endfunction function! ale#test#GetPreviewWindowText() abort for l:window in range(1, winnr('$')) if getwinvar(l:window, '&previewwindow', 0) let l:buffer = winbufnr(l:window) return getbufline(l:buffer, 1, '$') endif endfor endfunction " This function can be called with a timeout to wait for all jobs to finish. " If the jobs to not finish in the given number of milliseconds, " an exception will be thrown. " " The time taken will be a very rough approximation, and more time may be " permitted than is specified. function! ale#test#WaitForJobs(deadline) abort let l:start_time = ale#events#ClockMilliseconds() if l:start_time == 0 throw 'Failed to read milliseconds from the clock!' endif let l:job_list = [] " Gather all of the jobs from every buffer. for [l:buffer, l:data] in items(ale#command#GetData()) call extend(l:job_list, map(keys(l:data.jobs), 'str2nr(v:val)')) endfor " NeoVim has a built-in API for this, so use that. if has('nvim') let l:nvim_code_list = jobwait(l:job_list, a:deadline) if index(l:nvim_code_list, -1) >= 0 throw 'Jobs did not complete on time!' endif return endif let l:should_wait_more = 1 while l:should_wait_more let l:should_wait_more = 0 for l:job_id in l:job_list if ale#job#IsRunning(l:job_id) let l:now = ale#events#ClockMilliseconds() if l:now - l:start_time > a:deadline " Stop waiting after a timeout, so we don't wait forever. throw 'Jobs did not complete on time!' endif " Wait another 10 milliseconds let l:should_wait_more = 1 sleep 10ms break endif endfor endwhile " Sleep for a small amount of time after all jobs finish. " This seems to be enough to let handlers after jobs end run, and " prevents the occasional failure where this function exits after jobs " end, but before handlers are run. sleep 10ms " We must check the buffer data again to see if new jobs started for " linters with chained commands. let l:has_new_jobs = 0 " Check again to see if any jobs are running. for l:info in values(g:ale_buffer_info) for [l:job_id, l:linter] in get(l:info, 'job_list', []) if ale#job#IsRunning(l:job_id) let l:has_new_jobs = 1 break endif endfor endfor if l:has_new_jobs " We have to wait more. Offset the timeout by the time taken so far. let l:now = ale#events#ClockMilliseconds() let l:new_deadline = a:deadline - (l:now - l:start_time) if l:new_deadline <= 0 " Enough time passed already, so stop immediately. throw 'Jobs did not complete on time!' endif call ale#test#WaitForJobs(l:new_deadline) endif endfunction function! ale#test#FlushJobs() abort " The variable is checked for in a loop, as calling one series of " callbacks can trigger a further series of callbacks. while exists('g:ale_run_synchronously_callbacks') let l:callbacks = g:ale_run_synchronously_callbacks unlet g:ale_run_synchronously_callbacks for l:Callback in l:callbacks call l:Callback() endfor endwhile endfunction