changeset 17377:cb008de2a6ec v8.1.1687

patch 8.1.1687: the evalfunc.c file is too big commit https://github.com/vim/vim/commit/ecaa70ea29c269dd0dabd3cd5acdfa0ce42ccd54 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jul 14 14:55:39 2019 +0200 patch 8.1.1687: the evalfunc.c file is too big Problem: The evalfunc.c file is too big. Solution: Move testing support to a separate file.
author Bram Moolenaar <Bram@vim.org>
date Sun, 14 Jul 2019 15:00:05 +0200
parents da3299b8b5c0
children dd01b29f9ac3
files Filelist src/Make_cyg_ming.mak src/Make_morph.mak src/Make_mvc.mak src/Make_vms.mms src/Makefile src/README.md src/eval.c src/evalfunc.c src/proto.h src/proto/eval.pro src/proto/testing.pro src/testing.c src/version.c
diffstat 14 files changed, 1026 insertions(+), 1005 deletions(-) [+]
line wrap: on
line diff
--- a/Filelist
+++ b/Filelist
@@ -100,6 +100,7 @@ SRC_ALL =	\
 		src/terminal.c \
 		src/term.h \
 		src/termlib.c \
+		src/testing.c \
 		src/textprop.c \
 		src/ui.c \
 		src/undo.c \
@@ -224,6 +225,7 @@ SRC_ALL =	\
 		src/proto/term.pro \
 		src/proto/terminal.pro \
 		src/proto/termlib.pro \
+		src/proto/testing.pro \
 		src/proto/textprop.pro \
 		src/proto/ui.pro \
 		src/proto/undo.pro \
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -764,6 +764,7 @@ OBJ = \
 	$(OUTDIR)/syntax.o \
 	$(OUTDIR)/tag.o \
 	$(OUTDIR)/term.o \
+	$(OUTDIR)/testing.o \
 	$(OUTDIR)/textprop.o \
 	$(OUTDIR)/ui.o \
 	$(OUTDIR)/undo.o \
--- a/src/Make_morph.mak
+++ b/src/Make_morph.mak
@@ -81,6 +81,8 @@ SRC =	arabic.c						\
 	syntax.c						\
 	tag.c							\
 	term.c							\
+	testing.c						\
+	textprop.c						\
 	ui.c							\
 	undo.c							\
 	usercmd.c						\
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -773,6 +773,7 @@ OBJ = \
 	$(OUTDIR)\syntax.obj \
 	$(OUTDIR)\tag.obj \
 	$(OUTDIR)\term.obj \
+	$(OUTDIR)\testing.obj \
 	$(OUTDIR)\textprop.obj \
 	$(OUTDIR)\ui.obj \
 	$(OUTDIR)\undo.obj \
@@ -1620,6 +1621,8 @@ lib$(MZSCHEME_MAIN_LIB)$(MZSCHEME_VER).l
 
 $(OUTDIR)/term.obj:	$(OUTDIR) term.c  $(INCL)
 
+$(OUTDIR)/term.obj:	$(OUTDIR) testing.c  $(INCL)
+
 $(OUTDIR)/textprop.obj:	$(OUTDIR) textprop.c  $(INCL)
 
 $(OUTDIR)/ui.obj:	$(OUTDIR) ui.c  $(INCL)
@@ -1778,6 +1781,7 @@ proto.h: \
 	proto/syntax.pro \
 	proto/tag.pro \
 	proto/term.pro \
+	proto/testing.pro \
 	proto/textprop.pro \
 	proto/ui.pro \
 	proto/undo.pro \
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -2,7 +2,7 @@
 # Makefile for Vim on OpenVMS
 #
 # Maintainer:   Zoltan Arpadffy <arpadffy@polarhome.com>
-# Last change:  2019 May 24
+# Last change:  2019 Jul 14
 #
 # This has script been tested on VMS 6.2 to 8.2 on DEC Alpha, VAX and IA64
 # with MMS and MMK
@@ -315,7 +315,7 @@ SRC =	arabic.c autocmd.c beval.c blob.c 
 	menu.c mbyte.c memfile.c memline.c message.c misc1.c misc2.c move.c \
 	normal.c ops.c option.c popupmnu.c popupwin.c profiler.c quickfix.c \
 	regexp.c search.c sha256.c sign.c spell.c spellfile.c syntax.c tag.c \
-	term.c termlib.c textprop.c ui.c undo.c usercmd.c userfunc.c \
+	term.c termlib.c testing.c textprop.c ui.c undo.c usercmd.c userfunc.c \
 	version.c screen.c window.c os_unix.c os_vms.c pathdef.c \
 	$(GUI_SRC) $(PERL_SRC) $(PYTHON_SRC) $(TCL_SRC) \
  	$(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC)
@@ -330,7 +330,7 @@ OBJ = 	arabic.obj autocmd.obj beval.obj 
 	move.obj mbyte.obj normal.obj ops.obj option.obj popupmnu.obj \
 	popupwin.obj profiler.obj quickfix.obj regexp.obj search.obj \
 	sha256.obj sign.obj spell.obj spellfile.obj syntax.obj tag.obj \
