neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

testing.c (23717B)


      1 // testing.c: Support for tests
      2 
      3 #include <inttypes.h>
      4 #include <stdbool.h>
      5 #include <stddef.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 
      9 #include "nvim/ascii_defs.h"
     10 #include "nvim/errors.h"
     11 #include "nvim/eval.h"
     12 #include "nvim/eval/encode.h"
     13 #include "nvim/eval/typval.h"
     14 #include "nvim/eval/typval_defs.h"
     15 #include "nvim/eval/vars.h"
     16 #include "nvim/ex_docmd.h"
     17 #include "nvim/garray.h"
     18 #include "nvim/garray_defs.h"
     19 #include "nvim/gettext_defs.h"
     20 #include "nvim/globals.h"
     21 #include "nvim/hashtab.h"
     22 #include "nvim/hashtab_defs.h"
     23 #include "nvim/macros_defs.h"
     24 #include "nvim/mbyte.h"
     25 #include "nvim/memory.h"
     26 #include "nvim/message.h"
     27 #include "nvim/os/fs.h"
     28 #include "nvim/runtime.h"
     29 #include "nvim/runtime_defs.h"
     30 #include "nvim/strings.h"
     31 #include "nvim/testing.h"
     32 #include "nvim/types_defs.h"
     33 #include "nvim/vim_defs.h"
     34 
     35 /// Type of assert_* check being performed
     36 typedef enum {
     37  ASSERT_EQUAL,
     38  ASSERT_NOTEQUAL,
     39  ASSERT_MATCH,
     40  ASSERT_NOTMATCH,
     41  ASSERT_FAILS,
     42  ASSERT_OTHER,
     43 } assert_type_T;
     44 
     45 #include "testing.c.generated.h"
     46 
     47 static const char e_assert_fails_second_arg[]
     48  = N_(
     49      "E856: \"assert_fails()\" second argument must be a string or a list with one or two strings");
     50 static const char e_assert_fails_fourth_argument[]
     51  = N_("E1115: \"assert_fails()\" fourth argument must be a number");
     52 static const char e_assert_fails_fifth_argument[]
     53  = N_("E1116: \"assert_fails()\" fifth argument must be a string");
     54 static const char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[]
     55  = N_("E1142: Calling test_garbagecollect_now() while v:testing is not set");
     56 
     57 /// Prepare "gap" for an assert error and add the sourcing position.
     58 static void prepare_assert_error(garray_T *gap)
     59 {
     60  char *sname = estack_sfile(ESTACK_NONE);
     61 
     62  ga_init(gap, 1, 100);
     63  if (sname != NULL) {
     64    ga_concat(gap, sname);
     65    if (SOURCING_LNUM > 0) {
     66      ga_concat(gap, " ");
     67    }
     68  }
     69  if (SOURCING_LNUM > 0) {
     70    char buf[NUMBUFLEN];
     71    size_t buflen = vim_snprintf_safelen(buf, ARRAY_SIZE(buf),
     72                                         "line %" PRId64, (int64_t)SOURCING_LNUM);
     73    ga_concat_len(gap, buf, buflen);
     74  }
     75  if (sname != NULL || SOURCING_LNUM > 0) {
     76    GA_CONCAT_LITERAL(gap, ": ");
     77  }
     78  xfree(sname);
     79 }
     80 
     81 /// Append "p[clen]" to "gap", escaping unprintable characters.
     82 /// Changes NL to \n, CR to \r, etc.
     83 static void ga_concat_esc(garray_T *gap, const char *p, int clen)
     84  FUNC_ATTR_NONNULL_ALL
     85 {
     86  char buf[NUMBUFLEN];
     87 
     88  if (clen > 1) {
     89    memmove(buf, p, (size_t)clen);
     90    buf[clen] = NUL;
     91    ga_concat_len(gap, buf, (size_t)clen);
     92    return;
     93  }
     94 
     95  switch (*p) {
     96  case BS:
     97    GA_CONCAT_LITERAL(gap, "\\b"); break;
     98  case ESC:
     99    GA_CONCAT_LITERAL(gap, "\\e"); break;
    100  case FF:
    101    GA_CONCAT_LITERAL(gap, "\\f"); break;
    102  case NL:
    103    GA_CONCAT_LITERAL(gap, "\\n"); break;
    104  case TAB:
    105    GA_CONCAT_LITERAL(gap, "\\t"); break;
    106  case CAR:
    107    GA_CONCAT_LITERAL(gap, "\\r"); break;
    108  case '\\':
    109    GA_CONCAT_LITERAL(gap, "\\\\"); break;
    110  default:
    111    if ((uint8_t)(*p) < ' ' || *p == 0x7f) {
    112      size_t buflen = vim_snprintf_safelen(buf, NUMBUFLEN, "\\x%02x", *p);
    113      ga_concat_len(gap, buf, buflen);
    114    } else {
    115      ga_append(gap, (uint8_t)(*p));
    116    }
    117    break;
    118  }
    119 }
    120 
    121 /// Append "str" to "gap", escaping unprintable characters.
    122 /// Changes NL to \n, CR to \r, etc.
    123 static void ga_concat_shorten_esc(garray_T *gap, const char *str)
    124  FUNC_ATTR_NONNULL_ARG(1)
    125 {
    126  char buf[NUMBUFLEN];
    127 
    128  if (str == NULL) {
    129    GA_CONCAT_LITERAL(gap, "NULL");
    130    return;
    131  }
    132 
    133  for (const char *p = str; *p != NUL;) {
    134    int same_len = 1;
    135    const char *s = p;
    136    const int c = mb_cptr2char_adv(&s);
    137    const int clen = (int)(s - p);
    138    while (*s != NUL && c == utf_ptr2char(s)) {
    139      same_len++;
    140      s += clen;
    141    }
    142    if (same_len > 20) {
    143      GA_CONCAT_LITERAL(gap, "\\[");
    144      ga_concat_esc(gap, p, clen);
    145      GA_CONCAT_LITERAL(gap, " occurs ");
    146      size_t buflen = vim_snprintf_safelen(buf, NUMBUFLEN, "%d", same_len);
    147      ga_concat_len(gap, buf, buflen);
    148      GA_CONCAT_LITERAL(gap, " times]");
    149      p = s;
    150    } else {
    151      ga_concat_esc(gap, p, clen);
    152      p += clen;
    153    }
    154  }
    155 }
    156 
    157 /// Fill "gap" with information about an assert error.
    158 static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, const char *exp_str,
    159                              typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype)
    160 {
    161  typval_T *exp_tv = exp_tv_arg;
    162  typval_T *got_tv = got_tv_arg;
    163  bool did_copy = false;
    164  int omitted = 0;
    165 
    166  if (opt_msg_tv->v_type != VAR_UNKNOWN
    167      && !(opt_msg_tv->v_type == VAR_STRING
    168           && (opt_msg_tv->vval.v_string == NULL
    169               || *opt_msg_tv->vval.v_string == NUL))) {
    170    char *tofree = encode_tv2echo(opt_msg_tv, NULL);
    171    ga_concat(gap, tofree);
    172    xfree(tofree);
    173    GA_CONCAT_LITERAL(gap, ": ");
    174  }
    175 
    176  if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) {
    177    GA_CONCAT_LITERAL(gap, "Pattern ");
    178  } else if (atype == ASSERT_NOTEQUAL) {
    179    GA_CONCAT_LITERAL(gap, "Expected not equal to ");
    180  } else {
    181    GA_CONCAT_LITERAL(gap, "Expected ");
    182  }
    183 
    184  if (exp_str == NULL) {
    185    // When comparing dictionaries, drop the items that are equal, so that
    186    // it's a lot easier to see what differs.
    187    if (atype != ASSERT_NOTEQUAL
    188        && exp_tv->v_type == VAR_DICT && got_tv->v_type == VAR_DICT
    189        && exp_tv->vval.v_dict != NULL && got_tv->vval.v_dict != NULL) {
    190      dict_T *exp_d = exp_tv->vval.v_dict;
    191      dict_T *got_d = got_tv->vval.v_dict;
    192 
    193      did_copy = true;
    194      exp_tv->vval.v_dict = tv_dict_alloc();
    195      got_tv->vval.v_dict = tv_dict_alloc();
    196 
    197      int todo = (int)exp_d->dv_hashtab.ht_used;
    198      for (const hashitem_T *hi = exp_d->dv_hashtab.ht_array; todo > 0; hi++) {
    199        if (!HASHITEM_EMPTY(hi)) {
    200          dictitem_T *item2 = tv_dict_find(got_d, hi->hi_key, -1);
    201          if (item2 == NULL
    202              || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false)) {
    203            // item of exp_d not present in got_d or values differ.
    204            const size_t key_len = strlen(hi->hi_key);
    205            tv_dict_add_tv(exp_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
    206            if (item2 != NULL) {
    207              tv_dict_add_tv(got_tv->vval.v_dict, hi->hi_key, key_len, &item2->di_tv);
    208            }
    209          } else {
    210            omitted++;
    211          }
    212          todo--;
    213        }
    214      }
    215 
    216      // Add items only present in got_d.
    217      todo = (int)got_d->dv_hashtab.ht_used;
    218      for (const hashitem_T *hi = got_d->dv_hashtab.ht_array; todo > 0; hi++) {
    219        if (!HASHITEM_EMPTY(hi)) {
    220          dictitem_T *item2 = tv_dict_find(exp_d, hi->hi_key, -1);
    221          if (item2 == NULL) {
    222            // item of got_d not present in exp_d
    223            const size_t key_len = strlen(hi->hi_key);
    224            tv_dict_add_tv(got_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
    225          }
    226          todo--;
    227        }
    228      }
    229    }
    230 
    231    char *tofree = encode_tv2string(exp_tv, NULL);
    232    ga_concat_shorten_esc(gap, tofree);
    233    xfree(tofree);
    234  } else {
    235    if (atype == ASSERT_FAILS) {
    236      GA_CONCAT_LITERAL(gap, "'");
    237    }
    238    ga_concat_shorten_esc(gap, exp_str);
    239    if (atype == ASSERT_FAILS) {
    240      GA_CONCAT_LITERAL(gap, "'");
    241    }
    242  }
    243 
    244  if (atype != ASSERT_NOTEQUAL) {
    245    if (atype == ASSERT_MATCH) {
    246      GA_CONCAT_LITERAL(gap, " does not match ");
    247    } else if (atype == ASSERT_NOTMATCH) {
    248      GA_CONCAT_LITERAL(gap, " does match ");
    249    } else {
    250      GA_CONCAT_LITERAL(gap, " but got ");
    251    }
    252    char *tofree = encode_tv2string(got_tv, NULL);
    253    ga_concat_shorten_esc(gap, tofree);
    254    xfree(tofree);
    255 
    256    if (omitted != 0) {
    257      char buf[100];
    258      size_t buflen = vim_snprintf_safelen(buf, sizeof(buf),
    259                                           " - %d equal item%s omitted",
    260                                           omitted, omitted == 1 ? "" : "s");
    261      ga_concat_len(gap, buf, buflen);
    262    }
    263  }
    264 
    265  if (did_copy) {
    266    tv_clear(exp_tv);
    267    tv_clear(got_tv);
    268  }
    269 }
    270 
    271 static int assert_equal_common(typval_T *argvars, assert_type_T atype)
    272  FUNC_ATTR_NONNULL_ALL
    273 {
    274  garray_T ga;
    275 
    276  if (tv_equal(&argvars[0], &argvars[1], false) != (atype == ASSERT_EQUAL)) {
    277    prepare_assert_error(&ga);
    278    fill_assert_error(&ga, &argvars[2], NULL,
    279                      &argvars[0], &argvars[1], atype);
    280    assert_error(&ga);
    281    ga_clear(&ga);
    282    return 1;
    283  }
    284  return 0;
    285 }
    286 
    287 static int assert_match_common(typval_T *argvars, assert_type_T atype)
    288  FUNC_ATTR_NONNULL_ALL
    289 {
    290  char buf1[NUMBUFLEN];
    291  char buf2[NUMBUFLEN];
    292  const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1);
    293  const char *const text = tv_get_string_buf_chk(&argvars[1], buf2);
    294 
    295  if (pat != NULL && text != NULL
    296      && pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) {
    297    garray_T ga;
    298    prepare_assert_error(&ga);
    299    fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype);
    300    assert_error(&ga);
    301    ga_clear(&ga);
    302    return 1;
    303  }
    304  return 0;
    305 }
    306 
    307 /// Common for assert_true() and assert_false().
    308 static int assert_bool(typval_T *argvars, bool is_true)
    309  FUNC_ATTR_NONNULL_ALL
    310 {
    311  bool error = false;
    312  garray_T ga;
    313 
    314  if ((argvars[0].v_type != VAR_NUMBER
    315       || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true
    316       || error)
    317      && (argvars[0].v_type != VAR_BOOL
    318          || (argvars[0].vval.v_bool
    319              != (BoolVarValue)(is_true
    320                                ? kBoolVarTrue
    321                                : kBoolVarFalse)))) {
    322    prepare_assert_error(&ga);
    323    fill_assert_error(&ga, &argvars[1],
    324                      is_true ? "True" : "False",
    325                      NULL, &argvars[0], ASSERT_OTHER);
    326    assert_error(&ga);
    327    ga_clear(&ga);
    328    return 1;
    329  }
    330  return 0;
    331 }
    332 
    333 static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd)
    334  FUNC_ATTR_NONNULL_ALL
    335 {
    336  if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
    337    char *const tofree = encode_tv2echo(&argvars[2], NULL);
    338    ga_concat(gap, tofree);
    339    xfree(tofree);
    340  } else {
    341    ga_concat(gap, cmd);
    342  }
    343 }
    344 
    345 static int assert_beeps(typval_T *argvars, bool no_beep)
    346  FUNC_ATTR_NONNULL_ALL
    347 {
    348  const char *const cmd = tv_get_string_chk(&argvars[0]);
    349  int ret = 0;
    350 
    351  called_vim_beep = false;
    352  suppress_errthrow = true;
    353  emsg_silent = false;
    354  do_cmdline_cmd(cmd);
    355  if (no_beep ? called_vim_beep : !called_vim_beep) {
    356    garray_T ga;
    357    prepare_assert_error(&ga);
    358    if (no_beep) {
    359      GA_CONCAT_LITERAL(&ga, "command did beep: ");
    360    } else {
    361      GA_CONCAT_LITERAL(&ga, "command did not beep: ");
    362    }
    363    ga_concat(&ga, cmd);
    364    assert_error(&ga);
    365    ga_clear(&ga);
    366    ret = 1;
    367  }
    368 
    369  suppress_errthrow = false;
    370  emsg_on_display = false;
    371  return ret;
    372 }
    373 
    374 /// "assert_beeps(cmd [, error])" function
    375 void f_assert_beeps(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    376 {
    377  rettv->vval.v_number = assert_beeps(argvars, false);
    378 }
    379 
    380 /// "assert_nobeep(cmd [, error])" function
    381 void f_assert_nobeep(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    382 {
    383  rettv->vval.v_number = assert_beeps(argvars, true);
    384 }
    385 
    386 /// "assert_equal(expected, actual[, msg])" function
    387 void f_assert_equal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    388 {
    389  rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
    390 }
    391 
    392 static int assert_equalfile(typval_T *argvars)
    393  FUNC_ATTR_NONNULL_ALL
    394 {
    395  char buf1[NUMBUFLEN];
    396  char buf2[NUMBUFLEN];
    397  const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
    398  const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
    399 
    400  if (fname1 == NULL || fname2 == NULL) {
    401    return 0;
    402  }
    403 
    404  IObuff[0] = NUL;
    405  size_t IObufflen = 0;
    406  FILE *const fd1 = os_fopen(fname1, READBIN);
    407  char line1[200];
    408  char line2[200];
    409  ptrdiff_t lineidx = 0;
    410  if (fd1 == NULL) {
    411    IObufflen = vim_snprintf_safelen(IObuff, IOSIZE, e_cant_read_file_str, fname1);
    412  } else {
    413    FILE *const fd2 = os_fopen(fname2, READBIN);
    414    if (fd2 == NULL) {
    415      fclose(fd1);
    416      IObufflen = vim_snprintf_safelen(IObuff, IOSIZE, e_cant_read_file_str, fname2);
    417    } else {
    418      int64_t linecount = 1;
    419      for (int64_t count = 0;; count++) {
    420        const int c1 = fgetc(fd1);
    421        const int c2 = fgetc(fd2);
    422        if (c1 == EOF) {
    423          if (c2 != EOF) {
    424            IObufflen = xstrlcpy(IObuff, "first file is shorter", IOSIZE);
    425          }
    426          break;
    427        } else if (c2 == EOF) {
    428          IObufflen = xstrlcpy(IObuff, "second file is shorter", IOSIZE);
    429          break;
    430        } else {
    431          line1[lineidx] = (char)c1;
    432          line2[lineidx] = (char)c2;
    433          lineidx++;
    434          if (c1 != c2) {
    435            IObufflen
    436              = vim_snprintf_safelen(IObuff, IOSIZE,
    437                                     "difference at byte %" PRId64 ", line %" PRId64,
    438                                     count, linecount);
    439            break;
    440          }
    441        }
    442        if (c1 == NL) {
    443          linecount++;
    444          lineidx = 0;
    445        } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) {
    446          memmove(line1, line1 + 100, (size_t)(lineidx - 100));
    447          memmove(line2, line2 + 100, (size_t)(lineidx - 100));
    448          lineidx -= 100;
    449        }
    450      }
    451      fclose(fd1);
    452      fclose(fd2);
    453    }
    454  }
    455 
    456  if (IObufflen > 0) {
    457    garray_T ga;
    458    prepare_assert_error(&ga);
    459    if (argvars[2].v_type != VAR_UNKNOWN) {
    460      char *const tofree = encode_tv2echo(&argvars[2], NULL);
    461      ga_concat(&ga, tofree);
    462      xfree(tofree);
    463      GA_CONCAT_LITERAL(&ga, ": ");
    464    }
    465    ga_concat_len(&ga, IObuff, IObufflen);
    466    if (lineidx > 0) {
    467      line1[lineidx] = NUL;
    468      line2[lineidx] = NUL;
    469      GA_CONCAT_LITERAL(&ga, " after \"");
    470      ga_concat_len(&ga, line1, (size_t)lineidx);
    471      if (strcmp(line1, line2) != 0) {
    472        GA_CONCAT_LITERAL(&ga, "\" vs \"");
    473        ga_concat_len(&ga, line2, (size_t)lineidx);
    474      }
    475      GA_CONCAT_LITERAL(&ga, "\"");
    476    }
    477    assert_error(&ga);
    478    ga_clear(&ga);
    479    return 1;
    480  }
    481 
    482  return 0;
    483 }
    484 
    485 /// "assert_equalfile(fname-one, fname-two[, msg])" function
    486 void f_assert_equalfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    487 {
    488  rettv->vval.v_number = assert_equalfile(argvars);
    489 }
    490 
    491 /// "assert_notequal(expected, actual[, msg])" function
    492 void f_assert_notequal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    493 {
    494  rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
    495 }
    496 
    497 /// "assert_exception(string[, msg])" function
    498 void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    499 {
    500  garray_T ga;
    501 
    502  const char *const error = tv_get_string_chk(&argvars[0]);
    503  if (*get_vim_var_str(VV_EXCEPTION) == NUL) {
    504    prepare_assert_error(&ga);
    505    GA_CONCAT_LITERAL(&ga, "v:exception is not set");
    506    assert_error(&ga);
    507    ga_clear(&ga);
    508    rettv->vval.v_number = 1;
    509  } else if (error != NULL
    510             && strstr(get_vim_var_str(VV_EXCEPTION), error) == NULL) {
    511    prepare_assert_error(&ga);
    512    fill_assert_error(&ga, &argvars[1], NULL, &argvars[0],
    513                      get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER);
    514    assert_error(&ga);
    515    ga_clear(&ga);
    516    rettv->vval.v_number = 1;
    517  }
    518 }
    519 
    520 /// "assert_fails(cmd [, error [, msg]])" function
    521 void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    522 {
    523  garray_T ga;
    524  const int save_trylevel = trylevel;
    525  const int called_emsg_before = called_emsg;
    526  const char *wrong_arg_msg = NULL;
    527  char *tofree = NULL;
    528 
    529  if (tv_check_for_string_or_number_arg(argvars, 0) == FAIL
    530      || tv_check_for_opt_string_or_list_arg(argvars, 1) == FAIL
    531      || (argvars[1].v_type != VAR_UNKNOWN
    532          && (argvars[2].v_type != VAR_UNKNOWN
    533              && (tv_check_for_opt_number_arg(argvars, 3) == FAIL
    534                  || (argvars[3].v_type != VAR_UNKNOWN
    535                      && tv_check_for_opt_string_arg(argvars, 4) == FAIL))))) {
    536    return;
    537  }
    538 
    539  // trylevel must be zero for a ":throw" command to be considered failed
    540  trylevel = 0;
    541  suppress_errthrow = true;
    542  in_assert_fails = true;
    543  no_wait_return++;
    544 
    545  const char *const cmd = tv_get_string_chk(&argvars[0]);
    546  do_cmdline_cmd(cmd);
    547 
    548  // reset here for any errors reported below
    549  trylevel = save_trylevel;
    550  suppress_errthrow = false;
    551 
    552  if (called_emsg == called_emsg_before) {
    553    prepare_assert_error(&ga);
    554    GA_CONCAT_LITERAL(&ga, "command did not fail: ");
    555    assert_append_cmd_or_arg(&ga, argvars, cmd);
    556    assert_error(&ga);
    557    ga_clear(&ga);
    558    rettv->vval.v_number = 1;
    559  } else if (argvars[1].v_type != VAR_UNKNOWN) {
    560    char buf[NUMBUFLEN];
    561    const char *expected;
    562    const char *expected_str = NULL;
    563    bool error_found = false;
    564    int error_found_index = 1;
    565    char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg;
    566 
    567    if (argvars[1].v_type == VAR_STRING) {
    568      expected = tv_get_string_buf_chk(&argvars[1], buf);
    569      error_found = expected == NULL || strstr(actual, expected) == NULL;
    570    } else if (argvars[1].v_type == VAR_LIST) {
    571      const list_T *const list = argvars[1].vval.v_list;
    572      if (list == NULL || tv_list_len(list) < 1 || tv_list_len(list) > 2) {
    573        wrong_arg_msg = e_assert_fails_second_arg;
    574        goto theend;
    575      }
    576      const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list));
    577      expected = tv_get_string_buf_chk(tv, buf);
    578      if (expected == NULL) {
    579        goto theend;
    580      }
    581      if (!pattern_match(expected, actual, false)) {
    582        error_found = true;
    583        expected_str = expected;
    584      } else if (tv_list_len(list) == 2) {
    585        // make a copy, an error in pattern_match() may free it
    586        tofree = actual = xstrdup(get_vim_var_str(VV_ERRMSG));
    587        tv = TV_LIST_ITEM_TV(tv_list_last(list));
    588        expected = tv_get_string_buf_chk(tv, buf);
    589        if (expected == NULL) {
    590          goto theend;
    591        }
    592        if (!pattern_match(expected, actual, false)) {
    593          error_found = true;
    594          expected_str = expected;
    595        }
    596      }
    597    } else {
    598      wrong_arg_msg = e_assert_fails_second_arg;
    599      goto theend;
    600    }
    601 
    602    if (!error_found && argvars[2].v_type != VAR_UNKNOWN
    603        && argvars[3].v_type != VAR_UNKNOWN) {
    604      if (argvars[3].v_type != VAR_NUMBER) {
    605        wrong_arg_msg = e_assert_fails_fourth_argument;
    606        goto theend;
    607      } else if (argvars[3].vval.v_number >= 0
    608                 && argvars[3].vval.v_number != emsg_assert_fails_lnum) {
    609        error_found = true;
    610        error_found_index = 3;
    611      }
    612      if (!error_found && argvars[4].v_type != VAR_UNKNOWN) {
    613        if (argvars[4].v_type != VAR_STRING) {
    614          wrong_arg_msg = e_assert_fails_fifth_argument;
    615          goto theend;
    616        } else if (argvars[4].vval.v_string != NULL
    617                   && !pattern_match(argvars[4].vval.v_string,
    618                                     emsg_assert_fails_context, false)) {
    619          error_found = true;
    620          error_found_index = 4;
    621        }
    622      }
    623    }
    624 
    625    if (error_found) {
    626      typval_T actual_tv;
    627      prepare_assert_error(&ga);
    628      if (error_found_index == 3) {
    629        actual_tv.v_type = VAR_NUMBER;
    630        actual_tv.vval.v_number = emsg_assert_fails_lnum;
    631      } else if (error_found_index == 4) {
    632        actual_tv.v_type = VAR_STRING;
    633        actual_tv.vval.v_string = emsg_assert_fails_context;
    634      } else {
    635        actual_tv.v_type = VAR_STRING;
    636        actual_tv.vval.v_string = actual;
    637      }
    638      fill_assert_error(&ga, &argvars[2], expected_str,
    639                        &argvars[error_found_index], &actual_tv, ASSERT_FAILS);
    640      GA_CONCAT_LITERAL(&ga, ": ");
    641      assert_append_cmd_or_arg(&ga, argvars, cmd);
    642      assert_error(&ga);
    643      ga_clear(&ga);
    644      rettv->vval.v_number = 1;
    645    }
    646  }
    647 
    648 theend:
    649  trylevel = save_trylevel;
    650  suppress_errthrow = false;
    651  in_assert_fails = false;
    652  did_emsg = false;
    653  got_int = false;
    654  msg_col = 0;
    655  no_wait_return--;
    656  need_wait_return = false;
    657  emsg_on_display = false;
    658  msg_reset_scroll();
    659  lines_left = Rows;
    660  XFREE_CLEAR(emsg_assert_fails_msg);
    661  xfree(tofree);
    662  set_vim_var_string(VV_ERRMSG, NULL, 0);
    663  if (wrong_arg_msg != NULL) {
    664    emsg(_(wrong_arg_msg));
    665  }
    666 }
    667 
    668 // "assert_false(actual[, msg])" function
    669 void f_assert_false(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    670 {
    671  rettv->vval.v_number = assert_bool(argvars, false);
    672 }
    673 
    674 static int assert_inrange(typval_T *argvars)
    675  FUNC_ATTR_NONNULL_ALL
    676 {
    677  bool error = false;
    678 
    679  if (argvars[0].v_type == VAR_FLOAT
    680      || argvars[1].v_type == VAR_FLOAT
    681      || argvars[2].v_type == VAR_FLOAT) {
    682    const float_T flower = tv_get_float(&argvars[0]);
    683    const float_T fupper = tv_get_float(&argvars[1]);
    684    const float_T factual = tv_get_float(&argvars[2]);
    685 
    686    if (factual < flower || factual > fupper) {
    687      garray_T ga;
    688      prepare_assert_error(&ga);
    689      char expected_str[200];
    690      vim_snprintf(expected_str, sizeof(expected_str), "range %g - %g,", flower, fupper);
    691      fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER);
    692      assert_error(&ga);
    693      ga_clear(&ga);
    694      return 1;
    695    }
    696  } else {
    697    const varnumber_T lower = tv_get_number_chk(&argvars[0], &error);
    698    const varnumber_T upper = tv_get_number_chk(&argvars[1], &error);
    699    const varnumber_T actual = tv_get_number_chk(&argvars[2], &error);
    700 
    701    if (error) {
    702      return 0;
    703    }
    704    if (actual < lower || actual > upper) {
    705      garray_T ga;
    706      prepare_assert_error(&ga);
    707      char expected_str[200];
    708      vim_snprintf(expected_str, sizeof(expected_str),
    709                   "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
    710                   lower, upper);
    711      fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER);
    712      assert_error(&ga);
    713      ga_clear(&ga);
    714      return 1;
    715    }
    716  }
    717  return 0;
    718 }
    719 
    720 /// "assert_inrange(lower, upper[, msg])" function
    721 void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    722 {
    723  if (tv_check_for_float_or_nr_arg(argvars, 0) == FAIL
    724      || tv_check_for_float_or_nr_arg(argvars, 1) == FAIL
    725      || tv_check_for_float_or_nr_arg(argvars, 2) == FAIL
    726      || tv_check_for_opt_string_arg(argvars, 3) == FAIL) {
    727    return;
    728  }
    729 
    730  rettv->vval.v_number = assert_inrange(argvars);
    731 }
    732 
    733 /// "assert_match(pattern, actual[, msg])" function
    734 void f_assert_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    735 {
    736  rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
    737 }
    738 
    739 /// "assert_notmatch(pattern, actual[, msg])" function
    740 void f_assert_notmatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    741 {
    742  rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
    743 }
    744 
    745 /// "assert_report(msg)" function
    746 void f_assert_report(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    747 {
    748  garray_T ga;
    749 
    750  prepare_assert_error(&ga);
    751  ga_concat(&ga, tv_get_string(&argvars[0]));
    752  assert_error(&ga);
    753  ga_clear(&ga);
    754  rettv->vval.v_number = 1;
    755 }
    756 
    757 /// "assert_true(actual[, msg])" function
    758 void f_assert_true(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    759 {
    760  rettv->vval.v_number = assert_bool(argvars, true);
    761 }
    762 
    763 /// "test_garbagecollect_now()" function
    764 void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    765 {
    766  // This is dangerous, any Lists and Dicts used internally may be freed
    767  // while still in use.
    768  if (!get_vim_var_nr(VV_TESTING)) {
    769    emsg(_(e_calling_test_garbagecollect_now_while_v_testing_is_not_set));
    770  } else {
    771    garbage_collect(true);
    772  }
    773 }
    774 
    775 /// "test_write_list_log()" function
    776 void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, EvalFuncData fptr)
    777 {
    778  const char *const fname = tv_get_string_chk(&argvars[0]);
    779  if (fname == NULL) {
    780    return;
    781  }
    782 }