Merge #6439 from ZyX-I/fix-gc-failures

unittests: Force GC, fix GC failures in typval_spec
This commit is contained in:
Justin M. Keyes 2017-04-09 04:05:07 +02:00 committed by GitHub
commit 699e8406b5
13 changed files with 163 additions and 118 deletions

View File

@ -43,11 +43,6 @@ env:
# If this file exists, we know that the cache contains compiled
# dependencies and we can use it.
- CACHE_MARKER="$HOME/.cache/nvim-deps/.travis_cache_marker"
# Test success marker. If this file exists, we know that all tests
# were successful. Required because we only want to update the cache
# if the tests were successful, but don't have this information
# available in before_cache (which is run before after_success).
- SUCCESS_MARKER="$BUILD_DIR/.tests_successful"
# default target name for functional tests
- FUNCTIONALTEST=functionaltest
- CI_TARGET=tests

View File

@ -275,7 +275,6 @@ else()
endif()
add_definitions(-DINCLUDE_GENERATED_DECLARATIONS)
add_definitions(-DHAVE_CONFIG_H)
if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined")

View File

@ -3,12 +3,15 @@
set -e
set -o pipefail
CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CI_DIR}/common/suite.sh"
# Don't cache pip's log and selfcheck.
rm -rf "${HOME}/.cache/pip/log"
rm -f "${HOME}/.cache/pip/selfcheck.json"
# Update the third-party dependency cache only if the build was successful.
if [[ -f "${SUCCESS_MARKER}" ]]; then
if ended_successfully; then
rm -rf "${HOME}/.cache/nvim-deps"
mv "${DEPS_BUILD_DIR}" "${HOME}/.cache/nvim-deps"
touch "${CACHE_MARKER}"

View File