-	term.obj termlib.obj textprop.obj ui.obj undo.obj usercmd.obj \
+	term.obj termlib.obj testing.obj textprop.obj ui.obj undo.obj usercmd.obj \
 	userfunc.obj screen.obj version.obj window.obj os_unix.obj os_vms.obj \
 	pathdef.obj if_mzsch.obj \
 	$(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \
@@ -745,6 +745,10 @@ termlib.obj : termlib.c vim.h [.auto]con
  ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \
  [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \
 
+testing.obj : testing.c vim.h [.auto]config.h feature.h os_unix.h   \
+ ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \
+ [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \
+
 textprop.obj : textprop.c vim.h [.auto]config.h feature.h os_unix.h   \
  ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \
  [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \
--- a/src/Makefile
+++ b/src/Makefile
@@ -1642,6 +1642,7 @@ BASIC_SRC = \
 	tag.c \
 	term.c \
 	terminal.c \
+	testing.c \
 	textprop.c \
 	ui.c \
 	undo.c \
@@ -1759,6 +1760,7 @@ OBJ_COMMON = \
 	objects/tag.o \
 	objects/term.o \
 	objects/terminal.o \
+	objects/testing.o \
 	objects/textprop.o \
 	objects/ui.o \
 	objects/undo.o \
@@ -1902,6 +1904,7 @@ PRO_AUTO = \
 	term.pro \
 	terminal.pro \
 	termlib.pro \
+	testing.pro \
 	textprop.pro \
 	ui.pro \
 	undo.pro \
@@ -3271,6 +3274,9 @@ objects/term.o: term.c
 objects/terminal.o: terminal.c $(TERM_DEPS)
 	$(CCC) -o $@ terminal.c
 
+objects/testing.o: testing.c
+	$(CCC) -o $@ testing.c
+
 objects/textprop.o: textprop.c
 	$(CCC) -o $@ textprop.c
 
@@ -3702,6 +3708,10 @@ objects/terminal.o: terminal.c vim.h pro
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h libvterm/include/vterm.h \
  libvterm/include/vterm_keycodes.h
+objects/testing.o: testing.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/textprop.o: textprop.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
--- a/src/README.md
+++ b/src/README.md
@@ -21,8 +21,8 @@ To jump to a file, move the cursor on it
 
 Most code can be found in a file with an obvious name (incomplete list):
 
-File name | Description
---------- | -----------
+File name       | Description
+--------------- | -----------
 autocmd.c	| autocommands
 buffer.c	| manipulating buffers (loaded files)
 change.c	| handling changes to text
@@ -44,6 +44,8 @@ menu.c		| menus
 message.c	| (error) messages
 ops.c		| handling operators ("d", "y", "p")
 option.c	| options
+popupmnu.c	| popup menu
+popupwin.c	| popup window
 profiler.c	| vim script profiler
 quickfix.c	| quickfix commands (":make", ":cn")
 regexp.c	| pattern matching
@@ -54,6 +56,8 @@ spell.c		| spell checking
 syntax.c	| syntax and other highlighting
 tag.c		| tags
 term.c		| terminal handling, termcap codes
+testing.c	| testing: assert and test functions
+textprop.c	| text properties
 undo.c		| undo and redo
 usercmd.c	| user defined commands
 userfunc.c	| user defined functions
--- a/src/eval.c
+++ b/src/eval.c
@@ -3621,7 +3621,7 @@ get_user_var_name(expand_T *xp, int idx)
  * Return TRUE if "pat" matches "text".
  * Does not use 'cpo' and always uses 'magic'.
  */
-    static int
+    int
 pattern_match(char_u *pat, char_u *text, int ic)
 {
     int		matches = FALSE;
@@ -6990,6 +6990,15 @@ set_vim_var_nr(int idx, varnumber_T val)
 }
 
 /*
+ * Get typval_T v: variable value.
+ */
+    typval_T *
+get_vim_var_tv(int idx)
+{
+    return &vimvars[idx].vv_tv;
+}
+
+/*
  * Get number v: variable value.
  */
     varnumber_T
@@ -9592,30 +9601,6 @@ reset_v_option_vars(void)
 }
 
 /*
- * Prepare "gap" for an assert error and add the sourcing position.
- */
-    void
-prepare_assert_error(garray_T *gap)
-{
-    char buf[NUMBUFLEN];
-
-    ga_init2(gap, 1, 100);
-    if (sourcing_name != NULL)
-    {
-	ga_concat(gap, sourcing_name);
-	if (sourcing_lnum > 0)
-	    ga_concat(gap, (char_u *)" ");
-    }
-    if (sourcing_lnum > 0)
-    {
-	sprintf(buf, "line %ld", (long)sourcing_lnum);
-	ga_concat(gap, (char_u *)buf);
-    }
-    if (sourcing_name != NULL || sourcing_lnum > 0)
-	ga_concat(gap, (char_u *)": ");
-}
-
-/*
  * Add an assert error to v:errors.
  */
     void
@@ -9628,477 +9613,6 @@ assert_error(garray_T *gap)
 	set_vim_var_list(VV_ERRORS, list_alloc());
     list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, gap->ga_len);
 }
-
-    int
-assert_equal_common(typval_T *argvars, assert_type_T atype)
-{
-    garray_T	ga;
-
-    if (tv_equal(&argvars[0], &argvars[1], FALSE, FALSE)
-						   != (atype == ASSERT_EQUAL))
-    {
-	prepare_assert_error(&ga);
-	fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1],
-								       atype);
-	assert_error(&ga);
-	ga_clear(&ga);
-	return 1;
-    }
-    return 0;
-}
-
-    int
-assert_equalfile(typval_T *argvars)
-{
-    char_u	buf1[NUMBUFLEN];
-    char_u	buf2[NUMBUFLEN];
-    char_u	*fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
-    char_u	*fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
-    garray_T	ga;
-    FILE	*fd1;
-    FILE	*fd2;
-
-    if (fname1 == NULL || fname2 == NULL)
-	return 0;
-
-    IObuff[0] = NUL;
-    fd1 = mch_fopen((char *)fname1, READBIN);
-    if (fd1 == NULL)
-    {
-	vim_snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1);
-    }
-    else
-    {
-	fd2 = mch_fopen((char *)fname2, READBIN);
-	if (fd2 == NULL)
-	{
-	    fclose(fd1);
-	    vim_snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2);
-	}
-	else
-	{
-	    int c1, c2;
-	    long count = 0;
-
-	    for (;;)
-	    {
-		c1 = fgetc(fd1);
-		c2 = fgetc(fd2);
-		if (c1 == EOF)
-		{
-		    if (c2 != EOF)
-			STRCPY(IObuff, "first file is shorter");
-		    break;
-		}
-		else if (c2 == EOF)
-		{
-		    STRCPY(IObuff, "second file is shorter");
-		    break;
-		}
-		else if (c1 != c2)
-		{
-		    vim_snprintf((char *)IObuff, IOSIZE,
-					      "difference at byte %ld", count);
-		    break;
-		}
-		++count;
-	    }
-	    fclose(fd1);
-	    fclose(fd2);
-	}
-    }
-    if (IObuff[0] != NUL)
-    {
-	prepare_assert_error(&ga);
-	ga_concat(&ga, IObuff);
-	assert_error(&ga);
-	ga_clear(&ga);
-	return 1;
-    }
-    return 0;
-}
-
-    int
-assert_match_common(typval_T *argvars, assert_type_T atype)
-{
-    garray_T	ga;
-    char_u	buf1[NUMBUFLEN];
-    char_u	buf2[NUMBUFLEN];
-    char_u	*pat = tv_get_string_buf_chk(&argvars[0], buf1);
-    char_u	*text = tv_get_string_buf_chk(&argvars[1], buf2);
-
-    if (pat == NULL || text == NULL)
-	emsg(_(e_invarg));
-    else if (pattern_match(pat, text, FALSE) != (atype == ASSERT_MATCH))
-    {
-	prepare_assert_error(&ga);
-	fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1],
-									atype);
-	assert_error(&ga);
-	ga_clear(&ga);
-	return 1;
-    }
-    return 0;
-}
-
-    int
-assert_inrange(typval_T *argvars)
-{
-    garray_T	ga;
-    int		error = FALSE;
-    char_u	*tofree;
-    char	msg[200];
-    char_u	numbuf[NUMBUFLEN];
-
-#ifdef FEAT_FLOAT
-    if (argvars[0].v_type == VAR_FLOAT
-	    || argvars[1].v_type == VAR_FLOAT
-	    || argvars[2].v_type == VAR_FLOAT)
-    {
-	float_T flower = tv_get_float(&argvars[0]);
-	float_T fupper = tv_get_float(&argvars[1]);
-	float_T factual = tv_get_float(&argvars[2]);
-
-	if (factual < flower || factual > fupper)
-	{
-	    prepare_assert_error(&ga);
-	    if (argvars[3].v_type != VAR_UNKNOWN)
-	    {
-		ga_concat(&ga, tv2string(&argvars[3], &tofree, numbuf, 0));
-		vim_free(tofree);
-	    }
-	    else
-	    {
-		vim_snprintf(msg, 200, "Expected range %g - %g, but got %g",
-						      flower, fupper, factual);
-		ga_concat(&ga, (char_u *)msg);
-	    }
-	    assert_error(&ga);
-	    ga_clear(&ga);
-	    return 1;
-	}
-    }
-    else
-#endif
-    {
-	varnumber_T	lower = tv_get_number_chk(&argvars[0], &error);
-	varnumber_T	upper = tv_get_number_chk(&argvars[1], &error);
-	varnumber_T	actual = tv_get_number_chk(&argvars[2], &error);
-
-	if (error)
-	    return 0;
-	if (actual < lower || actual > upper)
-	{
-	    prepare_assert_error(&ga);
-	    if (argvars[3].v_type != VAR_UNKNOWN)
-	    {
-		ga_concat(&ga, tv2string(&argvars[3], &tofree, numbuf, 0));
-		vim_free(tofree);
-	    }
-	    else
-	    {
-		vim_snprintf(msg, 200, "Expected range %ld - %ld, but got %ld",
-				       (long)lower, (long)upper, (long)actual);
-		ga_concat(&ga, (char_u *)msg);
-	    }
-	    assert_error(&ga);
-	    ga_clear(&ga);
-	    return 1;
-	}
-    }
-    return 0;
-}
-
-/*
- * Common for assert_true() and assert_false().
- * Return non-zero for failure.
- */
-    int
-assert_bool(typval_T *argvars, int isTrue)
-{
-    int		error = FALSE;
-    garray_T	ga;
-
-    if (argvars[0].v_type == VAR_SPECIAL
-	    && argvars[0].vval.v_number == (isTrue ? VVAL_TRUE : VVAL_FALSE))
-	return 0;
-    if (argvars[0].v_type != VAR_NUMBER
-	    || (tv_get_number_chk(&argvars[0], &error) == 0) == isTrue
-	    || error)
-    {
-	prepare_assert_error(&ga);
-	fill_assert_error(&ga, &argvars[1],
-		(char_u *)(isTrue ? "True" : "False"),
-		NULL, &argvars[0], ASSERT_OTHER);
-	assert_error(&ga);
-	ga_clear(&ga);
-	return 1;
-    }
-    return 0;
-}
-
-    int
-assert_report(typval_T *argvars)
-{
-    garray_T	ga;
-
-    prepare_assert_error(&ga);
-    ga_concat(&ga, tv_get_string(&argvars[0]));
-    assert_error(&ga);
-    ga_clear(&ga);
-    return 1;
-}
-
-    int
-assert_exception(typval_T *argvars)
-{
-    garray_T	ga;
-    char_u	*error = tv_get_string_chk(&argvars[0]);
-
-    if (vimvars[VV_EXCEPTION].vv_str == NULL)
-    {
-	prepare_assert_error(&ga);
-	ga_concat(&ga, (char_u *)"v:exception is not set");
-	assert_error(&ga);
-	ga_clear(&ga);
-	return 1;
-    }
-    else if (error != NULL
-	&& strstr((char *)vimvars[VV_EXCEPTION].vv_str, (char *)error) == NULL)
-    {
-	prepare_assert_error(&ga);
-	fill_assert_error(&ga, &argvars[1], NULL, &argvars[0],
-				  &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER);
-	assert_error(&ga);
-	ga_clear(&ga);
-	return 1;
-    }
-    return 0;
-}
-
-    int
-assert_beeps(typval_T *argvars)
-{
-    char_u	*cmd = tv_get_string_chk(&argvars[0]);
-    garray_T	ga;
-    int		ret = 0;
-
-    called_vim_beep = FALSE;
-    suppress_errthrow = TRUE;
-    emsg_silent = FALSE;
-    do_cmdline_cmd(cmd);
-    if (!called_vim_beep)
-    {
-	prepare_assert_error(&ga);
-	ga_concat(&ga, (char_u *)"command did not beep: ");
-	ga_concat(&ga, cmd);
-	assert_error(&ga);
-	ga_clear(&ga);
-	ret = 1;
-    }
-
-    suppress_errthrow = FALSE;
-    emsg_on_display = FALSE;
-    return ret;
-}
-
-    static void
-assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, char_u *cmd)
-{
-    char_u	*tofree;
-    char_u	numbuf[NUMBUFLEN];
-
-    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN)
-    {
-	ga_concat(gap, echo_string(&argvars[2], &tofree, numbuf, 0));
-	vim_free(tofree);
-    }
-    else
-	ga_concat(gap, cmd);
-}
-
-    int
-assert_fails(typval_T *argvars)
-{
-    char_u	*cmd = tv_get_string_chk(&argvars[0]);
-    garray_T	ga;
-    int		ret = 0;
-    int		save_trylevel = trylevel;
-
-    // trylevel must be zero for a ":throw" command to be considered failed
-    trylevel = 0;
-    called_emsg = FALSE;
-    suppress_errthrow = TRUE;
-    emsg_silent = TRUE;
-
-    do_cmdline_cmd(cmd);
-    if (!called_emsg)
-    {
-	prepare_assert_error(&ga);
-	ga_concat(&ga, (char_u *)"command did not fail: ");
-	assert_append_cmd_or_arg(&ga, argvars, cmd);
-	assert_error(&ga);
-	ga_clear(&ga);
-	ret = 1;
-    }
-    else if (argvars[1].v_type != VAR_UNKNOWN)
-    {
-	char_u	buf[NUMBUFLEN];
-	char	*error = (char *)tv_get_string_buf_chk(&argvars[1], buf);
-
-	if (error == NULL
-		  || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL)
-	{
-	    prepare_assert_error(&ga);
-	    fill_assert_error(&ga, &argvars[2], NULL, &argvars[1],
-				     &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER);
-	    ga_concat(&ga, (char_u *)": ");
-	    assert_append_cmd_or_arg(&ga, argvars, cmd);
-	    assert_error(&ga);
-	    ga_clear(&ga);
-	    ret = 1;
-	}
-    }
-
-    trylevel = save_trylevel;
-    called_emsg = FALSE;
-    suppress_errthrow = FALSE;
-    emsg_silent = FALSE;
-    emsg_on_display = FALSE;
-    set_vim_var_string(VV_ERRMSG, NULL, 0);
-    return ret;
-}
-
-/*
- * Append "p[clen]" to "gap", escaping unprintable characters.
- * Changes NL to \n, CR to \r, etc.
- */
-    static void
-ga_concat_esc(garray_T *gap, char_u *p, int clen)
-{
-    char_u  buf[NUMBUFLEN];
-
-    if (clen > 1)
-    {
-	mch_memmove(buf, p, clen);
-	buf[clen] = NUL;
-	ga_concat(gap, buf);
-    }
-    else switch (*p)
-    {
-	case BS: ga_concat(gap, (char_u *)"\\b"); break;
-	case ESC: ga_concat(gap, (char_u *)"\\e"); break;
-	case FF: ga_concat(gap, (char_u *)"\\f"); break;
-	case NL: ga_concat(gap, (char_u *)"\\n"); break;
-	case TAB: ga_concat(gap, (char_u *)"\\t"); break;
-	case CAR: ga_concat(gap, (char_u *)"\\r"); break;
-	case '\\': ga_concat(gap, (char_u *)"\\\\"); break;
-	default:
-		   if (*p < ' ')
-		   {
-		       vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p);
-		       ga_concat(gap, buf);
-		   }
-		   else
-		       ga_append(gap, *p);
-		   break;
-    }
-}
-
-/*
- * Append "str" to "gap", escaping unprintable characters.
- * Changes NL to \n, CR to \r, etc.
- */
-    static void
-ga_concat_shorten_esc(garray_T *gap, char_u *str)
-{
-    char_u  *p;
-    char_u  *s;
-    int	    c;
-    int	    clen;
-    char_u  buf[NUMBUFLEN];
-    int	    same_len;
-
-    if (str == NULL)
-    {
-	ga_concat(gap, (char_u *)"NULL");
-	return;
-    }
-
-    for (p = str; *p != NUL; ++p)
-    {
-	same_len = 1;
-	s = p;
-	c = mb_ptr2char_adv(&s);
-	clen = s - p;
-	while (*s != NUL && c == mb_ptr2char(s))
-	{
-	    ++same_len;
-	    s += clen;
-	}
-	if (same_len > 20)
-	{
-	    ga_concat(gap, (char_u *)"\\[");
-	    ga_concat_esc(gap, p, clen);
-	    ga_concat(gap, (char_u *)" occurs ");
-	    vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len);
-	    ga_concat(gap, buf);
-	    ga_concat(gap, (char_u *)" times]");
-	    p = s - 1;
-	}
-	else
-	    ga_concat_esc(gap, p, clen);
-    }
-}
-
-/*
- * Fill "gap" with information about an assert error.
- */
-    void
-fill_assert_error(
-    garray_T	*gap,
-    typval_T	*opt_msg_tv,
-    char_u      *exp_str,
-    typval_T	*exp_tv,
-    typval_T	*got_tv,
-    assert_type_T atype)
-{
-    char_u	numbuf[NUMBUFLEN];
-    char_u	*tofree;
-
-    if (opt_msg_tv->v_type != VAR_UNKNOWN)
-    {
-	ga_concat(gap, echo_string(opt_msg_tv, &tofree, numbuf, 0));
-	vim_free(tofree);
-	ga_concat(gap, (char_u *)": ");
-    }
-
-    if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH)
-	ga_concat(gap, (char_u *)"Pattern ");
-    else if (atype == ASSERT_NOTEQUAL)
-	ga_concat(gap, (char_u *)"Expected not equal to ");
-    else
-	ga_concat(gap, (char_u *)"Expected ");
-    if (exp_str == NULL)
-    {
-	ga_concat_shorten_esc(gap, tv2string(exp_tv, &tofree, numbuf, 0));
-	vim_free(tofree);
-    }
-    else
-	ga_concat_shorten_esc(gap, exp_str);
-    if (atype != ASSERT_NOTEQUAL)
-    {
-	if (atype == ASSERT_MATCH)
-	    ga_concat(gap, (char_u *)" does not match ");
-	else if (atype == ASSERT_NOTMATCH)
-	    ga_concat(gap, (char_u *)" does match ");
-	else
-	    ga_concat(gap, (char_u *)" but got ");
-	ga_concat_shorten_esc(gap, tv2string(got_tv, &tofree, numbuf, 0));
-	vim_free(tofree);
-    }
-}
-
 /*
  * Compare "typ1" and "typ2".  Put the result in "typ1".
  */
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -41,18 +41,6 @@ static void f_argc(typval_T *argvars, ty
 static void f_argidx(typval_T *argvars, typval_T *rettv);
 static void f_arglistid(typval_T *argvars, typval_T *rettv);
 static void f_argv(typval_T *argvars, typval_T *rettv);
-static void f_assert_beeps(typval_T *argvars, typval_T *rettv);
-static void f_assert_equal(typval_T *argvars, typval_T *rettv);
-static void f_assert_equalfile(typval_T *argvars, typval_T *rettv);
-static void f_assert_exception(typval_T *argvars, typval_T *rettv);
-static void f_assert_fails(typval_T *argvars, typval_T *rettv);
-static void f_assert_false(typval_T *argvars, typval_T *rettv);
-static void f_assert_inrange(typval_T *argvars, typval_T *rettv);
-static void f_assert_match(typval_T *argvars, typval_T *rettv);
-static void f_assert_notequal(typval_T *argvars, typval_T *rettv);
-static void f_assert_notmatch(typval_T *argvars, typval_T *rettv);
-static void f_assert_report(typval_T *argvars, typval_T *rettv);
-static void f_assert_true(typval_T *argvars, typval_T *rettv);
 #ifdef FEAT_FLOAT
 static void f_asin(typval_T *argvars, typval_T *rettv);
 static void f_atan(typval_T *argvars, typval_T *rettv);
@@ -396,34 +384,6 @@ static void f_tabpagewinnr(typval_T *arg
 static void f_taglist(typval_T *argvars, typval_T *rettv);
 static void f_tagfiles(typval_T *argvars, typval_T *rettv);
 static void f_tempname(typval_T *argvars, typval_T *rettv);
-static void f_test_alloc_fail(typval_T *argvars, typval_T *rettv);
-static void f_test_autochdir(typval_T *argvars, typval_T *rettv);
-static void f_test_feedinput(typval_T *argvars, typval_T *rettv);
-static void f_test_getvalue(typval_T *argvars, typval_T *rettv);
-static void f_test_option_not_set(typval_T *argvars, typval_T *rettv);
-static void f_test_override(typval_T *argvars, typval_T *rettv);
-static void f_test_refcount(typval_T *argvars, typval_T *rettv);
-static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv);
-static void f_test_garbagecollect_soon(typval_T *argvars, typval_T *rettv);
-static void f_test_ignore_error(typval_T *argvars, typval_T *rettv);
-static void f_test_null_blob(typval_T *argvars, typval_T *rettv);
-#ifdef FEAT_JOB_CHANNEL
-static void f_test_null_channel(typval_T *argvars, typval_T *rettv);
-#endif
-static void f_test_null_dict(typval_T *argvars, typval_T *rettv);
-#ifdef FEAT_JOB_CHANNEL
-static void f_test_null_job(typval_T *argvars, typval_T *rettv);
-#endif
-static void f_test_null_list(typval_T *argvars, typval_T *rettv);
-static void f_test_null_partial(typval_T *argvars, typval_T *rettv);
-static void f_test_null_string(typval_T *argvars, typval_T *rettv);
-#ifdef FEAT_GUI
-static void f_test_scrollbar(typval_T *argvars, typval_T *rettv);
-#endif
-#ifdef FEAT_MOUSE
-static void f_test_setmouse(typval_T *argvars, typval_T *rettv);
-#endif
-static void f_test_settime(typval_T *argvars, typval_T *rettv);
 #ifdef FEAT_FLOAT
 static void f_tan(typval_T *argvars, typval_T *rettv);
 static void f_tanh(typval_T *argvars, typval_T *rettv);
@@ -1609,114 +1569,6 @@ f_argv(typval_T *argvars, typval_T *rett
 	get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
 }
 
-/*
- * "assert_beeps(cmd [, error])" function
- */
-    static void
-f_assert_beeps(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_beeps(argvars);
-}
-
-/*
- * "assert_equal(expected, actual[, msg])" function
- */
-    static void
-f_assert_equal(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
-}
-
-/*
- * "assert_equalfile(fname-one, fname-two)" function
- */
-    static void
-f_assert_equalfile(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_equalfile(argvars);
-}
-
-/*
- * "assert_notequal(expected, actual[, msg])" function
- */
-    static void
-f_assert_notequal(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
-}
-
-/*
- * "assert_exception(string[, msg])" function
- */
-    static void
-f_assert_exception(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_exception(argvars);
-}
-
-/*
- * "assert_fails(cmd [, error[, msg]])" function
- */
-    static void
-f_assert_fails(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_fails(argvars);
-}
-
-/*
- * "assert_false(actual[, msg])" function
- */
-    static void
-f_assert_false(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_bool(argvars, FALSE);
-}
-
-/*
- * "assert_inrange(lower, upper[, msg])" function
- */
-    static void
-f_assert_inrange(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_inrange(argvars);
-}
-
-/*
- * "assert_match(pattern, actual[, msg])" function
- */
-    static void
-f_assert_match(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
-}
-
-/*
- * "assert_notmatch(pattern, actual[, msg])" function
- */
-    static void
-f_assert_notmatch(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
-}
-
-/*
- * "assert_report(msg)" function
- */
-    static void
-f_assert_report(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_report(argvars);
-}
-
-/*
- * "assert_true(actual[, msg])" function
- */
-    static void
-f_assert_true(typval_T *argvars, typval_T *rettv)
-{
-    rettv->vval.v_number = assert_bool(argvars, TRUE);
-}
-
 #ifdef FEAT_FLOAT
 /*
  * "asin()" function
@@ -13547,351 +13399,6 @@ f_tanh(typval_T *argvars, typval_T *rett
 #endif
 
 /*
- * "test_alloc_fail(id, countdown, repeat)" function
- */
-    static void
-f_test_alloc_fail(typval_T *argvars, typval_T *rettv UNUSED)
-{
-    if (argvars[0].v_type != VAR_NUMBER
-	    || argvars[0].vval.v_number <= 0
-	    || argvars[1].v_type != VAR_NUMBER
-	    || argvars[1].vval.v_number < 0
-	    || argvars[2].v_type != VAR_NUMBER)
-	emsg(_(e_invarg));
-    else
-    {
-	alloc_fail_id = argvars[0].vval.v_number;
-	if (alloc_fail_id >= aid_last)
-	    emsg(_(e_invarg));
-	alloc_fail_countdown = argvars[1].vval.v_number;
-	alloc_fail_repeat = argvars[2].vval.v_number;
-	did_outofmem_msg = FALSE;
-    }
-}
-
-/*
- * "test_autochdir()"
- */
-    static void
-f_test_autochdir(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
-{
-#if defined(FEAT_AUTOCHDIR)
-    test_autochdir = TRUE;
-#endif
-}
-
-/*
- * "test_feedinput()"
- */
-    static void
-f_test_feedinput(typval_T *argvars, typval_T *rettv UNUSED)
-{
-#ifdef USE_INPUT_BUF
-    char_u	*val = tv_get_string_chk(&argvars[0]);
-
-    if (val != NULL)
-    {
-	trash_input_buf();
-	add_to_input_buf_csi(val, (int)STRLEN(val));
-    }
-#endif
-}
-
-/*
- * "test_getvalue({name})" function
- */
-    static void
-f_test_getvalue(typval_T *argvars, typval_T *rettv)
-{
-    if (argvars[0].v_type != VAR_STRING)
-	emsg(_(e_invarg));
-    else
-    {
-	char_u *name = tv_get_string(&argvars[0]);
-
-	if (STRCMP(name, (char_u *)"need_fileinfo") == 0)
-	    rettv->vval.v_number = need_fileinfo;
-	else
-	    semsg(_(e_invarg2), name);
-    }
-}
-
-/*
- * "test_option_not_set({name})" function
- */
-    static void
-f_test_option_not_set(typval_T *argvars, typval_T *rettv UNUSED)
-{
-    char_u *name = (char_u *)"";
-
-    if (argvars[0].v_type != VAR_STRING)
-	emsg(_(e_invarg));
-    else
-    {
-	name = tv_get_string(&argvars[0]);
-	if (reset_option_was_set(name) == FAIL)
-	    semsg(_(e_invarg2), name);
-    }
-}
-
-/*
- * "test_override({name}, {val})" function
- */
-    static void
-f_test_override(typval_T *argvars, typval_T *rettv UNUSED)
-{
-    char_u *name = (char_u *)"";
-    int     val;
-    static int save_starting = -1;
-
-    if (argvars[0].v_type != VAR_STRING
-	    || (argvars[1].v_type) != VAR_NUMBER)
-	emsg(_(e_invarg));
-    else
-    {
-	name = tv_get_string(&argvars[0]);
-	val = (int)tv_get_number(&argvars[1]);
-
-	if (STRCMP(name, (char_u *)"redraw") == 0)
-	    disable_redraw_for_testing = val;
-	else if (STRCMP(name, (char_u *)"redraw_flag") == 0)
-	    ignore_redraw_flag_for_testing = val;
-	else if (STRCMP(name, (char_u *)"char_avail") == 0)
-	    disable_char_avail_for_testing = val;
-	else if (STRCMP(name, (char_u *)"starting") == 0)
-	{
-	    if (val)
-	    {
-		if (save_starting < 0)
-		    save_starting = starting;
-		starting = 0;
-	    }
-	    else
-	    {
-		starting = save_starting;
-		save_starting = -1;
-	    }
-	}
-	else if (STRCMP(name, (char_u *)"nfa_fail") == 0)
-	    nfa_fail_for_testing = val;
-	else if (STRCMP(name, (char_u *)"no_query_mouse") == 0)
-	    no_query_mouse_for_testing = val;
-	else if (STRCMP(name, (char_u *)"no_wait_return") == 0)
-	    no_wait_return = val;
-	else if (STRCMP(name, (char_u *)"ALL") == 0)
-	{
-	    disable_char_avail_for_testing = FALSE;
-	    disable_redraw_for_testing = FALSE;
-	    ignore_redraw_flag_for_testing = FALSE;
-	    nfa_fail_for_testing = FALSE;
-	    no_query_mouse_for_testing = FALSE;
-	    if (save_starting >= 0)
-	    {
-		starting = save_starting;
-		save_starting = -1;
-	    }
-	}
-	else
-	    semsg(_(e_invarg2), name);
-    }
-}
-
-/*
- * "test_refcount({expr})" function
- */
-    static void
-f_test_refcount(typval_T *argvars, typval_T *rettv)
-{
-    int retval = -1;
-
-    switch (argvars[0].v_type)
-    {
-	case VAR_UNKNOWN:
-	case VAR_NUMBER:
-	case VAR_FLOAT:
-	case VAR_SPECIAL:
-	case VAR_STRING:
-	    break;
-	case VAR_JOB:
-#ifdef FEAT_JOB_CHANNEL
-	    if (argvars[0].vval.v_job != NULL)
-		retval = argvars[0].vval.v_job->jv_refcount - 1;
-#endif
-	    break;
-	case VAR_CHANNEL:
-#ifdef FEAT_JOB_CHANNEL
-	    if (argvars[0].vval.v_channel != NULL)
-		retval = argvars[0].vval.v_channel->ch_refcount - 1;
-#endif
-	    break;
-	case VAR_FUNC:
-	    if (argvars[0].vval.v_string != NULL)
-	    {
-		ufunc_T *fp;
-
-		fp = find_func(argvars[0].vval.v_string);
-		if (fp != NULL)
-		    retval = fp->uf_refcount;
-	    }
-	    break;
-	case VAR_PARTIAL:
-	    if (argvars[0].vval.v_partial != NULL)
-		retval = argvars[0].vval.v_partial->pt_refcount - 1;
-	    break;
-	case VAR_BLOB:
-	    if (argvars[0].vval.v_blob != NULL)
-		retval = argvars[0].vval.v_blob->bv_refcount - 1;
-	    break;
-	case VAR_LIST:
-	    if (argvars[0].vval.v_list != NULL)
-		retval = argvars[0].vval.v_list->lv_refcount - 1;
-	    break;
-	case VAR_DICT:
-	    if (argvars[0].vval.v_dict != NULL)
-		retval = argvars[0].vval.v_dict->dv_refcount - 1;
-	    break;
-    }
-
-    rettv->v_type = VAR_NUMBER;
-    rettv->vval.v_number = retval;
-
-}
-
-/*
- * "test_garbagecollect_now()" function
- */
-    static void
-f_test_garbagecollect_now(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
-{
-    /* This is dangerous, any Lists and Dicts used internally may be freed
-     * while still in use. */
-    garbage_collect(TRUE);
-}
-
-/*
- * "test_garbagecollect_soon()" function
- */
-    static void
-f_test_garbagecollect_soon(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
-{
-    may_garbage_collect = TRUE;
-}
-
-/*
- * "test_ignore_error()" function
- */
-    static void
-f_test_ignore_error(typval_T *argvars, typval_T *rettv UNUSED)
-{
-     ignore_error_for_testing(tv_get_string(&argvars[0]));
-}
-
-    static void
-f_test_null_blob(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv->v_type = VAR_BLOB;
-    rettv->vval.v_blob = NULL;
-}
-
-#ifdef FEAT_JOB_CHANNEL
-    static void
-f_test_null_channel(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv->v_type = VAR_CHANNEL;
-    rettv->vval.v_channel = NULL;
-}
-#endif
-
-    static void
-f_test_null_dict(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv_dict_set(rettv, NULL);
-}
-
-#ifdef FEAT_JOB_CHANNEL
-    static void
-f_test_null_job(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv->v_type = VAR_JOB;
-    rettv->vval.v_job = NULL;
-}
-#endif
-
-    static void
-f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv_list_set(rettv, NULL);
-}
-
-    static void
-f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv->v_type = VAR_PARTIAL;
-    rettv->vval.v_partial = NULL;
-}
-
-    static void
-f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv)
-{
-    rettv->v_type = VAR_STRING;
-    rettv->vval.v_string = NULL;
-}
-
-#ifdef FEAT_GUI
-    static void
-f_test_scrollbar(typval_T *argvars, typval_T *rettv UNUSED)
-{
-    char_u	*which;
-    long	value;
-    int		dragging;
-    scrollbar_T *sb = NULL;
-
-    if (argvars[0].v_type != VAR_STRING
-	    || (argvars[1].v_type) != VAR_NUMBER
-	    || (argvars[2].v_type) != VAR_NUMBER)
-    {
-	emsg(_(e_invarg));
-	return;
-    }
-    which = tv_get_string(&argvars[0]);
-    value = tv_get_number(&argvars[1]);
-    dragging = tv_get_number(&argvars[2]);
-
-    if (STRCMP(which, "left") == 0)
-	sb = &curwin->w_scrollbars[SBAR_LEFT];
-    else if (STRCMP(which, "right") == 0)
-	sb = &curwin->w_scrollbars[SBAR_RIGHT];
-    else if (STRCMP(which, "hor") == 0)
-	sb = &gui.bottom_sbar;
-    if (sb == NULL)
-    {
-	semsg(_(e_invarg2), which);
-	return;
-    }
-    gui_drag_scrollbar(sb, value, dragging);
-# ifndef USE_ON_FLY_SCROLL
-    // need to loop through normal_cmd() to handle the scroll events
-    exec_normal(FALSE, TRUE, FALSE);
-# endif
-}
-#endif
-
-#ifdef FEAT_MOUSE
-    static void
-f_test_setmouse(typval_T *argvars, typval_T *rettv UNUSED)
-{
-    mouse_row = (time_t)tv_get_number(&argvars[0]) - 1;
-    mouse_col = (time_t)tv_get_number(&argvars[1]) - 1;
-}
-#endif
-
-    static void
-f_test_settime(typval_T *argvars, typval_T *rettv UNUSED)
-{
-    time_for_testing = (time_t)tv_get_number(&argvars[0]);
-}
-
-/*
  * Get a callback from "arg".  It can be a Funcref or a function name.
  * When "arg" is zero return an empty string.
  * "cb_name" is not allocated.
--- a/src/proto.h
+++ b/src/proto.h
@@ -202,6 +202,7 @@ void qsort(void *base, size_t elm_count,
 #  include "popupwin.pro"
 #  include "textprop.pro"
 # endif
+# include "testing.pro"
 # include "ui.pro"
 # include "undo.pro"
 # include "usercmd.pro"
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -41,6 +41,7 @@ void ex_lockvar(exarg_T *eap);
 int do_unlet(char_u *name, int forceit);
 void del_menutrans_vars(void);
 char_u *get_user_var_name(expand_T *xp, int idx);
+int pattern_match(char_u *pat, char_u *text, int ic);
 int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate);
 int eval1(char_u **arg, typval_T *rettv, int evaluate);
 int get_option_tv(char_u **arg, typval_T *rettv, int evaluate);
@@ -67,6 +68,7 @@ char_u *find_name_end(char_u *arg, char_
 int eval_isnamec(int c);
 int eval_isnamec1(int c);
 void set_vim_var_nr(int idx, varnumber_T val);
+typval_T *get_vim_var_tv(int idx);
 varnumber_T get_vim_var_nr(int idx);
 char_u *get_vim_var_str(int idx);
 list_T *get_vim_var_list(int idx);
@@ -129,18 +131,7 @@ void write_viminfo_varlist(FILE *fp);
 int store_session_globals(FILE *fd);
 void last_set_msg(sctx_T script_ctx);
 void reset_v_option_vars(void);
-void prepare_assert_error(garray_T *gap);
 void assert_error(garray_T *gap);
-int assert_equal_common(typval_T *argvars, assert_type_T atype);
-int assert_equalfile(typval_T *argvars);
-int assert_match_common(typval_T *argvars, assert_type_T atype);
-int assert_inrange(typval_T *argvars);
-int assert_bool(typval_T *argvars, int isTrue);
-int assert_report(typval_T *argvars);
-int assert_exception(typval_T *argvars);
-int assert_beeps(typval_T *argvars);
-int assert_fails(typval_T *argvars);
-void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, typval_T *got_tv, assert_type_T atype);
 int typval_compare(typval_T *typ1, typval_T *typ2, exptype_T type, int type_is, int ic);
 char_u *typval_tostring(typval_T *arg);
 int var_exists(char_u *var);
new file mode 100644
--- /dev/null
+++ b/src/proto/testing.pro
@@ -0,0 +1,34 @@
+/* testing.c */
+void f_assert_beeps(typval_T *argvars, typval_T *rettv);
+void f_assert_equal(typval_T *argvars, typval_T *rettv);
+void f_assert_equalfile(typval_T *argvars, typval_T *rettv);
+void f_assert_notequal(typval_T *argvars, typval_T *rettv);
+void f_assert_exception(typval_T *argvars, typval_T *rettv);
+void f_assert_fails(typval_T *argvars, typval_T *rettv);
+void f_assert_false(typval_T *argvars, typval_T *rettv);
+void f_assert_inrange(typval_T *argvars, typval_T *rettv);
+void f_assert_match(typval_T *argvars, typval_T *rettv);
+void f_assert_notmatch(typval_T *argvars, typval_T *rettv);
+void f_assert_report(typval_T *argvars, typval_T *rettv);
+void f_assert_true(typval_T *argvars, typval_T *rettv);
+void f_test_alloc_fail(typval_T *argvars, typval_T *rettv);
+void f_test_autochdir(typval_T *argvars, typval_T *rettv);
+void f_test_feedinput(typval_T *argvars, typval_T *rettv);
+void f_test_getvalue(typval_T *argvars, typval_T *rettv);
+void f_test_option_not_set(typval_T *argvars, typval_T *rettv);
+void f_test_override(typval_T *argvars, typval_T *rettv);
+void f_test_refcount(typval_T *argvars, typval_T *rettv);
+void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv);
+void f_test_garbagecollect_soon(typval_T *argvars, typval_T *rettv);
+void f_test_ignore_error(typval_T *argvars, typval_T *rettv);
+void f_test_null_blob(typval_T *argvars, typval_T *rettv);
+void f_test_null_channel(typval_T *argvars, typval_T *rettv);
+void f_test_null_dict(typval_T *argvars, typval_T *rettv);
+void f_test_null_job(typval_T *argvars, typval_T *rettv);
+void f_test_null_list(typval_T *argvars, typval_T *rettv);
+void f_test_null_partial(typval_T *argvars, typval_T *rettv);
+void f_test_null_string(typval_T *argvars, typval_T *rettv);
+void f_test_scrollbar(typval_T *argvars, typval_T *rettv);
+void f_test_setmouse(typval_T *argvars, typval_T *rettv);
+void f_test_settime(typval_T *argvars, typval_T *rettv);
+/* vim: set ft=c : */
new file mode 100644
--- /dev/null
+++ b/src/testing.c
@@ -0,0 +1,945 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * testing.c: Support for tests.
+ */
+
+#include "vim.h"
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+
+/*
+ * Prepare "gap" for an assert error and add the sourcing position.
+ */
+    static void
+prepare_assert_error(garray_T *gap)
+{
+    char buf[NUMBUFLEN];
+
+    ga_init2(gap, 1, 100);
+    if (sourcing_name != NULL)
+    {
+	ga_concat(gap, sourcing_name);
+	if (sourcing_lnum > 0)
+	    ga_concat(gap, (char_u *)" ");
+    }
+    if (sourcing_lnum > 0)
+    {
+	sprintf(buf, "line %ld", (long)sourcing_lnum);
+	ga_concat(gap, (char_u *)buf);
+    }
+    if (sourcing_name != NULL || sourcing_lnum > 0)
+	ga_concat(gap, (char_u *)": ");
+}
+
+/*
+ * Append "p[clen]" to "gap", escaping unprintable characters.
+ * Changes NL to \n, CR to \r, etc.
+ */
+    static void
+ga_concat_esc(garray_T *gap, char_u *p, int clen)
+{
+    char_u  buf[NUMBUFLEN];
+
+    if (clen > 1)
+    {
+	mch_memmove(buf, p, clen);
+	buf[clen] = NUL;
+	ga_concat(gap, buf);
+    }
+    else switch (*p)
+    {
+	case BS: ga_concat(gap, (char_u *)"\\b"); break;
+	case ESC: ga_concat(gap, (char_u *)"\\e"); break;
+	case FF: ga_concat(gap, (char_u *)"\\f"); break;
+	case NL: ga_concat(gap, (char_u *)"\\n"); break;
+	case TAB: ga_concat(gap, (char_u *)"\\t"); break;
+	case CAR: ga_concat(gap, (char_u *)"\\r"); break;
+	case '\\': ga_concat(gap, (char_u *)"\\\\"); break;
+	default:
+		   if (*p < ' ')
+		   {
+		       vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p);
+		       ga_concat(gap, buf);
+		   }
+		   else
+		       ga_append(gap, *p);
+		   break;
+    }
+}
+
+/*
+ * Append "str" to "gap", escaping unprintable characters.
+ * Changes NL to \n, CR to \r, etc.
+ */
+    static void
+ga_concat_shorten_esc(garray_T *gap, char_u *str)
+{
+    char_u  *p;
+    char_u  *s;
+    int	    c;
+    int	    clen;
+    char_u  buf[NUMBUFLEN];
+    int	    same_len;
+
+    if (str == NULL)
+    {
+	ga_concat(gap, (char_u *)"NULL");
+	return;
+    }
+
+    for (p = str; *p != NUL; ++p)
+    {
+	same_len = 1;
+	s = p;
+	c = mb_ptr2char_adv(&s);
+	clen = s - p;
+	while (*s != NUL && c == mb_ptr2char(s))
+	{
+	    ++same_len;
+	    s += clen;
+	}
+	if (same_len > 20)
+	{
+	    ga_concat(gap, (char_u *)"\\[");
+	    ga_concat_esc(gap, p, clen);
+	    ga_concat(gap, (char_u *)" occurs ");
+	    vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len);
+	    ga_concat(gap, buf);
+	    ga_concat(gap, (char_u *)" times]");
+	    p = s - 1;
+	}
+	else
+	    ga_concat_esc(gap, p, clen);
+    }
+}
+
+/*
+ * Fill "gap" with information about an assert error.
+ */
+    static void
+fill_assert_error(
+    garray_T	*gap,
+    typval_T	*opt_msg_tv,
+    char_u      *exp_str,
+    typval_T	*exp_tv,
+    typval_T	*got_tv,
+    assert_type_T atype)
+{
+    char_u	numbuf[NUMBUFLEN];
+    char_u	*tofree;
+
+    if (opt_msg_tv->v_type != VAR_UNKNOWN)
+    {
+	ga_concat(gap, echo_string(opt_msg_tv, &tofree, numbuf, 0));
+	vim_free(tofree);
+	ga_concat(gap, (char_u *)": ");
+    }
+
+    if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH)
+	ga_concat(gap, (char_u *)"Pattern ");
+    else if (atype == ASSERT_NOTEQUAL)
+	ga_concat(gap, (char_u *)"Expected not equal to ");
+    else
+	ga_concat(gap, (char_u *)"Expected ");
+    if (exp_str == NULL)
+    {
+	ga_concat_shorten_esc(gap, tv2string(exp_tv, &tofree, numbuf, 0));
+	vim_free(tofree);
+    }
+    else
+	ga_concat_shorten_esc(gap, exp_str);
+    if (atype != ASSERT_NOTEQUAL)
+    {
+	if (atype == ASSERT_MATCH)
+	    ga_concat(gap, (char_u *)" does not match ");
+	else if (atype == ASSERT_NOTMATCH)
+	    ga_concat(gap, (char_u *)" does match ");
+	else
+	    ga_concat(gap, (char_u *)" but got ");
+	ga_concat_shorten_esc(gap, tv2string(got_tv, &tofree, numbuf, 0));
+	vim_free(tofree);
+    }
+}
+
+    static int
+assert_equal_common(typval_T *argvars, assert_type_T atype)
+{
+    garray_T	ga;
+
+    if (tv_equal(&argvars[0], &argvars[1], FALSE, FALSE)
+						   != (atype == ASSERT_EQUAL))
+    {
+	prepare_assert_error(&ga);
+	fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1],
+								       atype);
+	assert_error(&ga);
+	ga_clear(&ga);
+	return 1;
+    }
+    return 0;
+}
+
+    static int
+assert_match_common(typval_T *argvars, assert_type_T atype)
+{
+    garray_T	ga;
+    char_u	buf1[NUMBUFLEN];
+    char_u	buf2[NUMBUFLEN];
+    char_u	*pat = tv_get_string_buf_chk(&argvars[0], buf1);
+    char_u	*text = tv_get_string_buf_chk(&argvars[1], buf2);
+
+    if (pat == NULL || text == NULL)
+	emsg(_(e_invarg));
+    else if (pattern_match(pat, text, FALSE) != (atype == ASSERT_MATCH))
+    {
+	prepare_assert_error(&ga);
+	fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1],
+									atype);
+	assert_error(&ga);
+	ga_clear(&ga);
+	return 1;
+    }
+    return 0;
+}
+
+/*
+ * Common for assert_true() and assert_false().
+ * Return non-zero for failure.
+ */
+    static int
+assert_bool(typval_T *argvars, int isTrue)
+{
+    int		error = FALSE;
+    garray_T	ga;
+
+    if (argvars[0].v_type == VAR_SPECIAL
+	    && argvars[0].vval.v_number == (isTrue ? VVAL_TRUE : VVAL_FALSE))
+	return 0;
+    if (argvars[0].v_type != VAR_NUMBER
+	    || (tv_get_number_chk(&argvars[0], &error) == 0) == isTrue
+	    || error)
+    {
+	prepare_assert_error(&ga);
+	fill_assert_error(&ga, &argvars[1],
+		(char_u *)(isTrue ? "True" : "False"),
+		NULL, &argvars[0], ASSERT_OTHER);
+	assert_error(&ga);
+	ga_clear(&ga);
+	return 1;
+    }
+    return 0;
+}
+
+    static void
+assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, char_u *cmd)
+{
+    char_u	*tofree;
+    char_u	numbuf[NUMBUFLEN];
+
+    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN)
+    {
+	ga_concat(gap, echo_string(&argvars[2], &tofree, numbuf, 0));
+	vim_free(tofree);
+    }
+    else
+	ga_concat(gap, cmd);
+}
+
+    static int
+assert_beeps(typval_T *argvars)
+{
+    char_u	*cmd = tv_get_string_chk(&argvars[0]);
+    garray_T	ga;
+    int		ret = 0;
+
+    called_vim_beep = FALSE;
+    suppress_errthrow = TRUE;
+    emsg_silent = FALSE;
+    do_cmdline_cmd(cmd);
+    if (!called_vim_beep)
+    {
+	prepare_assert_error(&ga);
+	ga_concat(&ga, (char_u *)"command did not beep: ");
+	ga_concat(&ga, cmd);
+	assert_error(&ga);
+	ga_clear(&ga);
+	ret = 1;
+    }
+
+    suppress_errthrow = FALSE;
+    emsg_on_display = FALSE;
+    return ret;
+}
+
+/*
+ * "assert_beeps(cmd [, error])" function
+ */
+    void
+f_assert_beeps(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_beeps(argvars);
+}
+
+/*
+ * "assert_equal(expected, actual[, msg])" function
+ */
+    void
+f_assert_equal(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
+}
+
+    static int
+assert_equalfile(typval_T *argvars)
+{
+    char_u	buf1[NUMBUFLEN];
+    char_u	buf2[NUMBUFLEN];
+    char_u	*fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
+    char_u	*fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
+    garray_T	ga;
+    FILE	*fd1;
+    FILE	*fd2;
+
+    if (fname1 == NULL || fname2 == NULL)
+	return 0;
+
+    IObuff[0] = NUL;
+    fd1 = mch_fopen((char *)fname1, READBIN);
+    if (fd1 == NULL)
+    {
+	vim_snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1);
+    }
+    else
+    {
+	fd2 = mch_fopen((char *)fname2, READBIN);
+	if (fd2 == NULL)
+	{
+	    fclose(fd1);
+	    vim_snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2);
+	}
+	else
+	{
+	    int c1, c2;
+	    long count = 0;
+
+	    for (;;)
+	    {
+		c1 = fgetc(fd1);
+		c2 = fgetc(fd2);
+		if (c1 == EOF)
+		{
+		    if (c2 != EOF)
+			STRCPY(IObuff, "first file is shorter");
+		    break;
+		}
+		else if (c2 == EOF)
+		{
+		    STRCPY(IObuff, "second file is shorter");
+		    break;
+		}
+		else if (c1 != c2)
+		{
+		    vim_snprintf((char *)IObuff, IOSIZE,
+					      "difference at byte %ld", count);
+		    break;
+		}
+		++count;
+	    }
+	    fclose(fd1);
+	    fclose(fd2);
+	}
+    }
+    if (IObuff[0] != NUL)
+    {
+	prepare_assert_error(&ga);
+	ga_concat(&ga, IObuff);
+	assert_error(&ga);
+	ga_clear(&ga);
+	return 1;
+    }
+    return 0;
+}
+
+/*
+ * "assert_equalfile(fname-one, fname-two)" function
+ */
+    void
+f_assert_equalfile(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_equalfile(argvars);
+}
+
+/*
+ * "assert_notequal(expected, actual[, msg])" function
+ */
+    void
+f_assert_notequal(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
+}
+
+/*
+ * "assert_exception(string[, msg])" function
+ */
+    void
+f_assert_exception(typval_T *argvars, typval_T *rettv)
+{
+    garray_T	ga;
+    char_u	*error = tv_get_string_chk(&argvars[0]);
+
+    if (*get_vim_var_str(VV_EXCEPTION) == NUL)
+    {
+	prepare_assert_error(&ga);
+	ga_concat(&ga, (char_u *)"v:exception is not set");
+	assert_error(&ga);
+	ga_clear(&ga);
+	rettv->vval.v_number = 1;
+    }
+    else if (error != NULL
+	&& strstr((char *)get_vim_var_str(VV_EXCEPTION), (char *)error) == NULL)
+    {
+	prepare_assert_error(&ga);
+	fill_assert_error(&ga, &argvars[1], NULL, &argvars[0],
+				  get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER);
+	assert_error(&ga);
+	ga_clear(&ga);
+	rettv->vval.v_number = 1;
+    }
+}
+
+/*
+ * "assert_fails(cmd [, error[, msg]])" function
+ */
+    void
+f_assert_fails(typval_T *argvars, typval_T *rettv)
+{
+    char_u	*cmd = tv_get_string_chk(&argvars[0]);
+    garray_T	ga;
+    int		save_trylevel = trylevel;
+
+    // trylevel must be zero for a ":throw" command to be considered failed
+    trylevel = 0;
+    called_emsg = FALSE;
+    suppress_errthrow = TRUE;
+    emsg_silent = TRUE;
+
+    do_cmdline_cmd(cmd);
+    if (!called_emsg)
+    {
+	prepare_assert_error(&ga);
+	ga_concat(&ga, (char_u *)"command did not fail: ");
+	assert_append_cmd_or_arg(&ga, argvars, cmd);
+	assert_error(&ga);
+	ga_clear(&ga);
+	rettv->vval.v_number = 1;
+    }
+    else if (argvars[1].v_type != VAR_UNKNOWN)
+    {
+	char_u	buf[NUMBUFLEN];
+	char	*error = (char *)tv_get_string_buf_chk(&argvars[1], buf);
+
+	if (error == NULL
+		  || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL)
+	{
+	    prepare_assert_error(&ga);
+	    fill_assert_error(&ga, &argvars[2], NULL, &argvars[1],
+				      get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER);
+	    ga_concat(&ga, (char_u *)": ");
+	    assert_append_cmd_or_arg(&ga, argvars, cmd);
+	    assert_error(&ga);
+	    ga_clear(&ga);
+	    rettv->vval.v_number = 1;
+	}
+    }
+
+    trylevel = save_trylevel;
+    called_emsg = FALSE;
+    suppress_errthrow = FALSE;
+    emsg_silent = FALSE;
+    emsg_on_display = FALSE;
+    set_vim_var_string(VV_ERRMSG, NULL, 0);
+}
+
+/*
+ * "assert_false(actual[, msg])" function
+ */
+    void
+f_assert_false(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_bool(argvars, FALSE);
+}
+
+    static int
+assert_inrange(typval_T *argvars)
+{
+    garray_T	ga;
+    int		error = FALSE;
+    char_u	*tofree;
+    char	msg[200];
+    char_u	numbuf[NUMBUFLEN];
+
+#ifdef FEAT_FLOAT
+    if (argvars[0].v_type == VAR_FLOAT
+	    || argvars[1].v_type == VAR_FLOAT
+	    || argvars[2].v_type == VAR_FLOAT)
+    {
+	float_T flower = tv_get_float(&argvars[0]);
+	float_T fupper = tv_get_float(&argvars[1]);
+	float_T factual = tv_get_float(&argvars[2]);
+
+	if (factual < flower || factual > fupper)
+	{
+	    prepare_assert_error(&ga);
+	    if (argvars[3].v_type != VAR_UNKNOWN)
+	    {
+		ga_concat(&ga, tv2string(&argvars[3], &tofree, numbuf, 0));
+		vim_free(tofree);
+	    }
+	    else
+	    {
+		vim_snprintf(msg, 200, "Expected range %g - %g, but got %g",
+						      flower, fupper, factual);
+		ga_concat(&ga, (char_u *)msg);
+	    }
+	    assert_error(&ga);
+	    ga_clear(&ga);
+	    return 1;
+	}
+    }
+    else
+#endif
+    {
+	varnumber_T	lower = tv_get_number_chk(&argvars[0], &error);
+	varnumber_T	upper = tv_get_number_chk(&argvars[1], &error);
+	varnumber_T	actual = tv_get_number_chk(&argvars[2], &error);
+
+	if (error)
+	    return 0;
+	if (actual < lower || actual > upper)
+	{
+	    prepare_assert_error(&ga);
+	    if (argvars[3].v_type != VAR_UNKNOWN)
+	    {
+		ga_concat(&ga, tv2string(&argvars[3], &tofree, numbuf, 0));
+		vim_free(tofree);
+	    }
+	    else
+	    {
+		vim_snprintf(msg, 200, "Expected range %ld - %ld, but got %ld",
+				       (long)lower, (long)upper, (long)actual);
+		ga_concat(&ga, (char_u *)msg);
+	    }
+	    assert_error(&ga);
+	    ga_clear(&ga);
+	    return 1;
+	}
+    }
+    return 0;
+}
+
+/*
+ * "assert_inrange(lower, upper[, msg])" function
+ */
+    void
+f_assert_inrange(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_inrange(argvars);
+}
+
+/*
+ * "assert_match(pattern, actual[, msg])" function
+ */
+    void
+f_assert_match(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
+}
+
+/*
+ * "assert_notmatch(pattern, actual[, msg])" function
+ */
+    void
+f_assert_notmatch(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
+}
+
+/*
+ * "assert_report(msg)" function
+ */
+    void
+f_assert_report(typval_T *argvars, typval_T *rettv)
+{
+    garray_T	ga;
+
+    prepare_assert_error(&ga);
+    ga_concat(&ga, tv_get_string(&argvars[0]));
+    assert_error(&ga);
+    ga_clear(&ga);
+    rettv->vval.v_number = 1;
+}
+
+/*
+ * "assert_true(actual[, msg])" function
+ */
+    void
+f_assert_true(typval_T *argvars, typval_T *rettv)
+{
+    rettv->vval.v_number = assert_bool(argvars, TRUE);
+}
+
+/*
+ * "test_alloc_fail(id, countdown, repeat)" function
+ */
+    void
+f_test_alloc_fail(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    if (argvars[0].v_type != VAR_NUMBER
+	    || argvars[0].vval.v_number <= 0
+	    || argvars[1].v_type != VAR_NUMBER
+	    || argvars[1].vval.v_number < 0
+	    || argvars[2].v_type != VAR_NUMBER)
+	emsg(_(e_invarg));
+    else
+    {
+	alloc_fail_id = argvars[0].vval.v_number;
+	if (alloc_fail_id >= aid_last)
+	    emsg(_(e_invarg));
+	alloc_fail_countdown = argvars[1].vval.v_number;
+	alloc_fail_repeat = argvars[2].vval.v_number;
+	did_outofmem_msg = FALSE;
+    }
+}
+
+/*
+ * "test_autochdir()"
+ */
+    void
+f_test_autochdir(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+#if defined(FEAT_AUTOCHDIR)
+    test_autochdir = TRUE;
+#endif
+}
+
+/*
+ * "test_feedinput()"
+ */
+    void
+f_test_feedinput(typval_T *argvars, typval_T *rettv UNUSED)
+{
+#ifdef USE_INPUT_BUF
+    char_u	*val = tv_get_string_chk(&argvars[0]);
+
+    if (val != NULL)
+    {
+	trash_input_buf();
+	add_to_input_buf_csi(val, (int)STRLEN(val));
+    }
+#endif
+}
+
+/*
+ * "test_getvalue({name})" function
+ */
+    void
+f_test_getvalue(typval_T *argvars, typval_T *rettv)
+{
+    if (argvars[0].v_type != VAR_STRING)
+	emsg(_(e_invarg));
+    else
+    {
+	char_u *name = tv_get_string(&argvars[0]);
+
+	if (STRCMP(name, (char_u *)"need_fileinfo") == 0)
+	    rettv->vval.v_number = need_fileinfo;
+	else
+	    semsg(_(e_invarg2), name);
+    }
+}
+
+/*
+ * "test_option_not_set({name})" function
+ */
+    void
+f_test_option_not_set(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    char_u *name = (char_u *)"";
+
+    if (argvars[0].v_type != VAR_STRING)
+	emsg(_(e_invarg));
+    else
+    {
+	name = tv_get_string(&argvars[0]);
+	if (reset_option_was_set(name) == FAIL)
+	    semsg(_(e_invarg2), name);
+    }
+}
+
+/*
+ * "test_override({name}, {val})" function
+ */
+    void
+f_test_override(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    char_u *name = (char_u *)"";
+    int     val;
+    static int save_starting = -1;
+
+    if (argvars[0].v_type != VAR_STRING
+	    || (argvars[1].v_type) != VAR_NUMBER)
+	emsg(_(e_invarg));
+    else
+    {
+	name = tv_get_string(&argvars[0]);
+	val = (int)tv_get_number(&argvars[1]);
+
+	if (STRCMP(name, (char_u *)"redraw") == 0)
+	    disable_redraw_for_testing = val;
+	else if (STRCMP(name, (char_u *)"redraw_flag") == 0)
+	    ignore_redraw_flag_for_testing = val;
+	else if (STRCMP(name, (char_u *)"char_avail") == 0)
+	    disable_char_avail_for_testing = val;
+	else if (STRCMP(name, (char_u *)"starting") == 0)
+	{
+	    if (val)
+	    {
+		if (save_starting < 0)
+		    save_starting = starting;
+		starting = 0;
+	    }
+	    else
+	    {
+		starting = save_starting;
+		save_starting = -1;
+	    }
+	}
+	else if (STRCMP(name, (char_u *)"nfa_fail") == 0)
+	    nfa_fail_for_testing = val;
+	else if (STRCMP(name, (char_u *)"no_query_mouse") == 0)
+	    no_query_mouse_for_testing = val;
+	else if (STRCMP(name, (char_u *)"no_wait_return") == 0)
+	    no_wait_return = val;
+	else if (STRCMP(name, (char_u *)"ALL") == 0)
+	{
+	    disable_char_avail_for_testing = FALSE;
+	    disable_redraw_for_testing = FALSE;
+	    ignore_redraw_flag_for_testing = FALSE;
+	    nfa_fail_for_testing = FALSE;
+	    no_query_mouse_for_testing = FALSE;
+	    if (save_starting >= 0)
+	    {
+		starting = save_starting;
+		save_starting = -1;
+	    }
+	}
+	else
+	    semsg(_(e_invarg2), name);
+    }
+}
+
+/*
+ * "test_refcount({expr})" function
+ */
+    void
+f_test_refcount(typval_T *argvars, typval_T *rettv)
+{
+    int retval = -1;
+
+    switch (argvars[0].v_type)
+    {
+	case VAR_UNKNOWN:
+	case VAR_NUMBER:
+	case VAR_FLOAT:
+	case VAR_SPECIAL:
+	case VAR_STRING:
+	    break;
+	case VAR_JOB:
+#ifdef FEAT_JOB_CHANNEL
+	    if (argvars[0].vval.v_job != NULL)
+		retval = argvars[0].vval.v_job->jv_refcount - 1;
+#endif
+	    break;
+	case VAR_CHANNEL:
+#ifdef FEAT_JOB_CHANNEL
+	    if (argvars[0].vval.v_channel != NULL)
+		retval = argvars[0].vval.v_channel->ch_refcount - 1;
+#endif
+	    break;
+	case VAR_FUNC:
+	    if (argvars[0].vval.v_string != NULL)
+	    {
+		ufunc_T *fp;
+
+		fp = find_func(argvars[0].vval.v_string);
+		if (fp != NULL)
+		    retval = fp->uf_refcount;
+	    }
+	    break;
+	case VAR_PARTIAL:
+	    if (argvars[0].vval.v_partial != NULL)
+		retval = argvars[0].vval.v_partial->pt_refcount - 1;
+	    break;
+	case VAR_BLOB:
+	    if (argvars[0].vval.v_blob != NULL)
+		retval = argvars[0].vval.v_blob->bv_refcount - 1;
+	    break;
+	case VAR_LIST:
+	    if (argvars[0].vval.v_list != NULL)
+		retval = argvars[0].vval.v_list->lv_refcount - 1;
+	    break;
+	case VAR_DICT:
+	    if (argvars[0].vval.v_dict != NULL)
+		retval = argvars[0].vval.v_dict->dv_refcount - 1;
+	    break;
+    }
+
+    rettv->v_type = VAR_NUMBER;
+    rettv->vval.v_number = retval;
+
+}
+
+/*
+ * "test_garbagecollect_now()" function
+ */
+    void
+f_test_garbagecollect_now(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+    /* This is dangerous, any Lists and Dicts used internally may be freed
+     * while still in use. */
+    garbage_collect(TRUE);
+}
+
+/*
+ * "test_garbagecollect_soon()" function
+ */
+    void
+f_test_garbagecollect_soon(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+    may_garbage_collect = TRUE;
+}
+
+/*
+ * "test_ignore_error()" function
+ */
+    void
+f_test_ignore_error(typval_T *argvars, typval_T *rettv UNUSED)
+{
+     ignore_error_for_testing(tv_get_string(&argvars[0]));
+}
+
+    void
+f_test_null_blob(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv->v_type = VAR_BLOB;
+    rettv->vval.v_blob = NULL;
+}
+
+#ifdef FEAT_JOB_CHANNEL
+    void
+f_test_null_channel(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv->v_type = VAR_CHANNEL;
+    rettv->vval.v_channel = NULL;
+}
+#endif
+
+    void
+f_test_null_dict(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv_dict_set(rettv, NULL);
+}
+
+#ifdef FEAT_JOB_CHANNEL
+    void
+f_test_null_job(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv->v_type = VAR_JOB;
+    rettv->vval.v_job = NULL;
+}
+#endif
+
+    void
+f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv_list_set(rettv, NULL);
+}
+
+    void
+f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv->v_type = VAR_PARTIAL;
+    rettv->vval.v_partial = NULL;
+}
+
+    void
+f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = NULL;
+}
+
+#ifdef FEAT_GUI
+    void
+f_test_scrollbar(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    char_u	*which;
+    long	value;
+    int		dragging;
+    scrollbar_T *sb = NULL;
+
+    if (argvars[0].v_type != VAR_STRING
+	    || (argvars[1].v_type) != VAR_NUMBER
+	    || (argvars[2].v_type) != VAR_NUMBER)
+    {
+	emsg(_(e_invarg));
+	return;
+    }
+    which = tv_get_string(&argvars[0]);
+    value = tv_get_number(&argvars[1]);
+    dragging = tv_get_number(&argvars[2]);
+
+    if (STRCMP(which, "left") == 0)
+	sb = &curwin->w_scrollbars[SBAR_LEFT];
+    else if (STRCMP(which, "right") == 0)
+	sb = &curwin->w_scrollbars[SBAR_RIGHT];
+    else if (STRCMP(which, "hor") == 0)
+	sb = &gui.bottom_sbar;
+    if (sb == NULL)
+    {
+	semsg(_(e_invarg2), which);
+	return;
+    }
+    gui_drag_scrollbar(sb, value, dragging);
+# ifndef USE_ON_FLY_SCROLL
+    // need to loop through normal_cmd() to handle the scroll events
+    exec_normal(FALSE, TRUE, FALSE);
+# endif
+}
+#endif
+
+#ifdef FEAT_MOUSE
+    void
+f_test_setmouse(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    mouse_row = (time_t)tv_get_number(&argvars[0]) - 1;
+    mouse_col = (time_t)tv_get_number(&argvars[1]) - 1;
+}
+#endif
+
+    void
+f_test_settime(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    time_for_testing = (time_t)tv_get_number(&argvars[0]);
+}
+
+
+#endif // defined(FEAT_EVAL)
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1687,
+/**/
     1686,
 /**/
     1685,