Skip to content

Commit 4334554

Browse files
committed
patch 7.4.944
Problem: Writing tests for Vim script is hard. Solution: Add assertEqual(), assertFalse() and assertTrue() functions. Add the v:errors variable. Add the runtest script. Add a first new style test script.
1 parent 48a969b commit 4334554

8 files changed

Lines changed: 294 additions & 7 deletions

File tree

runtime/doc/eval.txt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*eval.txt* For Vim version 7.4. Last change: 2015 Sep 19
1+
*eval.txt* For Vim version 7.4. Last change: 2015 Nov 29
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1379,6 +1379,15 @@ v:errmsg Last given error message. It's allowed to set this variable.
13791379
: ... handle error
13801380
< "errmsg" also works, for backwards compatibility.
13811381

1382+
*v:errors* *errors-variable*
1383+
v:errors Errors found by assert functions, such as |assertTrue()|.
1384+
This is a list of strings.
1385+
The assert functions append an item when an assert fails.
1386+
To remove old results make it empty: >
1387+
:let v:errors = []
1388+
< If v:errors is set to anything but a list it is made an empty
1389+
list by the assert function.
1390+
13821391
*v:exception* *exception-variable*
13831392
v:exception The value of the exception most recently caught and not
13841393
finished. See also |v:throwpoint| and |throw-variables|.
@@ -1737,6 +1746,9 @@ arglistid( [{winnr}, [ {tabnr}]])
17371746
Number argument list id
17381747
argv( {nr}) String {nr} entry of the argument list
17391748
argv( ) List the argument list
1749+
assertEqual( {exp}, {act}) none assert that {exp} equals {act}
1750+
assertFalse( {actual}) none assert that {actual} is false
1751+
assertTrue( {actual}) none assert that {actual} is true
17401752
asin( {expr}) Float arc sine of {expr}
17411753
atan( {expr}) Float arc tangent of {expr}
17421754
atan2( {expr}, {expr}) Float arc tangent of {expr1} / {expr2}
@@ -2154,6 +2166,31 @@ argv([{nr}]) The result is the {nr}th file in the argument list of the
21542166
< Without the {nr} argument a |List| with the whole |arglist| is
21552167
returned.
21562168

2169+
*assertEqual()*
2170+
assertEqual({expected}, {actual})
2171+
When {expected} and {actual} are not equal an error message is
2172+
added to |v:errors|.
2173+
There is no automatic conversion, the String "4" is different
2174+
from the Number 4. And the number 4 is different from the
2175+
Float 4.0. The value of 'ignorecase' is not used here, case
2176+
always matters.
2177+
Example: >
2178+
assertEqual('foo', 'bar')
2179+
< Will result in a string to be added to |v:errors|:
2180+
test.vim line 12: Expected 'foo' but got 'bar' ~
2181+
2182+
assertFalse({actual}) *assertFalse()*
2183+
When {actual} is not false an error message is added to
2184+
|v:errors|, like with |assertEqual()|..
2185+
A value is false when it is zero. When "{actual}" is not a
2186+
number the assert fails.
2187+
2188+
assertTrue({actual}) *assertTrue()*
2189+
When {actual} is not true an error message is added to
2190+
|v:errors|, like with |assertEqual()|..
2191+
A value is true when it is a non-zeron number. When {actual}
2192+
is not a number the assert fails.
2193+
21572194
asin({expr}) *asin()*
21582195
Return the arc sine of {expr} measured in radians, as a |Float|
21592196
in the range of [-pi/2, pi/2].

src/eval.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ static struct vimvar
368368
{VV_NAME("option_new", VAR_STRING), VV_RO},
369369
{VV_NAME("option_old", VAR_STRING), VV_RO},
370370
{VV_NAME("option_type", VAR_STRING), VV_RO},
371+
{VV_NAME("errors", VAR_LIST), 0},
371372
};
372373

