mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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:
parent
64c375c589
commit
b0fc6108c9
@ -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.
|
||||
{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 Full filename of the last loaded or saved session file. See
|
||||
|: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}
|
||||
tanh({expr}) Float hyperbolic tangent of {expr}
|
||||
tempname() String name for a temporary file
|
||||
test_garbagecollect_now() none free memory right now for testing
|
||||
timer_start({time}, {callback} [, {options}])
|
||||
Number create a timer
|
||||
timer_stop({timer}) none stop a timer
|
||||
@ -3589,18 +3593,25 @@ function({name} [, {arglist}] [, {dict}])
|
||||
|
||||
garbagecollect([{atexit}]) *garbagecollect()*
|
||||
Cleanup unused |Lists| and |Dictionaries| that have circular
|
||||
references. There is hardly ever a need to invoke this
|
||||
function, as it is automatically done when Vim runs out of
|
||||
memory or is waiting for the user to press a key after
|
||||
'updatetime'. Items without circular references are always
|
||||
freed when they become unused.
|
||||
references.
|
||||
|
||||
There is hardly ever a need to invoke this function, as it is
|
||||
automatically done when Vim runs out of memory or is waiting
|
||||
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
|
||||
|Dictionary| with circular references in a script that runs
|
||||
for a long time.
|
||||
|
||||
When the optional {atexit} argument is one, garbage
|
||||
collection will also be done when exiting Vim, if it wasn't
|
||||
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 item {idx} from |List| {list}. When this item is not
|
||||
available return {default}. Return zero when {default} is
|
||||
@ -7288,6 +7299,12 @@ termopen({cmd}[, {opts}]) {Nvim} *termopen()*
|
||||
|
||||
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()*
|
||||
Return the tangent of {expr}, measured in radians, as a |Float|
|
||||
in the range [-inf, inf].
|
||||
|
@ -389,6 +389,7 @@ static struct vimvar {
|
||||
VV(VV__NULL_LIST, "_null_list", VAR_LIST, 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_TESTING, "testing", VAR_NUMBER, 0),
|
||||
VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_STRING, "t_string", 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));
|
||||
ga_clear(&ga_scripts);
|
||||
|
||||
/* unreferenced lists and dicts */
|
||||
(void)garbage_collect();
|
||||
// unreferenced lists and dicts
|
||||
(void)garbage_collect(false);
|
||||
|
||||
/* functions */
|
||||
free_all_functions();
|
||||
@ -5813,6 +5814,9 @@ int get_copyID(void)
|
||||
return current_copyID;
|
||||
}
|
||||
|
||||
// Used by get_func_tv()
|
||||
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
|
||||
|
||||
/*
|
||||
* Garbage collection for lists and dictionaries.
|
||||
*
|
||||
@ -5835,16 +5839,19 @@ int get_copyID(void)
|
||||
|
||||
/// Do garbage collection for lists and dicts.
|
||||
///
|
||||
/// @param testing true if called from test_garbagecollect_now().
|
||||
/// @returns true if some memory was freed.
|
||||
bool garbage_collect(void)
|
||||
bool garbage_collect(bool testing)
|
||||
{
|
||||
bool abort = false;
|
||||
#define ABORTING(func) abort = abort || func
|
||||
|
||||
if (!testing) {
|
||||
// Only do this once.
|
||||
want_garbage_collect = false;
|
||||
may_garbage_collect = false;
|
||||
garbage_collect_at_exit = false;
|
||||
}
|
||||
|
||||
// We advance by two because we add one for items referenced through
|
||||
// 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
|
||||
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
|
||||
|
||||
@ -6008,7 +6021,7 @@ bool garbage_collect(void)
|
||||
if (did_free_funccal) {
|
||||
// When a funccal was freed some more items might be garbage
|
||||
// collected, so run again.
|
||||
(void)garbage_collect();
|
||||
(void)garbage_collect(testing);
|
||||
}
|
||||
} else if (p_verbose > 0) {
|
||||
verb_msg((char_u *)_(
|
||||
@ -7085,9 +7098,24 @@ get_func_tv (
|
||||
ret = FAIL;
|
||||
|
||||
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,
|
||||
partial, selfdict);
|
||||
|
||||
funcargs.ga_len -= i;
|
||||
} else if (!aborting()) {
|
||||
if (argcount == MAX_FUNC_ARGS) {
|
||||
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;
|
||||
}
|
||||
|
||||
// "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)
|
||||
{
|
||||
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
|
||||
|
@ -127,6 +127,7 @@ typedef enum {
|
||||
VV__NULL_LIST, // List with NULL value. For test purposes only.
|
||||
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
|
||||
VV_VIM_DID_ENTER,
|
||||
VV_TESTING,
|
||||
VV_TYPE_NUMBER,
|
||||
VV_TYPE_STRING,
|
||||
VV_TYPE_FUNC,
|
||||
|
@ -303,6 +303,7 @@ return {
|
||||
tanh={args=1, func="float_op_wrapper", data="&tanh"},
|
||||
tempname={},
|
||||
termopen={args={1, 2}},
|
||||
test_garbagecollect_now={},
|
||||
timer_start={args={2,3}},
|
||||
timer_stop={args=1},
|
||||
tolower={args=1},
|
||||
|
@ -1327,8 +1327,9 @@ int using_script(void)
|
||||
void before_blocking(void)
|
||||
{
|
||||
updatescript(0);
|
||||
if (may_garbage_collect)
|
||||
garbage_collect();
|
||||
if (may_garbage_collect) {
|
||||
garbage_collect(false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1366,10 +1367,11 @@ int vgetc(void)
|
||||
char_u buf[MB_MAXBYTES + 1];
|
||||
int i;
|
||||
|
||||
/* Do garbage collection when garbagecollect() was called previously and
|
||||
* we are now at the toplevel. */
|
||||
if (may_garbage_collect && want_garbage_collect)
|
||||
garbage_collect();
|
||||
// Do garbage collection when garbagecollect() was called previously and
|
||||
// we are now at the toplevel.
|
||||
if (may_garbage_collect && want_garbage_collect) {
|
||||
garbage_collect(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* If a character was put back with vungetc, it was already processed.
|
||||
|
@ -624,8 +624,9 @@ void getout(int exitval)
|
||||
iconv_end();
|
||||
#endif
|
||||
cs_end();
|
||||
if (garbage_collect_at_exit)
|
||||
garbage_collect();
|
||||
if (garbage_collect_at_exit) {
|
||||
garbage_collect(false);
|
||||
}
|
||||
|
||||
mch_exit(exitval);
|
||||
}
|
||||
|
@ -62,6 +62,12 @@ lang mess C
|
||||
" Always use forward slashes.
|
||||
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.
|
||||
set directory^=.
|
||||
set nohidden
|
||||
|
286
src/nvim/testdir/test_lambda.vim
Normal file
286
src/nvim/testdir/test_lambda.vim
Normal 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
|
@ -714,7 +714,7 @@ static int included_patches[] = {
|
||||
1730,
|
||||
// 1729 NA
|
||||
1728,
|
||||
// 1727 NA
|
||||
1727,
|
||||
// 1726 NA
|
||||
// 1725 NA
|
||||
// 1724 NA
|
||||
|
Loading…
Reference in New Issue
Block a user