Created
July 22, 2012 08:40
-
-
Save kannokanno/3158930 to your computer and use it in GitHub Desktop.
Mock sample for Vim script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function! s:mock(context) | |
let mock = {'context' : a:context, 'instance_map' : {}} | |
function! mock.function(name) | |
let instance = s:mock_instance(self.context, a:name) | |
let self.instance_map[instance.funcname_full] = instance | |
return instance | |
endfunction | |
function! mock.assert() | |
try | |
for v in values(self.instance_map) | |
call v.assert() | |
endfor | |
catch | |
call s:error(v:errmsg) | |
finally | |
unlet g:mock | |
endtry | |
endfunction | |
function! mock.assert_args(key, ...) | |
if has_key(self.instance_map, a:key) | |
call self.instance_map[a:key].assert_args(a:1) | |
endif | |
endfunction | |
function! mock.called(key) | |
if has_key(self.instance_map, a:key) | |
call self.instance_map[a:key].called() | |
endif | |
endfunction | |
" @see s:rewrite_func() | |
" フックする関数にて参照するためグローバルに定義する | |
let g:mock = deepcopy(mock) | |
return g:mock | |
endfunction | |
function! s:mock_instance(context, funcname) | |
let mock = { | |
\ 'context' : a:context, | |
\ 'funcname' : a:funcname, | |
\ 'funcname_full' : s:get_funcname(s:get_func(a:context, a:funcname)), | |
\ '_expect_args' : [], | |
\ '_called_actual' : 0, | |
\ '_called_expected' : 0, | |
\} | |
let mock['_original_arg_define'] = s:record_original_arg_define(s:get_funcbody(mock.funcname_full)) | |
let mock['_original_body_define'] = s:record_original_body_define(s:get_funcbody(mock.funcname_full)) | |
let mock['_rewrite_value'] = "\n" . mock._original_body_define | |
function! mock.once() | |
let self._called_expected = 1 | |
call s:rewrite_func(self) | |
return self | |
endfunction | |
function! mock.any() | |
let self._called_expected = -1 | |
call s:rewrite_func(self) | |
return self | |
endfunction | |
function! mock.with(...) | |
let self._expect_args = len(a:000) == 0 ? [] : a:000 | |
return self | |
endfunction | |
function! mock.return(value) abort | |
let self._rewrite_value = (type(a:value) == type('') && !empty(a:value)) ? "\nreturn '" . a:value . "'" : a:value | |
return self | |
endfunction | |
function! mock.throw(exception) | |
let self._rewrite_value = printf("\nthrow '%s'", a:exception) | |
return self | |
endfunction | |
function! mock.callback(func) abort | |
let callback = printf("\nreturn call('%s', a:000)", s:get_funcname(s:get_func(self.context, a:func))) | |
let self._rewrite_value = callback | |
return self | |
endfunction | |
function! mock.assert() | |
try | |
if self._called_expected != -1 | |
if self._called_actual != self._called_expected | |
call s:error(printf('Function %s called expected %s but was %s', self.funcname, self._called_expected, self._called_actual)) | |
endif | |
endif | |
finally | |
exec printf("function! %s(%s)\n%s\nendfunction", self.funcname_full, self._original_arg_define, self._original_body_define) | |
endtry | |
endfunction | |
function! mock.assert_args(...) | |
for i in range(len(self._expect_args)) | |
let expected = self._expect_args[i] | |
let actual = a:1[i] | |
try | |
if actual != expected | |
call s:error(printf('Function %s args expected %s but was %s', self.funcname, string(self._expect_args), string(a:1))) | |
endif | |
finally | |
unlet expected | |
unlet actual | |
endtry | |
endfor | |
endfunction | |
function! mock.called() | |
let self._called_actual += 1 | |
return self | |
endfunction | |
return mock | |
endfunction | |
" @see http://mattn.kaoriya.net/software/vim/20090826003359.htm | |
function! s:get_sid(context) | |
let out = '' | |
redir => out | |
silent! scriptnames | |
redir END | |
let sid_map = {} | |
let pattern = '^\s*\(\d\+\):\s*\(.*\)$' | |
for line in split(out, "\n") | |
let sid_map[tolower(expand(substitute(line, pattern, '\2', '')))] = substitute(line, pattern, '\1', '') | |
endfor | |
return sid_map[tolower(a:context)] | |
endfunction | |
function! s:get_func(context, funcname) | |
return function(printf('<SNR>%s_%s', s:get_sid(a:context), a:funcname)) | |
endfunction | |
function! s:get_funcname(func) | |
return type(a:func) == type(function('tr')) | |
\ ? substitute(string(a:func), "^function('\\(.*\\)')$", '\1', '') | |
\ : a:func | |
endfunction | |
function! s:get_funcbody(funcname) | |
let out = '' | |
redir => out | |
silent! exec 'function '.a:funcname | |
redir END | |
return split(out, '\n') | |
endfunction | |
function! s:build_func_define(name, args, body) | |
return printf("function! %s(%s)\n%s\nendfunction", a:name, a:args, a:body) | |
endfunction | |
function! s:rewrite_func(mock) | |
let key = a:mock.funcname_full | |
let args = map(split(a:mock._original_arg_define, ',\s*'), "'a:'.v:val") | |
let body = printf("call g:mock.called('%s')\ncall g:mock.assert_args('%s', [%s])%s", key, key, join(args, ','), a:mock._rewrite_value) | |
let define = s:build_func_define(s:get_funcname(s:get_func(a:mock.context, a:mock.funcname)), a:mock._original_arg_define, body) | |
execute define | |
endfunction | |
function! s:record_original_arg_define(lines) | |
return substitute(a:lines[0], ".*function.*(\\(.*\\))", '\1', '') | |
endfunction | |
function! s:record_original_body_define(lines) | |
return join(map(a:lines[1:len(a:lines) - 2], 'substitute(v:val, "^[0-9]*", "", "")'), "\n") | |
endfunction | |
function! s:error(msg) | |
echohl Error | |
echo a:msg | |
echohl NONE | |
endfunction | |
" ------------------ | |
" use sample | |
" sample function | |
function! s:echo() | |
echo 100 | |
endfunction | |
function! s:hello(text1, text2) | |
return 'Hello, ' . a:text1 . ' and ' . a:text2 | |
endfunction | |
" -ケース1:引数や戻り値は特にいじらず、一度だけ呼ばれることを検証する | |
echo 'case:1-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('echo').once() | |
call s:echo() | |
call s:mock.assert() | |
unlet s:mock | |
echo '-------------------' | |
" -ケース2:実行された際の処理をフックし、一度だけ呼ばれることを検証する | |
function! s:echo_callback() | |
echo 'callback dayo' | |
endfunction | |
echo 'case:2-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('echo').callback('echo_callback').once() | |
call s:echo() | |
" => callback dayo | |
call s:mock.assert() | |
call s:echo() | |
" => 100 | |
unlet s:mock | |
echo '-------------------' | |
" -ケース3:例外を発生させるようにし、一度だけ呼ばれることを検証する | |
echo 'case:3-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('echo').throw('booo').once() | |
try | |
call s:echo() | |
catch /booo/ | |
echo 'error dayo' | |
endtry | |
call s:mock.assert() | |
call s:echo() | |
" => 100 | |
unlet s:mock | |
echo '-------------------' | |
" -ケース4:引数の検証をし、一度だけ呼ばれることを検証する | |
echo 'case:4-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('hello').with('World', 'Vim').once() | |
echo s:hello('World', 'Vim') | |
"echo s:hello('World', 'Emacs') | |
" => Function hello args expected ['World', 'Vim'] but was ['World', 'Emacs'] | |
call s:mock.assert() | |
unlet s:mock | |
echo '-------------------' | |
" -ケース5:戻り値をフックし、一度だけ呼ばれることを検証する | |
echo 'case:5-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('hello').return('hogehoge').once() | |
echo s:hello('World', 'Vim') | |
" => hogehoge | |
call s:mock.assert() | |
echo s:hello('World', 'Vim') | |
" => Hello, World and Vim | |
unlet s:mock | |
echo '-------------------' | |
" -ケース6:引数の検証と戻り値をフックし、一度だけ呼ばれることを検証する | |
echo 'case:6-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('hello').with('World', 'Vim').return('hogehoge').once() | |
echo s:hello('World', 'Vim') | |
" => hogehoge | |
call s:mock.assert() | |
echo s:hello('World', 'Vim') | |
" => Hello, World and Vim | |
unlet s:mock | |
echo '-------------------' | |
" -ケース7:引数の検証と戻り値をフックする。呼び出し回数は検証しない | |
echo 'case:7-------------' | |
let s:mock = s:mock(expand('%:p')) | |
call s:mock.function('hello').with('World', 'Vim').return('hogehoge').any() | |
echo s:hello('World', 'Vim') | |
" => hogehoge | |
echo s:hello('World', 'Vim') | |
" => hogehoge | |
echo s:hello('World', 'Vim') | |
" => hogehoge | |
call s:mock.assert() | |
echo s:hello('World', 'Vim') | |
" => Hello, World and Vim | |
unlet s:mock | |
echo '-------------------' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment