Merge pull request #14226 from bfredl/luarefcount

lua: track reference ownership with ASAN when present
This commit is contained in:
Björn Linse 2021-04-03 17:48:39 +02:00 committed by GitHub
commit 804ea22944
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 198 additions and 160 deletions

View File

@ -222,11 +222,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
return buf_updates_register(buf, channel_id, cb, send_buffer);
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
api_free_luaref(cb.on_lines);
api_free_luaref(cb.on_bytes);
api_free_luaref(cb.on_changedtick);
api_free_luaref(cb.on_detach);
buffer_update_callbacks_free(cb);
return false;
}

View File

@ -1708,33 +1708,6 @@ const char *describe_ns(NS ns_id)
return "(UNKNOWN PLUGIN)";
}
DecorProvider *get_provider(NS ns_id, bool force)
{
ssize_t i;
for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
DecorProvider *item = &kv_A(decor_providers, i);
if (item->ns_id == ns_id) {
return item;
} else if (item->ns_id > ns_id) {
break;
}
}
if (!force) {
return NULL;
}
for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
// allocates if needed:
(void)kv_a(decor_providers, (size_t)j+1);
kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
}
DecorProvider *item = &kv_a(decor_providers, (size_t)i);
*item = DECORATION_PROVIDER_INIT(ns_id);
return item;
}
static bool parse_float_anchor(String anchor, FloatAnchor *out)
{
if (anchor.size == 0) {

View File

@ -2708,6 +2708,7 @@ Dictionary nvim__stats(void)
Dictionary rv = ARRAY_DICT_INIT;
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
return rv;
}
@ -2880,19 +2881,6 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
static void clear_provider(DecorProvider *p)
{
if (p == NULL) {
return;
}
NLUA_CLEAR_REF(p->redraw_start);
NLUA_CLEAR_REF(p->redraw_buf);
NLUA_CLEAR_REF(p->redraw_win);
NLUA_CLEAR_REF(p->redraw_line);
NLUA_CLEAR_REF(p->redraw_end);
p->active = false;
}
/// Set or change decoration provider for a namespace
///
/// This is a very general purpose interface for having lua callbacks
@ -2937,8 +2925,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
Error *err)
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
{
DecorProvider *p = get_provider((NS)ns_id, true);
clear_provider(p);
DecorProvider *p = get_decor_provider((NS)ns_id, true);
decor_provider_clear(p);
// regardless of what happens, it seems good idea to redraw
redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
@ -2981,5 +2969,5 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
p->active = true;
return;
error:
clear_provider(p);
decor_provider_clear(p);
}

View File

@ -176,7 +176,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
} else {
free_update_callbacks(cb);
buffer_update_callbacks_free(cb);
}
}
kv_size(buf->update_callbacks) = j;
@ -290,7 +290,7 @@ void buf_updates_send_changes(buf_T *buf,
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
buffer_update_callbacks_free(cb);
keep = false;
}
api_free_object(res);
@ -342,7 +342,7 @@ void buf_updates_send_splice(
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
buffer_update_callbacks_free(cb);
keep = false;
}
}
@ -378,7 +378,7 @@ void buf_updates_changedtick(buf_T *buf)
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
buffer_update_callbacks_free(cb);
keep = false;
}
api_free_object(res);
@ -406,8 +406,11 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
rpc_send_event(channel_id, "nvim_buf_changedtick_event", args);
}
static void free_update_callbacks(BufUpdateCallbacks cb)
void buffer_update_callbacks_free(BufUpdateCallbacks cb)
{
api_free_luaref(cb.on_lines);
api_free_luaref(cb.on_bytes);
api_free_luaref(cb.on_changedtick);
api_free_luaref(cb.on_reload);
api_free_luaref(cb.on_detach);
}

View File

@ -2,6 +2,7 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/vim.h"
#include "nvim/lua/executor.h"
#include "nvim/extmark.h"
#include "nvim/decoration.h"
#include "nvim/screen.h"
@ -365,3 +366,52 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col,
decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true,
priority);
}
DecorProvider *get_decor_provider(NS ns_id, bool force)
{
ssize_t i;
for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
DecorProvider *item = &kv_A(decor_providers, i);
if (item->ns_id == ns_id) {
return item;
} else if (item->ns_id > ns_id) {
break;
}
}
if (!force) {
return NULL;
}
for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
// allocates if needed:
(void)kv_a(decor_providers, (size_t)j+1);
kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
}
DecorProvider *item = &kv_a(decor_providers, (size_t)i);
*item = DECORATION_PROVIDER_INIT(ns_id);
return item;
}
void decor_provider_clear(DecorProvider *p)
{
if (p == NULL) {
return;
}
NLUA_CLEAR_REF(p->redraw_start);
NLUA_CLEAR_REF(p->redraw_buf);
NLUA_CLEAR_REF(p->redraw_win);
NLUA_CLEAR_REF(p->redraw_line);
NLUA_CLEAR_REF(p->redraw_end);
p->active = false;
}
void decor_free_all_mem(void)
{
for (size_t i = 0; i < kv_size(decor_providers); i++) {
decor_provider_clear(&kv_A(decor_providers, i));
}
kv_destroy(decor_providers);
}

View File

@ -6263,6 +6263,7 @@ void common_function(typval_T *argvars, typval_T *rettv,
// function(dict.MyFunc, [arg])
arg_pt = argvars[0].vval.v_partial;
s = partial_name(arg_pt);
// TODO(bfredl): do the entire nlua_is_table_from_lua dance
} else {
// function('MyFunc', [arg], dict)
s = (char_u *)tv_get_string(&argvars[0]);
@ -7362,7 +7363,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
char_u *name = nlua_register_table_as_callable(arg);
if (name != NULL) {
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
} else {

View File

@ -810,6 +810,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
bool owned = false;
char_u *func;
partial_T *partial = NULL;
dict_T *selfdict = NULL;
@ -820,6 +821,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func = partial_name(partial);
} else if (nlua_is_table_from_lua(&argvars[0])) {
func = nlua_register_table_as_callable(&argvars[0]);
owned = true;
} else {
func = (char_u *)tv_get_string(&argvars[0]);
}
@ -837,6 +839,9 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
func_call(func, &argvars[1], partial, selfdict, rettv);
if (owned) {
func_unref(func);
}
}
/*

View File

@ -219,6 +219,7 @@ list_T *tv_list_alloc(const ptrdiff_t len)
list->lv_used_next = gc_first_list;
gc_first_list = list;
list_log(list, NULL, (void *)(uintptr_t)len, "alloc");
list->lua_table_ref = LUA_NOREF;
return list;
}
@ -302,7 +303,7 @@ void tv_list_free_list(list_T *const l)
}
list_log(l, NULL, NULL, "freelist");
nlua_free_typval_list(l);
NLUA_CLEAR_REF(l->lua_table_ref);
xfree(l);
}
@ -1404,6 +1405,8 @@ dict_T *tv_dict_alloc(void)
d->dv_copyID = 0;
QUEUE_INIT(&d->watchers);
d->lua_table_ref = LUA_NOREF;
return d;
}
@ -1454,7 +1457,7 @@ void tv_dict_free_dict(dict_T *const d)
d->dv_used_next->dv_used_prev = d->dv_used_prev;
}
nlua_free_typval_dict(d);
NLUA_CLEAR_REF(d->lua_table_ref);
xfree(d);
}

View File

@ -151,7 +151,7 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id)
{
DecorProvider *p = get_provider(ns_id, true);
DecorProvider *p = get_decor_provider(ns_id, true);
if ((attrs.rgb_ae_attr & HL_DEFAULT)
&& map_has(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id))) {
return;
@ -175,7 +175,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
ns_id = ns_hl_active;
}
DecorProvider *p = get_provider(ns_id, true);
DecorProvider *p = get_decor_provider(ns_id, true);
ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id));
// TODO(bfredl): map_ref true even this?
bool valid_cache = it.version >= p->hl_valid;

View File

@ -400,7 +400,6 @@ nlua_pop_typval_table_processing_end:
case LUA_TFUNCTION: {
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
state->lua_callable.func_ref = nlua_ref(lstate, -1);
state->lua_callable.table_ref = LUA_NOREF;
char_u *name = register_cfunc(
&nlua_CFunction_func_call,
@ -412,6 +411,7 @@ nlua_pop_typval_table_processing_end:
break;
}
case LUA_TUSERDATA: {
// TODO(bfredl): check mt.__call and convert to function?
nlua_pushref(lstate, nlua_nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);

View File

@ -11,7 +11,6 @@
typedef struct {
LuaRef func_ref;
LuaRef table_ref;
} LuaCallable;
typedef struct {

View File

@ -5,6 +5,7 @@
#include <lualib.h>
#include <lauxlib.h>
#include "nvim/assert.h"
#include "nvim/version.h"
#include "nvim/misc1.h"
#include "nvim/getchar.h"
@ -18,6 +19,7 @@
#include "nvim/vim.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
#include "nvim/map.h"
#include "nvim/message.h"
#include "nvim/memline.h"
#include "nvim/buffer_defs.h"
@ -32,9 +34,7 @@
#include "nvim/event/time.h"
#include "nvim/event/loop.h"
#ifdef WIN32
#include "nvim/os/os.h"
#endif
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
@ -63,6 +63,11 @@ typedef struct {
} \
}
#if __has_feature(address_sanitizer)
PMap(handle_T) *nlua_ref_markers = NULL;
# define NLUA_TRACK_REFS
#endif
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@ -547,6 +552,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
static lua_State *nlua_init(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
#ifdef NLUA_TRACK_REFS
const char *env = os_getenv("NVIM_LUA_NOTRACK");
if (!env || !*env) {
nlua_ref_markers = pmap_new(handle_T)();
}
#endif
lua_State *lstate = luaL_newstate();
if (lstate == NULL) {
EMSG(_("E970: Failed to initialize lua interpreter"));
@ -554,9 +566,13 @@ static lua_State *nlua_init(void)
}
luaL_openlibs(lstate);
nlua_state_init(lstate);
return lstate;
}
// only to be used by nlua_enter and nlua_free_all_mem!
static lua_State *global_lstate = NULL;
/// Enter lua interpreter
///
/// Calls nlua_init() if needed. Is responsible for pre-lua call initalization
@ -567,26 +583,39 @@ static lua_State *nlua_init(void)
static lua_State *nlua_enter(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
static lua_State *global_lstate = NULL;
if (global_lstate == NULL) {
global_lstate = nlua_init();
}
lua_State *const lstate = global_lstate;
// Last used p_rtp value. Must not be dereferenced because value pointed to
// may already be freed. Used to check whether &runtimepath option value
// changed.
static const void *last_p_rtp = NULL;
if (last_p_rtp != (const void *)p_rtp) {
// stack: (empty)
lua_getglobal(lstate, "vim");
// stack: vim
lua_pop(lstate, 1);
// stack: (empty)
last_p_rtp = (const void *)p_rtp;
}
return lstate;
}
void nlua_free_all_mem(void)
{
if (!global_lstate) {
return;
}
lua_State *lstate = global_lstate;
nlua_unref(lstate, nlua_nil_ref);
nlua_unref(lstate, nlua_empty_dict_ref);
#ifdef NLUA_TRACK_REFS
if (nlua_refcount) {
fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
}
if (nlua_ref_markers) {
// in case there are leaked luarefs, leak the associated memory
// to get LeakSanitizer stacktraces on exit
pmap_free(handle_T)(nlua_ref_markers);
}
#endif
nlua_refcount = 0;
lua_close(lstate);
}
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@ -866,17 +895,35 @@ static int nlua_getenv(lua_State *lstate)
}
#endif
/// add the value to the registry
LuaRef nlua_ref(lua_State *lstate, int index)
{
lua_pushvalue(lstate, index);
return luaL_ref(lstate, LUA_REGISTRYINDEX);
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
if (ref > 0) {
nlua_refcount++;
#ifdef NLUA_TRACK_REFS
if (nlua_ref_markers) {
// dummy allocation to make LeakSanitizer track our luarefs
pmap_put(handle_T)(nlua_ref_markers, ref, xmalloc(3));
}
#endif
}
return ref;
}
/// remove the value from the registry
void nlua_unref(lua_State *lstate, LuaRef ref)
{
if (ref > 0) {
nlua_refcount--;
#ifdef NLUA_TRACK_REFS
// NB: don't remove entry from map to track double-unref
if (nlua_ref_markers) {
xfree(pmap_get(handle_T)(nlua_ref_markers, ref));
}
#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
}
}
@ -893,19 +940,11 @@ void nlua_pushref(lua_State *lstate, LuaRef ref)
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
}
/// Gets a new reference to an object stored at original_ref
///
/// NOTE: It does not copy the value, it creates a new ref to the lua object.
/// Leaves the stack unchanged.
LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
{
nlua_pushref(lstate, original_ref);
LuaRef new_ref = nlua_ref(lstate, -1);
lua_pop(lstate, 1);
return new_ref;
}
LuaRef api_new_luaref(LuaRef original_ref)
{
if (original_ref == LUA_NOREF) {
@ -913,7 +952,10 @@ LuaRef api_new_luaref(LuaRef original_ref)
}
lua_State *const lstate = nlua_enter();
return nlua_newref(lstate, original_ref);
nlua_pushref(lstate, original_ref);
LuaRef new_ref = nlua_ref(lstate, -1);
lua_pop(lstate, 1);
return new_ref;
}
@ -1023,25 +1065,13 @@ int typval_exec_lua_callable(
typval_T *rettv
)
{
int offset = 0;
LuaRef cb = lua_cb.func_ref;
if (cb == LUA_NOREF) {
// This shouldn't happen.
luaL_error(lstate, "Invalid function passed to VimL");
return ERROR_OTHER;
}
nlua_pushref(lstate, cb);
if (lua_cb.table_ref != LUA_NOREF) {
offset += 1;
nlua_pushref(lstate, lua_cb.table_ref);
}
PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
if (lua_pcall(lstate, argcount + offset, 1, 0)) {
if (lua_pcall(lstate, argcount, 1, 0)) {
nlua_print(lstate);
return ERROR_OTHER;
}
@ -1508,6 +1538,8 @@ static int regex_match_line(lua_State *lstate)
return nret;
}
// Required functions for lua c functions as VimL callbacks
int nlua_CFunction_func_call(
int argcount,
typval_T *argvars,
@ -1517,53 +1549,40 @@ int nlua_CFunction_func_call(
lua_State *const lstate = nlua_enter();
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
return typval_exec_lua_callable(
lstate,
funcstate->lua_callable,
argcount,
argvars,
rettv);
return typval_exec_lua_callable(lstate, funcstate->lua_callable,
argcount, argvars, rettv);
}
/// Required functions for lua c functions as VimL callbacks
void nlua_CFunction_func_free(void *state)
{
lua_State *const lstate = nlua_enter();
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
nlua_unref(lstate, funcstate->lua_callable.func_ref);
nlua_unref(lstate, funcstate->lua_callable.table_ref);
xfree(funcstate);
}
bool nlua_is_table_from_lua(typval_T *const arg)
{
if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) {
return false;
}
if (arg->v_type == VAR_DICT) {
return arg->vval.v_dict->lua_table_ref > 0
&& arg->vval.v_dict->lua_table_ref != LUA_NOREF;
return arg->vval.v_dict->lua_table_ref != LUA_NOREF;
} else if (arg->v_type == VAR_LIST) {
return arg->vval.v_list->lua_table_ref > 0
&& arg->vval.v_list->lua_table_ref != LUA_NOREF;
}
return arg->vval.v_list->lua_table_ref != LUA_NOREF;
} else {
return false;
}
}
char_u *nlua_register_table_as_callable(typval_T *const arg)
{
if (!nlua_is_table_from_lua(arg)) {
return NULL;
}
LuaRef table_ref;
LuaRef table_ref = LUA_NOREF;
if (arg->v_type == VAR_DICT) {
table_ref = arg->vval.v_dict->lua_table_ref;
} else if (arg->v_type == VAR_LIST) {
table_ref = arg->vval.v_list->lua_table_ref;
} else {
}
if (table_ref == LUA_NOREF) {
return NULL;
}
@ -1573,55 +1592,34 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
int top = lua_gettop(lstate);
#endif
nlua_pushref(lstate, table_ref);
nlua_pushref(lstate, table_ref); // [table]
if (!lua_getmetatable(lstate, -1)) {
lua_pop(lstate, 1);
assert(top == lua_gettop(lstate));
return NULL;
}
} // [table, mt]
lua_getfield(lstate, -1, "__call");
lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call]
if (!lua_isfunction(lstate, -1)) {
lua_pop(lstate, 3);
assert(top == lua_gettop(lstate));
return NULL;
}
LuaRef new_table_ref = nlua_newref(lstate, table_ref);
lua_pop(lstate, 2); // [table]
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
state->lua_callable.func_ref = nlua_ref(lstate, -1);
state->lua_callable.table_ref = new_table_ref;
char_u *name = register_cfunc(
&nlua_CFunction_func_call,
&nlua_CFunction_func_free,
state);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free, state);
lua_pop(lstate, 3);
lua_pop(lstate, 1); // []
assert(top == lua_gettop(lstate));
return name;
}
/// Helper function to free a list_T
void nlua_free_typval_list(list_T *const l)
{
if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) {
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, l->lua_table_ref);
l->lua_table_ref = LUA_NOREF;
}
}
/// Helper function to free a dict_T
void nlua_free_typval_dict(dict_T *const d)
{
if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) {
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, d->lua_table_ref);
d->lua_table_ref = LUA_NOREF;
}
}
void nlua_execute_log_keystroke(int c)
{
char_u buf[NUMBUFLEN];

View File

@ -16,6 +16,8 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
EXTERN int nlua_refcount INIT(= 0);
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \

View File

@ -19,6 +19,8 @@
#include "nvim/ui.h"
#include "nvim/sign.h"
#include "nvim/api/vim.h"
#include "nvim/lua/executor.h"
#include "nvim/decoration.h"
#ifdef UNIT_TESTING
# define malloc(size) mem_malloc(size)
@ -695,6 +697,10 @@ void free_all_mem(void)
list_free_log();
check_quickfix_busy();
decor_free_all_mem();
nlua_free_all_mem();
}
#endif

View File

@ -29,6 +29,16 @@ teardown(function()
os.remove(fake_lsp_logfile)
end)
local function clear_notrace()
-- problem: here be dragons
-- solution: don't look for dragons to closely
clear {env={
NVIM_LUA_NOTRACK="1";
VIMRUNTIME=os.getenv"VIMRUNTIME";
}}
end
local function fake_lsp_server_setup(test_name, timeout_ms, options)
exec_lua([=[
lsp = require('vim.lsp')
@ -36,6 +46,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options)
TEST_RPC_CLIENT_ID = lsp.start_client {
cmd_env = {
NVIM_LOG_FILE = logfile;
NVIM_LUA_NOTRACK = "1";
};
cmd = {
vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
@ -65,7 +76,7 @@ end
local function test_rpc_server(config)
if config.test_name then
clear()
clear_notrace()
fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options)
end
local client = setmetatable({}, {
@ -120,7 +131,7 @@ end
describe('LSP', function()
describe('server_name specified', function()
before_each(function()
clear()
clear_notrace()
-- Run an instance of nvim on the file which contains our "scripts".
-- Pass TEST_NAME to pick the script.
local test_name = "basic_init"
@ -250,6 +261,10 @@ describe('LSP', function()
end)
it('should succeed with manual shutdown', function()
if 'openbsd' == helpers.uname() then
pending('hangs the build on openbsd #14028, re-enable with freeze timeout #14204')
return
end
local expected_callbacks = {
{NIL, "shutdown", {}, 1, NIL};
{NIL, "test", {}, 1};
@ -314,7 +329,7 @@ describe('LSP', function()
}
end)
it('workspace/configuration returns NIL per section if client was started without config.settings', function()
clear()
clear_notrace()
fake_lsp_server_setup('workspace/configuration no settings')
eq({ NIL, NIL, }, exec_lua [[
local params = {
@ -941,7 +956,7 @@ end)
describe('LSP', function()
before_each(function()
clear()
clear_notrace()
end)
local function make_edit(y_0, x_0, y_1, x_1, text)