@ -2,11 +2,18 @@
NL="$(printf '\nE')"
NL="${NL%E}"
FAILED=0
FAIL_SUMMARY=""
# Test success marker. If END_MARKER file exists, we know that all tests
# finished. If FAIL_SUMMARY_FILE exists we know that some tests failed, this
# file will contain information about failed tests. Build is considered
# successful if tests ended without any of them failing.
END_MARKER="$BUILD_DIR/.tests_finished"
FAIL_SUMMARY_FILE="$BUILD_DIR/.test_errors"
enter_suite() {
FAILED=0
rm -f "${END_MARKER}"
local suite_name="$1"
export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE}/$suite_name"
}
@ -19,17 +26,16 @@ exit_suite() {
export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE%/*}"
if test "x$1" != "x--continue" ; then
exit $FAILED
else
local saved_failed=$FAILED
FAILED=0
return $saved_failed
fi
}
fail() {
local allow_failure=
if test "x$1" = "x--allow-failure" ; then
shift
allow_failure=A
fi
local test_name="$1"
local fail_char="$allow_failure$2"
local fail_char="$2"
local message="$3"
: ${fail_char:=F}
@ -37,10 +43,9 @@ fail() {
local full_msg="$fail_char $NVIM_TEST_CURRENT_SUITE|$test_name :: $message"
FAIL_SUMMARY="${FAIL_SUMMARY}${NL}${full_msg}"
echo "${full_msg}" >> "${FAIL_SUMMARY_FILE}"
echo "Failed: $full_msg"
if test "x$allow_failure" = "x" ; then
FAILED=1
fi
FAILED=1
}
run_test() {
@ -55,6 +60,12 @@ run_test() {
}
run_test_wd() {
local hang_ok=
if test "x$1" = "x--allow-hang" ; then
hang_ok=1
shift
fi
local timeout="$1"
test $# -gt 0 && shift
@ -77,14 +88,13 @@ run_test_wd() {
while test $restarts -gt 0 ; do
: > "${status_file}"
(
FAILED=0
if ! (
set -o pipefail
eval "$cmd" 2>&1 | tee -a "$output_file"
) ; then
fail "${test_name}" "$@"
set -o pipefail
ret=0
if ! eval "$cmd" 2>&1 | tee -a "$output_file" ; then
ret=1
fi
echo "$FAILED" > "$status_file"
echo "$ret" > "$status_file"
exit $ret
) &
local pid=$!
while test "$(stat -c "%s" "$status_file")" -eq 0 ; do
@ -101,7 +111,9 @@ run_test_wd() {
# status file not updated, assuming hang
kill -KILL $pid
if test $restarts -eq 0 ; then
fail "${test_name}" E "Test hang up"
if test "x$hang_ok" = "x" ; then
fail "${test_name}" E "Test hang up"
fi
else
echo "Test ${test_name} hang up, restarting"
eval "$restart_cmd"
@ -116,6 +128,20 @@ run_test_wd() {
done
}
succeeded() {
return $FAILED
ended_successfully() {
if [[ -f "${FAIL_SUMMARY_FILE}" ]]; then
echo 'Test failed, complete summary:'
cat "${FAIL_SUMMARY_FILE}"
return 1
fi
if ! [[ -f "${END_MARKER}" ]] ; then
echo 'ended_successfully called before end marker was touched'
return 1
fi
return 0
}
end_tests() {
touch "${END_MARKER}"
ended_successfully
}

View File

@ -1,4 +1,5 @@
source "${CI_DIR}/common/build.sh"
source "${CI_DIR}/common/suite.sh"
print_core() {
local app="$1"
@ -40,10 +41,9 @@ check_core_dumps() {
print_core "$app" "$core"
fi
done
if test "$app" = quiet ; then
return 0
if test "$app" != quiet ; then
fail 'cores' E 'Core dumps found'
fi
exit 1
}
check_logs() {
@ -62,8 +62,7 @@ check_logs() {
err=1
done
if [[ -n "${err}" ]]; then
echo "Runtime errors detected."
exit 1
fail 'logs' E 'Runtime errors detected.'
fi
}
@ -75,50 +74,53 @@ asan_check() {
check_logs "${1}" "*san.*"
}
run_unittests() {
run_unittests() {(
enter_suite unittests
ulimit -c unlimited
if ! build_make unittest ; then
check_core_dumps "$(which luajit)"
exit 1
fail 'unittests' F 'Unit tests failed'
fi
check_core_dumps "$(which luajit)"
}
exit_suite
)}
run_functionaltests() {
run_functionaltests() {(
enter_suite functionaltests
ulimit -c unlimited
if ! build_make ${FUNCTIONALTEST}; then
asan_check "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
exit 1
fail 'functionaltests' F 'Functional tests failed'
fi
asan_check "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
}
exit_suite
)}
run_oldtests() {
run_oldtests() {(
enter_suite oldtests
ulimit -c unlimited
if ! make -C "${TRAVIS_BUILD_DIR}/src/nvim/testdir"; then
reset
asan_check "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
exit 1
fail 'oldtests' F 'Legacy tests failed'
fi
asan_check "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
}
exit_suite
)}
install_nvim() {
build_make install
install_nvim() {(
enter_suite 'install_nvim'
if ! build_make install ; then
fail 'install' E 'make install failed'
exit_suite
fi
"${INSTALL_PREFIX}/bin/nvim" --version
"${INSTALL_PREFIX}/bin/nvim" -u NONE -e -c ':help' -c ':qall' || {
echo "Running ':help' in the installed nvim failed."
echo "Maybe the helptags have not been generated properly."
exit 1
fail 'help' F 'Failed running :help'
}
local genvimsynf=syntax/vim/generated.vim
@ -127,24 +129,22 @@ install_nvim() {
cd runtime ; git ls-files | grep -e '.vim$' -e '.ps$' -e '.dict$' -e '.py$' -e '.tutor$'
) ; do
if ! test -e "${INSTALL_PREFIX}/share/nvim/runtime/$file" ; then
echo "It appears that $file is not installed."
exit 1
fail 'runtime-install' F "It appears that $file is not installed."
fi
done
# Check that generated syntax file has function names, #5060.
local gpat='syn keyword vimFuncName .*eval'
if ! grep -q "$gpat" "${INSTALL_PREFIX}/share/nvim/runtime/$genvimsynf"; then
echo "It appears that $genvimsynf does not contain $gpat."
exit 1
fail 'funcnames' F "It appears that $genvimsynf does not contain $gpat."
fi
for file in $(
cd runtime ; git ls-files | grep -e '.awk$' -e '.sh$' -e '.bat$'
) ; do
if ! test -x "${INSTALL_PREFIX}/share/nvim/runtime/$file" ; then
echo "It appears that $file is not installed or is not executable."
exit 1
fail 'not-exe' F "It appears that $file is not installed or is not executable."
fi
done
}
exit_suite
)}

View File

@ -20,9 +20,10 @@ csi_clean() {
run_test 'top_make clint-full' clint
run_test 'top_make testlint' testlint
CLICOLOR_FORCE=1 run_test_wd \
5s \
--allow-hang \
10s \
'top_make check-single-includes' \
'csi_clean' \
single-includes
exit_suite
end_tests

View File

@ -27,8 +27,4 @@ run_test run_oldtests
run_test install_nvim
if succeeded ; then
touch "${SUCCESS_MARKER}"
fi
exit_suite
end_tests

View File

@ -10,9 +10,7 @@
// USE_ICONV, or to put the USE_ICONV definition in config.h.in directly. As
// it stands, globals.h needs to be included alongside iconv.h.
#ifdef HAVE_CONFIG_H
# include "auto/config.h"
#endif
#include "auto/config.h"
// Use iconv() when it's available, either by linking to the library at
// compile time or by loading it at runtime.

View File

@ -11,20 +11,16 @@
#define RUNTIME_DIRNAME "runtime"
/* end */
/* ============ the header file puzzle (ca. 50-100 pieces) ========= */
#ifdef HAVE_CONFIG_H /* GNU autoconf (or something else) was here */
# include "auto/config.h"
# define HAVE_PATHDEF
#include "auto/config.h"
#define HAVE_PATHDEF
/*
* Check if configure correctly managed to find sizeof(int). If this failed,
* it becomes zero. This is likely a problem of not being able to run the
* test program. Other items from configure may also be wrong then!
*/
# if (SIZEOF_INT == 0)
Error: configure did not run properly.Check auto/config.log.
# endif
#if (SIZEOF_INT == 0)
# error Configure did not run properly.
#endif
#include "nvim/os/os_defs.h" /* bring lots of system header files */
@ -46,11 +42,6 @@ enum { NUMBUFLEN = 65 };
#include "nvim/keymap.h"
#include "nvim/macros.h"
/* ================ end of the header file puzzle =============== */
#include "nvim/gettext.h"
/* special attribute addition: Put message in history */

View File

@ -162,7 +162,7 @@ end
local tests_skipped = 0
local function check_cores(app)
local function check_cores(app, force)
app = app or 'build/bin/nvim'
local initial_path, re, exc_re
local gdb_db_cmd = 'gdb -n -batch -ex "thread apply all bt full" "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"'
@ -188,7 +188,7 @@ local function check_cores(app)
random_skip = true
end
-- Finding cores takes too much time on linux
if random_skip and math.random() < 0.9 then
if not force and random_skip and math.random() < 0.9 then
tests_skipped = tests_skipped + 1
return
end

View File

@ -468,7 +468,7 @@ local function tbl2callback(tbl)
data={funcref=eval.xstrdup(tbl.fref)}}})
elseif tbl.type == 'pt' then
local pt = ffi.gc(ffi.cast('partial_T*',
eval.xcalloc(1, ffi.sizeof('partial_T'))), eval.partial_unref)
eval.xcalloc(1, ffi.sizeof('partial_T'))), nil)
ret = ffi.new('Callback[1]', {{type=eval.kCallbackPartial,
data={partial=populate_partial(pt, tbl.pt, {})}}})
else