373374
/* shorthand */
@@ -472,6 +473,9 @@ static void f_argc __ARGS((typval_T *argvars, typval_T *rettv));
472473
static void f_argidx __ARGS((typval_T *argvars, typval_T *rettv));
473474
static void f_arglistid __ARGS((typval_T *argvars, typval_T *rettv));
474475
static void f_argv __ARGS((typval_T *argvars, typval_T *rettv));
476+
static void f_assertEqual __ARGS((typval_T *argvars, typval_T *rettv));
477+
static void f_assertFalse __ARGS((typval_T *argvars, typval_T *rettv));
478+
static void f_assertTrue __ARGS((typval_T *argvars, typval_T *rettv));
475479
#ifdef FEAT_FLOAT
476480
static void f_asin __ARGS((typval_T *argvars, typval_T *rettv));
477481
static void f_atan __ARGS((typval_T *argvars, typval_T *rettv));
@@ -8068,6 +8072,9 @@ static struct fst
80688072
{"argidx", 0, 0, f_argidx},
80698073
{"arglistid", 0, 2, f_arglistid},
80708074
{"argv", 0, 1, f_argv},
8075+
{"assertEqual", 2, 3, f_assertEqual},
8076+
{"assertFalse", 1, 2, f_assertFalse},
8077+
{"assertTrue", 1, 2, f_assertTrue},
80718078
#ifdef FEAT_FLOAT
80728079
{"asin", 1, 1, f_asin}, /* WJMc */
80738080
{"atan", 1, 1, f_atan},
@@ -9124,6 +9131,113 @@ f_argv(argvars, rettv)
91249131
alist_name(&ARGLIST[idx]), -1);
91259132
}
91269133

