vim-patch:7.4.1727

Problem:    Cannot detect a crash in tests when caused by garbagecollect().
Solution:   Add garbagecollect_for_testing().  Do not free a job if is still
            useful.

ebf7dfa6f1
This commit is contained in:
Michael Ennen 2017-01-05 17:13:23 -07:00
parent 64c375c589
commit b0fc6108c9
9 changed files with 373 additions and 23 deletions

View File

@ -1791,6 +1791,9 @@ v:termresponse The escape sequence returned by the terminal for the |t_RV|
always 95 or bigger). Pc is always zero. always 95 or bigger). Pc is always zero.
{only when compiled with |+termresponse| feature} {only when compiled with |+termresponse| feature}
*v:testing* *testing-variable*
v:testing Must be set before using `test_garbagecollect_now()`.
*v:this_session* *this_session-variable* *v:this_session* *this_session-variable*
v:this_session Full filename of the last loaded or saved session file. See v:this_session Full filename of the last loaded or saved session file. See
|:mksession|. It is allowed to set this variable. When no |:mksession|. It is allowed to set this variable. When no
@ -2215,6 +2218,7 @@ tagfiles() List tags files used
tan({expr}) Float tangent of {expr} tan({expr}) Float tangent of {expr}
tanh({expr}) Float hyperbolic tangent of {expr} tanh({expr}) Float hyperbolic tangent of {expr}
tempname() String name for a temporary file tempname() String name for a temporary file
test_garbagecollect_now() none free memory right now for testing
timer_start({time}, {callback} [, {options}]) timer_start({time}, {callback} [, {options}])
Number create a timer Number create a timer
timer_stop({timer}) none stop a timer timer_stop({timer}) none stop a timer
@ -3589,18 +3593,25 @@ function({name} [, {arglist}] [, {dict}])
garbagecollect([{atexit}]) *garbagecollect()* garbagecollect([{atexit}]) *garbagecollect()*
Cleanup unused |Lists| and |Dictionaries| that have circular Cleanup unused |Lists| and |Dictionaries| that have circular
references. There is hardly ever a need to invoke this references.
function, as it is automatically done when Vim runs out of
memory or is waiting for the user to press a key after There is hardly ever a need to invoke this function, as it is
'updatetime'. Items without circular references are always automatically done when Vim runs out of memory or is waiting
freed when they become unused. for the user to press a key after 'updatetime'. Items without
circular references are always freed when they become unused.
This is useful if you have deleted a very big |List| and/or This is useful if you have deleted a very big |List| and/or
|Dictionary| with circular references in a script that runs |Dictionary| with circular references in a script that runs
for a long time. for a long time.
When the optional {atexit} argument is one, garbage When the optional {atexit} argument is one, garbage
collection will also be done when exiting Vim, if it wasn't collection will also be done when exiting Vim, if it wasn't
done before. This is useful when checking for memory leaks. done before. This is useful when checking for memory leaks.
The garbage collection is not done immediately but only when
it's safe to perform. This is when waiting for the user to
type a character. To force garbage collection immediately use
|test_garbagecollect_now()|.
get({list}, {idx} [, {default}]) *get()* get({list}, {idx} [, {default}]) *get()*
Get item {idx} from |List| {list}. When this item is not Get item {idx} from |List| {list}. When this item is not
available return {default}. Return zero when {default} is available return {default}. Return zero when {default} is
@ -7288,6 +7299,12 @@ termopen({cmd}[, {opts}]) {Nvim} *termopen()*
See |terminal-emulator| for more information. See |terminal-emulator| for more information.
test_garbagecollect_now() *test_garbagecollect_now()*
Like garbagecollect(), but executed right away. This must
only be called directly to avoid any structure to exist
internally, and |v:testing| must have been set before calling
any function.
tan({expr}) *tan()* tan({expr}) *tan()*
Return the tangent of {expr}, measured in radians, as a |Float| Return the tangent of {expr}, measured in radians, as a |Float|
in the range [-inf, inf]. in the range [-inf, inf].

View File