View File

@ -47,6 +47,15 @@ local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
'./src/nvim/eval.h', './src/nvim/vim.h',
'./src/nvim/globals.h')
local function vimconv_alloc()
return ffi.gc(
ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))),
function(vc)
lib.convert_setup(vc, nil, nil)
lib.xfree(vc)
end)
end
local function list_watch_alloc(li)
return ffi.cast('listwatch_T*', ffi.new('listwatch_T[1]', {{lw_item=li}}))
end
@ -237,24 +246,33 @@ describe('typval.c', function()
list_watch(l, lis[4]),
list_watch(l, lis[7]),
}
alloc_log:check({
a.list(l),
a.li(lis[1]),
a.li(lis[2]),
a.li(lis[3]),
a.li(lis[4]),
a.li(lis[5]),
a.li(lis[6]),
a.li(lis[7]),
})
lib.tv_list_item_remove(l, lis[4])
ffi.gc(lis[4], lib.tv_list_item_free)
alloc_log:check({a.freed(lis[4])})
eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
lib.tv_list_item_remove(l, lis[2])
ffi.gc(lis[2], lib.tv_list_item_free)
alloc_log:check({a.freed(lis[2])})
eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
lib.tv_list_item_remove(l, lis[7])
ffi.gc(lis[7], lib.tv_list_item_free)
alloc_log:check({a.freed(lis[7])})
eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
lib.tv_list_item_remove(l, lis[1])
ffi.gc(lis[1], lib.tv_list_item_free)
alloc_log:check({a.freed(lis[1])})
eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
alloc_log:clear()
lib.tv_list_watch_remove(l, lws[2])
lib.tv_list_watch_remove(l, lws[3])
lib.tv_list_watch_remove(l, lws[1])
@ -460,6 +478,10 @@ describe('typval.c', function()
eq(empty_list, typvalt2lua(l_tv))
eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
lib.tv_list_watch_remove(l, lws[1])
lib.tv_list_watch_remove(l, lws[2])
lib.tv_list_watch_remove(l, lws[3])
alloc_log:check({})
end)
end)
@ -730,10 +752,8 @@ describe('typval.c', function()
collectgarbage()
end)
itp('copies list correctly and converts items', function()
local vc = ffi.gc(ffi.new('vimconv_T[1]'), function(vc)
lib.convert_setup(vc, nil, nil)
end)
-- UTF-8 ↔ latin1 conversions need no iconv
local vc = vimconv_alloc()
-- UTF-8 ↔ latin1 conversions needs no iconv
eq(OK, lib.convert_setup(vc, to_cstr('utf-8'), to_cstr('latin1')))
local v = {{['«']='»'}, {''}, 1, '', null_string, null_list, null_dict}
@ -1087,12 +1107,16 @@ describe('typval.c', function()
end)
end)
describe('join()', function()
local function list_join(l, sep, ret)
local function list_join(l, sep, join_ret)
local ga = ga_alloc()
eq(ret or OK, lib.tv_list_join(ga, l, sep))
if ga.ga_data == nil then return ''
else return ffi.string(ga.ga_data)
eq(join_ret or OK, lib.tv_list_join(ga, l, sep))
local ret = ''
if ga.ga_data ~= nil then
ret = ffi.string(ga.ga_data)
end
-- For some reason this is not working well in GC
lib.ga_clear(ffi.gc(ga, nil))
return ret
end
itp('works', function()
local l
@ -1508,7 +1532,7 @@ describe('typval.c', function()
eq(s:sub(1, len), ffi.string(di.di_key))
alloc_log:check({a.di(di, len)})
if tv then
di.di_tv = tv
di.di_tv = ffi.gc(tv, nil)
else
di.di_tv.v_type = lib.VAR_UNKNOWN
end
@ -1539,7 +1563,7 @@ describe('typval.c', function()
alloc_log:check({a.dict(d)})
local di = ffi.gc(lib.tv_dict_item_alloc(''), nil)
local tv = lua2typvalt('test')
di.di_tv = tv
di.di_tv = ffi.gc(tv, nil)
alloc_log:check({a.di(di, ''), a.str(tv.vval.v_string, 'test')})
eq(OK, lib.tv_dict_add(d, di))
alloc_log:check({})
@ -2131,9 +2155,7 @@ describe('typval.c', function()
collectgarbage()
end)
itp('copies dict correctly and converts items', function()
local vc = ffi.gc(ffi.new('vimconv_T[1]'), function(vc)
lib.convert_setup(vc, nil, nil)
end)
local vc = vimconv_alloc()
-- UTF-8 ↔ latin1 conversions need no iconv
eq(OK, lib.convert_setup(vc, to_cstr('utf-8'), to_cstr('latin1')))
@ -2659,7 +2681,8 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]
@ -2687,7 +2710,8 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = {v[4], not not emsg}
@ -2721,7 +2745,8 @@ describe('typval.c', function()
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', -1},
}) do
lib.curwin.w_cursor.lnum = 46
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]
@ -2749,7 +2774,8 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, 'E907: Using a special value as a Float', 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_float(UNKNOWN)', 0},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]
@ -2780,7 +2806,9 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr in place of Neovim allocated string, cannot
-- tv_clear() that.
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]
@ -2821,7 +2849,8 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]
@ -2861,7 +2890,8 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]
@ -2902,7 +2932,8 @@ describe('typval.c', function()
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
local tv = typvalt(v[1], v[2])
-- Using to_cstr, cannot free with tv_clear
local tv = ffi.gc(typvalt(v[1], v[2]), nil)
alloc_log:check({})
local emsg = v[3]
local ret = v[4]

View File

@ -632,8 +632,9 @@ local function itp_child(wr, func)
collectgarbage('stop')
child_sethook(wr)
local err, emsg = pcall(func)
debug.sethook()
collectgarbage('restart')
collectgarbage()
debug.sethook()
emsg = tostring(emsg)
sc.write(wr, trace_end_msg)
if not err then
@ -654,6 +655,7 @@ end
local function check_child_err(rd)
local trace = {}
local did_traceline = false
while true do
local traceline = sc.read(rd, hook_msglen)
if #traceline ~= hook_msglen then
@ -664,6 +666,7 @@ local function check_child_err(rd)
end
end
if traceline == trace_end_msg then
did_traceline = true
break
end
trace[#trace + 1] = traceline
@ -679,6 +682,13 @@ local function check_child_err(rd)
error = error .. trace[i]
end
end
if not did_traceline then
error = error .. '\nNo end of trace occurred'
end
local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
if not cc_err then
error = error .. '\ncheck_cores failed: ' .. cc_emsg
end
assert.just_fail(error)
end
if res == '+\n' then
@ -764,11 +774,6 @@ local module = {
child_cleanup_once = child_cleanup_once,
sc = sc,
}
return function(after_each)
if after_each then
after_each(function()
check_cores(Paths.test_luajit_prg)
end)
end
return function()
return module
end