9134+
static void assertError __ARGS((garray_T *gap));
9135+
static void prepareForAssertError __ARGS((garray_T*gap));
9136+
static void assertBool __ARGS((typval_T *argvars, int isTrue));
9137+
9138+
/*
9139+
* Add an assert error to v:errors.
9140+
*/
9141+
static void
9142+
assertError(gap)
9143+
garray_T *gap;
9144+
{
9145+
struct vimvar *vp = &vimvars[VV_ERRORS];
9146+
9147+
if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL)
9148+
/* Make sure v:errors is a list. */
9149+
set_vim_var_list(VV_ERRORS, list_alloc());
9150+
list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, gap->ga_len);
9151+
}
9152+
9153+
static void
9154+
prepareForAssertError(gap)
9155+
garray_T *gap;
9156+
{
9157+
char buf[NUMBUFLEN];
9158+
9159+
ga_init2(gap, 1, 100);
9160+
ga_concat(gap, sourcing_name);
9161+
sprintf(buf, " line %ld", (long)sourcing_lnum);
9162+
ga_concat(gap, (char_u *)buf);
9163+
}
9164+
9165+
/*
9166+
* "assertEqual(expected, actual[, msg])" function
9167+
*/
9168+
static void
9169+
f_assertEqual(argvars, rettv)
9170+
typval_T *argvars;
9171+
typval_T *rettv UNUSED;
9172+
{
9173+
garray_T ga;
9174+
char_u *tofree;
9175+
char_u numbuf[NUMBUFLEN];
9176+
9177+
if (!tv_equal(&argvars[0], &argvars[1], FALSE, FALSE))
9178+
{
9179+
prepareForAssertError(&ga);
9180+
ga_concat(&ga, (char_u *)": Expected ");
9181+
ga_concat(&ga, tv2string(&argvars[0], &tofree, numbuf, 0));
9182+
vim_free(tofree);
9183+
ga_concat(&ga, (char_u *)" but got ");
9184+
ga_concat(&ga, tv2string(&argvars[1], &tofree, numbuf, 0));
9185+
vim_free(tofree);
9186+
assertError(&ga);
9187+
ga_clear(&ga);
9188+
}
9189+
}
9190+
9191+
static void
9192+
assertBool(argvars, isTrue)
9193+
typval_T *argvars;
9194+
int isTrue;
9195+
{
9196+
int error = FALSE;
9197+
garray_T ga;
9198+
char_u *tofree;
9199+
char_u numbuf[NUMBUFLEN];
9200+
9201+
if (argvars[0].v_type != VAR_NUMBER
9202+
|| (get_tv_number_chk(&argvars[0], &error) == 0) == isTrue
9203+
|| error)
9204+
{
9205+
prepareForAssertError(&ga);
9206+
ga_concat(&ga, (char_u *)": Expected ");
9207+
if (isTrue)
9208+
ga_concat(&ga, (char_u *)"True ");
9209+
else
9210+
ga_concat(&ga, (char_u *)"False ");
9211+
ga_concat(&ga, (char_u *)"but got ");
9212+
ga_concat(&ga, tv2string(&argvars[0], &tofree, numbuf, 0));
9213+
vim_free(tofree);
9214+
assertError(&ga);
9215+
ga_clear(&ga);
9216+
}
9217+
}
9218+
9219+
/*
9220+
* "assertFalse(actual[, msg])" function
9221+
*/
9222+
static void
9223+
f_assertFalse(argvars, rettv)
9224+
typval_T *argvars;
9225+
typval_T *rettv UNUSED;
9226+
{
9227+
assertBool(argvars, FALSE);
9228+
}
9229+
9230+
/*
9231+
* "assertTrue(actual[, msg])" function
9232+
*/
9233+
static void
9234+
f_assertTrue(argvars, rettv)
9235+
typval_T *argvars;
9236+
typval_T *rettv UNUSED;
9237+
{
9238+
assertBool(argvars, TRUE);
9239+
}
9240+
91279241
#ifdef FEAT_FLOAT
91289242
/*
91299243
* "asin()" function

src/misc2.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2092,15 +2092,19 @@ ga_concat_strings(gap, sep)
20922092

20932093
/*
20942094
* Concatenate a string to a growarray which contains characters.
2095+
* When "s" is NULL does not do anything.
20952096
* Note: Does NOT copy the NUL at the end!
20962097
*/
20972098
void
20982099
ga_concat(gap, s)
20992100
garray_T *gap;
21002101
char_u *s;
21012102
{
2102-
int len = (int)STRLEN(s);
2103+
int len;
21032104

2105+
if (s == NULL)
2106+
return;
2107+
len = (int)STRLEN(s);
21042108
if (ga_grow(gap, len) == OK)
21052109
{
21062110
mch_memmove((char *)gap->ga_data + gap->ga_len, s, (size_t)len);

src/testdir/Makefile

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,17 @@ SCRIPTS = test1.out test2.out test3.out test4.out test5.out test6.out \
6868
test_utf8.out \
6969
test_writefile.out
7070

71+
NEW_TESTS = test_assert.res
72+
7173
SCRIPTS_GUI = test16.out
7274

7375
SCRIPTS_BENCH = bench_re_freeze.out
7476

75-
.SUFFIXES: .in .out
77+
.SUFFIXES: .in .out .res .vim
7678

77-
nongui: nolog $(SCRIPTS) report
79+
nongui: nolog $(SCRIPTS) newtests report
7880

79-
gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) report
81+
gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) newtests report
8082

8183
benchmark: $(SCRIPTS_BENCH)
8284

@@ -95,7 +97,7 @@ RM_ON_START = tiny.vim small.vim mbyte.vim mzscheme.vim lua.vim test.ok benchmar
9597
RUN_VIM = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(VALGRIND) $(VIMPROG) -u unix.vim -U NONE --noplugin -s dotest.in
9698

9799
clean:
98-
-rm -rf *.out *.failed *.rej *.orig test.log $(RM_ON_RUN) $(RM_ON_START) valgrind.*
100+
-rm -rf *.out *.failed *.res *.rej *.orig test.log $(RM_ON_RUN) $(RM_ON_START) valgrind.*
99101

100102
test1.out: test1.in
101103
-rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize
@@ -157,3 +159,14 @@ bench_re_freeze.out: bench_re_freeze.vim
157159

158160
nolog:
159161
-rm -f test.log
162+
163+
164+
# New style of tests uses Vim script with assert calls. These are easier
165+
# to write and a lot easier to read and debug.
166+
# Limitation: Only works with the +eval feature.
167+
RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(VALGRIND) $(VIMPROG) -u unix.vim -U NONE --noplugin
168+
169+
newtests: $(NEW_TESTS)
170+
171+
.vim.res:
172+
$(RUN_VIMTEST) -u runtest.vim $*.vim