@ -389,6 +389,7 @@ static struct vimvar {
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO),
VV(VV_TESTING, "testing", VAR_NUMBER, 0),
VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO),
VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO),
@ -648,8 +649,8 @@ void eval_clear(void)
xfree(SCRIPT_SV(i)); xfree(SCRIPT_SV(i));
ga_clear(&ga_scripts); ga_clear(&ga_scripts);
/* unreferenced lists and dicts */ // unreferenced lists and dicts
(void)garbage_collect(); (void)garbage_collect(false);
/* functions */ /* functions */
free_all_functions(); free_all_functions();
@ -5813,6 +5814,9 @@ int get_copyID(void)
return current_copyID; return current_copyID;
} }
// Used by get_func_tv()
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
/* /*
* Garbage collection for lists and dictionaries. * Garbage collection for lists and dictionaries.
* *
@ -5835,16 +5839,19 @@ int get_copyID(void)
/// Do garbage collection for lists and dicts. /// Do garbage collection for lists and dicts.
/// ///
/// @param testing true if called from test_garbagecollect_now().
/// @returns true if some memory was freed. /// @returns true if some memory was freed.
bool garbage_collect(void) bool garbage_collect(bool testing)
{ {
bool abort = false; bool abort = false;
#define ABORTING(func) abort = abort || func #define ABORTING(func) abort = abort || func
// Only do this once. if (!testing) {
want_garbage_collect = false; // Only do this once.
may_garbage_collect = false; want_garbage_collect = false;
garbage_collect_at_exit = false; may_garbage_collect = false;
garbage_collect_at_exit = false;
}
// We advance by two because we add one for items referenced through // We advance by two because we add one for items referenced through
// previous_funccal. // previous_funccal.
@ -5954,6 +5961,12 @@ bool garbage_collect(void)
}) })
} }
// function call arguments, if v:testing is set.
for (int i = 0; i < funcargs.ga_len; i++) {
ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i],
copyID, NULL, NULL);
}
// v: vars // v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL); ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
@ -6008,7 +6021,7 @@ bool garbage_collect(void)
if (did_free_funccal) { if (did_free_funccal) {
// When a funccal was freed some more items might be garbage // When a funccal was freed some more items might be garbage
// collected, so run again. // collected, so run again.
(void)garbage_collect(); (void)garbage_collect(testing);
} }
} else if (p_verbose > 0) { } else if (p_verbose > 0) {
verb_msg((char_u *)_( verb_msg((char_u *)_(
@ -7085,9 +7098,24 @@ get_func_tv (
ret = FAIL; ret = FAIL;
if (ret == OK) { if (ret == OK) {
ret = call_func(name, len, rettv, argcount, argvars, int i = 0;
if (get_vim_var_nr(VV_TESTING)) {
// Prepare for calling garbagecollect_for_testing(), need to know
// what variables are used on the call stack.
if (funcargs.ga_itemsize == 0) {
ga_init(&funcargs, (int)sizeof(typval_T *), 50);
}
for (i = 0; i < argcount; i++) {
ga_grow(&funcargs, 1);
((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
}
}
ret = call_func(name, len, rettv, argcount, argvars, NULL,
firstline, lastline, doesrange, evaluate, firstline, lastline, doesrange, evaluate,
partial, selfdict); partial, selfdict);
funcargs.ga_len -= i;
} else if (!aborting()) { } else if (!aborting()) {
if (argcount == MAX_FUNC_ARGS) { if (argcount == MAX_FUNC_ARGS) {
emsg_funcname(N_("E740: Too many arguments for function %s"), name); emsg_funcname(N_("E740: Too many arguments for function %s"), name);
@ -17306,6 +17334,14 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return; return;
} }
// "test_garbagecollect_now()" function
static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
// This is dangerous, any Lists and Dicts used internally may be freed
// while still in use.
garbage_collect(true);
}
static bool callback_from_typval(Callback *callback, typval_T *arg) static bool callback_from_typval(Callback *callback, typval_T *arg)
{ {
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) { if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {

View File

@ -127,6 +127,7 @@ typedef enum {
VV__NULL_LIST, // List with NULL value. For test purposes only. VV__NULL_LIST, // List with NULL value. For test purposes only.
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV_VIM_DID_ENTER, VV_VIM_DID_ENTER,
VV_TESTING,
VV_TYPE_NUMBER, VV_TYPE_NUMBER,
VV_TYPE_STRING, VV_TYPE_STRING,
VV_TYPE_FUNC, VV_TYPE_FUNC,

View File

@ -303,6 +303,7 @@ return {
tanh={args=1, func="float_op_wrapper", data="&tanh"}, tanh={args=1, func="float_op_wrapper", data="&tanh"},
tempname={}, tempname={},
termopen={args={1, 2}}, termopen={args={1, 2}},
test_garbagecollect_now={},
timer_start={args={2,3}}, timer_start={args={2,3}},
timer_stop={args=1}, timer_stop={args=1},
tolower={args=1}, tolower={args=1},

View File

@ -1327,8 +1327,9 @@ int using_script(void)
void before_blocking(void) void before_blocking(void)
{ {
updatescript(0); updatescript(0);
if (may_garbage_collect) if (may_garbage_collect) {
garbage_collect(); garbage_collect(false);
}
} }
/* /*
@ -1366,10 +1367,11 @@ int vgetc(void)
char_u buf[MB_MAXBYTES + 1]; char_u buf[MB_MAXBYTES + 1];
int i; int i;
/* Do garbage collection when garbagecollect() was called previously and // Do garbage collection when garbagecollect() was called previously and
* we are now at the toplevel. */ // we are now at the toplevel.
if (may_garbage_collect && want_garbage_collect) if (may_garbage_collect && want_garbage_collect) {
garbage_collect(); garbage_collect(false);
}
/* /*
* If a character was put back with vungetc, it was already processed. * If a character was put back with vungetc, it was already processed.

View File

@ -624,8 +624,9 @@ void getout(int exitval)
iconv_end(); iconv_end();
#endif #endif
cs_end(); cs_end();
if (garbage_collect_at_exit) if (garbage_collect_at_exit) {
garbage_collect(); garbage_collect(false);
}
mch_exit(exitval); mch_exit(exitval);
} }

View File

@ -62,6 +62,12 @@ lang mess C
" Always use forward slashes. " Always use forward slashes.
set shellslash set shellslash
" Make sure $HOME does not get read or written.
let $HOME = '/does/not/exist'
" Prepare for calling garbagecollect_for_testing().
let v:testing = 1
" Align with vim defaults. " Align with vim defaults.
set directory^=. set directory^=.
set nohidden set nohidden

View File

@ -0,0 +1,286 @@
" Test for lambda and closure
function! Test_lambda_feature()
call assert_equal(1, has('lambda'))
endfunction
function! Test_lambda_with_filter()
let s:x = 2
call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
endfunction
function! Test_lambda_with_map()
let s:x = 1
call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
endfunction
function! Test_lambda_with_sort()
call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
endfunction
" function! Test_lambda_with_timer()
" if !has('timers')
" return
" endif
" let s:n = 0
" let s:timer_id = 0
" function! s:Foo()
" "let n = 0
" let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
" endfunction
" call s:Foo()
" sleep 200ms
" " do not collect lambda
" call garbagecollect()
" let m = s:n
" sleep 200ms
" call timer_stop(s:timer_id)
" call assert_true(m > 1)
" call assert_true(s:n > m + 1)
" call assert_true(s:n < 9)
" endfunction
function! Test_lambda_with_partial()
let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
endfunction
function Test_lambda_fails()
call assert_equal(3, {a, b -> a + b}(1, 2))
call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
endfunc
func Test_not_lambda()
let x = {'>' : 'foo'}
call assert_equal('foo', x['>'])
endfunc
function! Test_lambda_capture_by_reference()
let v = 1
let l:F = {x -> x + v}
let v = 2
call assert_equal(12, l:F(10))
endfunction
function! Test_lambda_side_effect()
function! s:update_and_return(arr)
let a:arr[1] = 5
return a:arr
endfunction
function! s:foo(arr)
return {-> s:update_and_return(a:arr)}
endfunction
let arr = [3,2,1]
call assert_equal([3, 5, 1], s:foo(arr)())
endfunction
function! Test_lambda_refer_local_variable_from_other_scope()
function! s:foo(X)
return a:X() " refer l:x in s:bar()
endfunction
function! s:bar()
let x = 123
return s:foo({-> x})
endfunction
call assert_equal(123, s:bar())
endfunction
function! Test_lambda_do_not_share_local_variable()
function! s:define_funcs()
let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
let l:Two = {-> exists("a") ? a : "no"}
return [l:One, l:Two]
endfunction
let l:F = s:define_funcs()
call assert_equal('no', l:F[1]())
call assert_equal('abc', l:F[0]())
call assert_equal('no', l:F[1]())
endfunction
function! Test_lambda_closure_counter()
function! s:foo()
let x = 0
return {-> [execute("let x += 1"), x][-1]}
endfunction
let l:F = s:foo()
call garbagecollect()
call assert_equal(1, l:F())
call assert_equal(2, l:F())
call assert_equal(3, l:F())
call assert_equal(4, l:F())
endfunction
function! Test_lambda_with_a_var()
function! s:foo()
let x = 2
return {... -> a:000 + [x]}
endfunction
function! s:bar()
return s:foo()(1)
endfunction
call assert_equal([1, 2], s:bar())
endfunction
function! Test_lambda_call_lambda_from_lambda()
function! s:foo(x)
let l:F1 = {-> {-> a:x}}
return {-> l:F1()}
endfunction
let l:F = s:foo(1)
call assert_equal(1, l:F()())
endfunction
function! Test_lambda_delfunc()
function! s:gen()
let pl = l:
let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
let l:Bar = l:Foo
delfunction l:Foo
return l:Bar
endfunction
let l:F = s:gen()
call assert_fails(':call l:F()', 'E933:')
endfunction
function! Test_lambda_scope()
function! s:NewCounter()
let c = 0
return {-> [execute('let c += 1'), c][-1]}
endfunction
function! s:NewCounter2()
return {-> [execute('let c += 100'), c][-1]}
endfunction
let l:C = s:NewCounter()
let l:D = s:NewCounter2()
call assert_equal(1, l:C())
call assert_fails(':call l:D()', 'E15:') " E121: then E15:
call assert_equal(2, l:C())
endfunction
function! Test_lambda_share_scope()
function! s:New()
let c = 0
let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
return [l:Inc0, l:Dec0]
endfunction
let [l:Inc, l:Dec] = s:New()
call assert_equal(1, l:Inc())
call assert_equal(2, l:Inc())
call assert_equal(1, l:Dec())
endfunction
function! Test_lambda_circular_reference()
function! s:Foo()
let d = {}
let d.f = {-> d}
return d.f
endfunction
call s:Foo()
call garbagecollect()
let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
call garbagecollect()
endfunction
function! Test_lambda_combination()
call assert_equal(2, {x -> {x -> x}}(1)(2))
call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
" Z combinator
let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
call assert_equal(120, Z(Fact)(5))
endfunction
function! Test_closure_counter()
function! s:foo()
let x = 0
function! s:bar() closure
let x += 1
return x
endfunction
return function('s:bar')
endfunction
let l:F = s:foo()
call garbagecollect()
call assert_equal(1, l:F())
call assert_equal(2, l:F())
call assert_equal(3, l:F())
call assert_equal(4, l:F())
endfunction
function! Test_closure_unlet()
function! s:foo()
let x = 1
function! s:bar() closure
unlet x
endfunction
call s:bar()
return l:
endfunction
call assert_false(has_key(s:foo(), 'x'))
call garbagecollect()
endfunction
function! LambdaFoo()
let x = 0
function! LambdaBar() closure
let x += 1
return x
endfunction
return function('LambdaBar')
endfunction
func Test_closure_refcount()
let g:Count = LambdaFoo()
call test_garbagecollect_now()
call assert_equal(1, g:Count())
let g:Count2 = LambdaFoo()
call test_garbagecollect_now()
call assert_equal(1, g:Count2())
call assert_equal(2, g:Count())
call assert_equal(3, g:Count2())
delfunc LambdaFoo
delfunc LambdaBar
endfunc
func Test_named_function_closure()
func! Afoo()
let x = 14
func! s:Abar() closure
return x
endfunc
call assert_equal(14, s:Abar())
endfunc
call Afoo()
call assert_equal(14, s:Abar())
call garbagecollect()
call assert_equal(14, s:Abar())
endfunc

View File

@ -714,7 +714,7 @@ static int included_patches[] = {
1730, 1730,
// 1729 NA // 1729 NA
1728, 1728,
// 1727 NA 1727,
// 1726 NA // 1726 NA
// 1725 NA // 1725 NA
// 1724 NA // 1724 NA