src/testdir/runtest.vim

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
" This script is sourced while editing the .vim file with the tests.
2+
" When the script is successful the .res file will be created.
3+
" Errors are appended to the test.log file.
4+
"
5+
" The test script may contain anything, only functions that start with
6+
" "Test_" are special. These will be invoked and should contain assert
7+
" functions. See test_assert.vim for an example.
8+
"
9+
" It is possible to source other files that contain "Test_" functions. This
10+
" can speed up testing, since Vim does not need to restart. But be careful
11+
" that the tests do not interfere with each other.
12+
"
13+
" If an error cannot be detected properly with an assert function add the
14+
" error to the v:errors list:
15+
" call add(v:errors, 'test foo failed: Cannot find xyz')
16+
"
17+
" If preparation for each Test_ function is needed, define a SetUp function.
18+
" It will be called before each Test_ function.
19+
"
20+
" If cleanup after each Test_ function is needed, define a TearDown function.
21+
" It will be called after each Test_ function.
22+
23+
" Without the +eval feature we can't run these tests, bail out.
24+
if 0
25+
quit!
26+
endif
27+
28+
" Check that the screen size is at least 24 x 80 characters.
29+
if &lines < 24 || &columns < 80
30+
let error = 'Screen size too small! Tests require at least 24 lines with 80 characters'
31+
echoerr error
32+
split test.log
33+
$put =error
34+
w
35+
cquit
36+
endif
37+
38+
" Source the test script. First grab the file name, in case the script
39+
" navigates away.
40+
let testname = expand('%')
41+
source %
42+
43+
" Locate Test_ functions and execute them.
44+
redir @q
45+
function /^Test_
46+
redir END
47+
let tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
48+
49+
let done = 0
50+
let fail = 0
51+
let errors = []
52+
for test in tests
53+
if exists("*SetUp")
54+
call SetUp()
55+
endif
56+
57+
let done += 1
58+
try
59+
exe 'call ' . test
60+
catch
61+
let fail += 1
62+
call add(v:errors, 'Caught exception in ' . test . ': ' . v:exception . ' @ ' . v:throwpoint)
63+
endtry
64+
65+
if len(v:errors) > 0
66+
let fail += 1
67+
call add(errors, 'Found errors in ' . test . ':')
68+
call extend(errors, v:errors)
69+
let v:errors = []
70+
endif
71+
72+
if exists("*TearDown")
73+
call TearDown()
74+
endif
75+
endfor
76+
77+
if fail == 0
78+
" Success, create the .res file so that make knows it's done.
79+
split %:r.res
80+
write
81+
endif
82+
83+
if len(errors) > 0
84+
" Append errors to test.log
85+
split test.log
86+
call append(line('$'), '')
87+
call append(line('$'), 'From ' . testname . ':')
88+
call append(line('$'), errors)
89+
write
90+
endif
91+
92+
echo 'Executed ' . done . (done > 1 ? ' tests': ' test')
93+
if fail > 0
94+
echo fail . ' FAILED'
95+
endif
96+
97+
qall!

src/testdir/test_assert.vim

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
" Test that the methods used for testing work.
2+
3+
func Test_assertFalse()
4+
call assertFalse(0)
5+
endfunc
6+
7+
func Test_assertTrue()
8+
call assertTrue(1)
9+
call assertTrue(123)
10+
endfunc
11+
12+
func Test_assertEqual()
13+
let s = 'foo'
14+
call assertEqual('foo', s)
15+
let n = 4
16+
call assertEqual(4, n)
17+
let l = [1, 2, 3]
18+
call assertEqual([1, 2, 3], l)
19+
endfunc

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,8 @@ static char *(features[]) =
741741

742742
static int included_patches[] =
743743
{ /* Add new patch number below this line */
744+
/**/
745+
944,
744746
/**/
745747
943,
746748
/**/

0 commit comments

Comments
 (0)