├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lfmt.c ├── luaunit.lua ├── rockspecs ├── lua-fmt-0.1.0-1.rockspec └── lua-fmt-scm-1.rockspec └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.gcda 4 | *.gcno 5 | *.gcov 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | dist: xenial 3 | os: linux 4 | 5 | env: 6 | global: 7 | - ROCKSPEC=rockspecs/lua-fmt-scm-1.rockspec 8 | jobs: 9 | - LUA="lua 5.1" 10 | - LUA="lua 5.2" 11 | - LUA="lua 5.3" 12 | - LUA="lua 5.4" 13 | - LUA="luajit 2.0" 14 | - LUA="luajit 2.1" 15 | 16 | branches: 17 | only: 18 | - master 19 | 20 | before_install: 21 | - pip install --user urllib3[secure] cpp-coveralls 22 | - curl -O https://raw.githubusercontent.com/luarocks/hererocks/master/hererocks.py 23 | - python hererocks.py env --$LUA -rlatest 24 | - source env/bin/activate 25 | 26 | install: 27 | - luarocks make $ROCKSPEC CFLAGS="-O3 -fPIC -Wall -Wextra --coverage" LIBFLAG="-shared --coverage" 28 | 29 | script: 30 | - lua test.lua 31 | 32 | after_success: 33 | - coveralls 34 | 35 | notifications: 36 | email: 37 | on_success: change 38 | on_failure: always 39 | 40 | # vim: ft=yaml nu et sw=2 fdc=2 fdm=syntax 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Xavier Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-fmt - A fmtlib implement for Lua 2 | 3 | [![Build Status](https://travis-ci.org/starwing/lua-fmt.svg?branch=master)](https://travis-ci.org/starwing/lua-fmt)[![Coverage Status](https://coveralls.io/repos/github/starwing/lua-fmt/badge.svg?branch=master)](https://coveralls.io/github/starwing/lua-fmt?branch=master) 4 | 5 | 6 | Lua-fmt is a fmt like simple C module for Lua to format string. 7 | 8 | The format syntax is same with 9 | [fmtlib](https://fmt.dev/latest/syntax.html), which is same with 10 | Python's `format` routine. 11 | 12 | ## Install 13 | 14 | The simplest way to install it is using Luarocks: 15 | 16 | ```shell 17 | luarocks install --server=https://luarocks.org/dev lua-fmt 18 | ``` 19 | 20 | Or, just compile the single C file: 21 | 22 | ```shell 23 | # Linux 24 | gcc -o lfmt.so -O3 -fPIC -shared lfmt.c 25 | # macOS 26 | gcc -o lfmt.so -O3 -fPIC -shared -undefined dynamic_lookup lfmt.c 27 | # Windows 28 | cl /Fefmt.dll /LD /MT /O2 /DLUA_BUILD_AS_DLL /Ipath/to/lua/include lfmt.c path/to/lua/lib 29 | ``` 30 | 31 | # Example 32 | 33 | ```lua 34 | local fmt = require "fmt" 35 | 36 | -- automatic indexed argument 37 | print(fmt("{} {} {}", 1,2, 3)) --> "1 2 3" 38 | 39 | -- manual indexed argument 40 | print(fmt("{2}, {1}", "World", "Hello")) --> "Hello, World" 41 | 42 | -- named indexed argument 43 | print(fmt("{name} is {type}", { name = "foo", type = "bar" })) --> "foo is bar" 44 | print(fmt("{t.name} is {t.type}", {t = { name = "foo", type = "bar" }})) --> "foo is bar" 45 | 46 | -- format specifier 47 | print(fmt("{:b}", 42)) --> "101010" 48 | 49 | ``` 50 | 51 | ## Document 52 | 53 | Mostly same as [fmtlib](https://fmt.dev/latest/syntax.html). 54 | 55 | Format strings contain “replacement fields” surrounded by curly braces 56 | `{}`. Anything that is not contained in braces is considered literal 57 | text, which is copied unchanged to the output. If you need to include 58 | a brace character in the literal text, it can be escaped by doubling: 59 | `{{` and `}}`. 60 | 61 | The grammar for a replacement field is as follows: 62 | 63 | ``` 64 | replacement_field ::= "{" [arg_id] [":" format_spec] "}" 65 | arg_id ::= field_name accessor* 66 | field_name ::= integer | identifier 67 | accessor ::= "." field_name | "[" key_name "]" 68 | key_name ::= field_name | * 69 | integer ::= digit+ 70 | digit ::= "0"..."9" 71 | identifier ::= id_start id_continue* 72 | id_start ::= "a"..."z" | "A"..."Z" | "_" 73 | id_continue ::= id_start | digit 74 | ``` 75 | 76 | the mainly difference is the support of accessor, which is supported 77 | by Python but not by fmtlib. 78 | 79 | - - - 80 | 81 | “Format specifications” are used within replacement fields contained 82 | within a format string to define how individual values are presented. 83 | Each formattable type may define how the format specification is to be 84 | interpreted. 85 | 86 | Most built-in types implement the following options for format 87 | specifications, although some of the formatting options are only 88 | supported by the numeric types. 89 | 90 | The general form of a standard format specifier is: 91 | 92 | ``` 93 | format_spec ::= [[fill]align][sign]["#"]["0"][width][grouping]["." precision][type] 94 | fill ::= 95 | align ::= "<" | ">" | "^" 96 | sign ::= "+" | "-" | " " 97 | width ::= integer | "{" [arg_id] "}" 98 | grouping ::= "_" | "," 99 | precision ::= integer | "{" [arg_id] "}" 100 | type ::= int_type | flt_type | str_type 101 | int_type ::= "b" | "B" | "d" | "o" | "x" | "X" | "c" 102 | flt_type ::= "e" | "E" | "f" | "F" | "g" | "G" | "%" 103 | str_type ::= "p" | "s" 104 | ``` 105 | 106 | Differences (all exists in Python): 107 | - add grouping support for int_type: `"{:_}"` e.g. `"10_000"` 108 | - add `"%"` specifier for float type: `"{:%}"` e.g. `"100.0%"` 109 | 110 | Lua type vs. type specifiers: 111 | | Lua Type | Type specifiers | 112 | | --------- | ------------------ | 113 | | "integer" | `int_type` | 114 | | "float" | `flt_type` | 115 | | Others | `str_type` | 116 | 117 | 118 | -------------------------------------------------------------------------------- /lfmt.c: -------------------------------------------------------------------------------- 1 | #define LUA_LIB 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #if LUA_VERSION_NUM == 501 9 | static void luaL_tolstring(lua_State *L, int idx, size_t *len) { 10 | int tt; const char *kind; (void)len; 11 | if (luaL_callmeta(L, idx, "__tostring")) { 12 | if (!lua_isstring(L, -1)) 13 | luaL_error(L, "'__tostring' must return a string"); 14 | return; 15 | } 16 | switch (lua_type(L, idx)) { 17 | case LUA_TSTRING: 18 | lua_pushvalue(L, idx); break; 19 | case LUA_TBOOLEAN: 20 | lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false")); 21 | break; 22 | case LUA_TNIL: 23 | lua_pushliteral(L, "nil"); 24 | break; 25 | default: 26 | tt = luaL_getmetafield(L, idx, "__name"); 27 | kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : 28 | luaL_typename(L, idx); 29 | lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx)); 30 | if (tt != LUA_TNIL) lua_remove(L, -2); 31 | } 32 | } 33 | #endif 34 | 35 | #if LUA_VERSION_NUM < 502 && !defined(LUA_OK) 36 | static lua_Integer lua_tointegerx(lua_State *L, int idx, int *isint) { 37 | lua_Integer i = lua_tointeger(L, idx); 38 | *isint = i == 0 ? lua_type(L, idx)==LUA_TNUMBER : lua_tonumber(L, idx)==i; 39 | return i; 40 | } 41 | #endif 42 | 43 | #if LUA_VERSION_NUM < 503 44 | static void lua_geti(lua_State *L, int idx, int i) 45 | { lua_pushinteger(L, i); lua_gettable(L, idx); } 46 | 47 | static int lua_isinteger(lua_State *L, int idx) { 48 | lua_Number v = lua_tonumber(L, idx); 49 | if (v == 0.0 && lua_type(L,idx) != LUA_TNUMBER) return 0; 50 | return (lua_Number)(lua_Integer)v == v; 51 | } 52 | #endif 53 | 54 | typedef struct fmt_State { 55 | lua_State *L; 56 | luaL_Buffer B; 57 | int idx, top, zeroing; 58 | } fmt_State; 59 | 60 | #define fmt_check(S,cond,...) ((void)((cond)||luaL_error((S)->L,__VA_ARGS__))) 61 | 62 | /* read argid */ 63 | 64 | #define fmt_argslot(S) ((S)->top+1) 65 | #define fmt_tmpslot(S) ((S)->top+2) 66 | #define fmt_isdigit(ch) ((ch) >= '0' && (ch) <= '9') 67 | #define fmt_isalpha(ch) ((ch) == '_' \ 68 | || ((ch) >= 'A' && (ch) <= 'Z') \ 69 | || ((ch) >= 'a' && (ch) <= 'z')) 70 | 71 | #define FMT_AUTO "automatic field numbering" 72 | #define FMT_MANUAL "manual field specification" 73 | #define FMT_A2M "cannot switch from " FMT_AUTO " to " FMT_MANUAL 74 | #define FMT_M2A "cannot switch from " FMT_MANUAL " to " FMT_AUTO 75 | 76 | static void fmt_manualidx(fmt_State *S) 77 | { fmt_check(S, S->idx <= 1, FMT_A2M); S->idx = 0; } 78 | 79 | static int fmt_autoidx(fmt_State *S) { 80 | fmt_check(S, S->idx != 0, FMT_M2A); 81 | fmt_check(S, ++S->idx <= S->top, "automatic index out of range"); 82 | return S->idx; 83 | } 84 | 85 | static int fmt_integer(fmt_State *S, const char **pp) { 86 | const char *p = *pp; 87 | unsigned v = 0; 88 | do { 89 | int o = v < INT_MAX/10 || (v == INT_MAX/10 && *p++ <= INT_MAX%10); 90 | fmt_check(S, o, "Too many decimal digits in format string"); 91 | v = v*10 + (*p++ - '0'); 92 | } while (fmt_isdigit(*p)); 93 | return (*pp = p), v; 94 | } 95 | 96 | static void fmt_identity(fmt_State *S, const char **pp) { 97 | const char *p = *pp; 98 | while (*++p && (fmt_isalpha(*p) || fmt_isdigit(*p))) 99 | ; 100 | lua_pushlstring(S->L, *pp, p - *pp); 101 | *pp = p; 102 | } 103 | 104 | static void fmt_accessor(fmt_State *S, int to, const char **pp, const char *e) { 105 | /* "." (number | identity) | "[" "]" */ 106 | const char *p = *pp; 107 | do { 108 | int idx; 109 | if (*p++ == '.') { 110 | if (fmt_isdigit(*p)) 111 | lua_geti(S->L, to, fmt_integer(S, &p)); 112 | else if (fmt_isalpha(*p)) 113 | fmt_identity(S, &p), lua_gettable(S->L, to); 114 | else luaL_error(S->L, "unexpected '%c' in field name", *p); 115 | } else { /* *p == '[' */ 116 | const char *c = p; 117 | if (fmt_isdigit(*c) && ((idx = fmt_integer(S, &c)), *c == ']')) 118 | lua_geti(S->L, to, idx); 119 | else { 120 | while (c < e && *c != ']') ++c; 121 | fmt_check(S, c < e, "expected '}' before end of string1"); 122 | lua_pushlstring(S->L, p, c - p); 123 | lua_gettable(S->L, to); 124 | } 125 | p = c + 1; 126 | } 127 | lua_replace(S->L, to); 128 | } while (*p == '.' || *p == '['); 129 | *pp = p; 130 | } 131 | 132 | static void fmt_argid(fmt_State *S, int to, const char **pp, const char *e) { 133 | /* [(number | identity) [accessor]] */ 134 | const char *p = *pp; 135 | fmt_check(S, p < e, "expected '}' before end of string2"); 136 | do { 137 | int idx; 138 | if (*p == ':' || *p == '}') { 139 | idx = fmt_autoidx(S); 140 | lua_copy(S->L, idx, to); 141 | } else if (fmt_isdigit(*p)) { 142 | fmt_manualidx(S); 143 | idx = fmt_integer(S, &p) + 1; 144 | fmt_check(S, idx>1 && idx<=S->top, "argument index out of range"); 145 | lua_copy(S->L, idx, to); 146 | } else if (fmt_isalpha(*p)) { 147 | fmt_manualidx(S); 148 | fmt_identity(S, &p); 149 | lua_gettable(S->L, 2); 150 | lua_replace(S->L, to); 151 | } else luaL_error(S->L, "unexpected '%c' in field name", *p); 152 | } while (0); 153 | *pp = p; 154 | if (*p == '.' || *p == '[') fmt_accessor(S, to, pp, e); 155 | } 156 | 157 | /* read spec */ 158 | 159 | typedef struct fmt_Spec { 160 | int fill; 161 | int align; /* '<', '^', '>' */ 162 | int sign; /* ' ', '+', '-' */ 163 | int alter; /* '#' */ 164 | int zero; /* '0' */ 165 | int width; 166 | int grouping; /* '_', ',' */ 167 | int precision; 168 | int type; 169 | } fmt_Spec; 170 | 171 | static int fmt_readchar(fmt_State *S, const char **pp, const char *e) { 172 | int ch = *(*pp)++; 173 | fmt_check(S, *pp < e, "unmatched '{' in format spec"); 174 | return ch; 175 | } 176 | 177 | static int fmt_readint(fmt_State *S, const char *name, const char **pp, const char *e) { 178 | /* number | '{' argid '}' */ 179 | const char *p = *pp; 180 | int isint, v = 0; 181 | if (*p == '{') { 182 | p += 1; 183 | fmt_argid(S, fmt_tmpslot(S), &p, e); 184 | fmt_check(S, *p == '}', "unexpected '%c' in field name", *p); 185 | *pp = p + 1; 186 | v = (int)lua_tointegerx(S->L, fmt_tmpslot(S), &isint); 187 | fmt_check(S, isint, "integer expected for %s, got %s", 188 | name, luaL_typename(S->L, fmt_tmpslot(S))); 189 | } else if (fmt_isdigit(*p)) { 190 | v = fmt_integer(S, pp); 191 | fmt_check(S, *pp < e, "unmatched '{' in format spec"); 192 | } else luaL_error(S->L, "Format specifier missing %s", name); 193 | return v; 194 | } 195 | 196 | static void fmt_spec(fmt_State *S, fmt_Spec *d, const char **pp, const char *e) { 197 | /* [[fill]align][sign]["#"]["0"][width][grouping]["." precision][type] */ 198 | const char *p = *pp; 199 | if (p[1] == '<' || p[1] == '>' || p[1] == '^') { 200 | d->fill = fmt_readchar(S, &p, e); 201 | d->align = fmt_readchar(S, &p, e); 202 | } else if (*p == '<' || *p == '>' || *p == '^') 203 | d->align = fmt_readchar(S, &p, e); 204 | if (*p == ' ' || *p == '+' || *p == '-') 205 | d->sign = fmt_readchar(S, &p, e); 206 | if (*p == '#') d->alter = fmt_readchar(S, &p, e); 207 | if (*p == '0') d->zero = fmt_readchar(S, &p, e); 208 | if (fmt_isdigit(*p) || *p == '{') d->width = fmt_readint(S, "width", &p, e); 209 | if (*p == '_' || *p == ',') d->grouping = fmt_readchar(S, &p, e); 210 | if (*p == '.') ++p, d->precision = fmt_readint(S, "precision", &p, e); 211 | if (*p != '}') { 212 | const char *b = p++; 213 | d->type = *b; 214 | if (*p != '}') { 215 | while (p < e && *p != '}') ++p; 216 | fmt_check(S, p < e, "unmatched '{' in format spec"); 217 | luaL_error(S->L, "Invalid format specifier: '%s'", b); 218 | } 219 | } 220 | *pp = p; 221 | } 222 | 223 | /* write spec */ 224 | 225 | #define FMT_DELIMITPOS 3 226 | #define FMT_UTF8BUFFSIZ 8 227 | #define FMT_FMTLEN 10 /* "%#.99f" */ 228 | #define FMT_FLTMAXPREC 100 229 | #define FMT_INTBUFFSIZ 100 230 | #define FMT_PTRBUFFSIZ 100 231 | #define FMT_FLTBUFFSIZ (10 + FMT_FLTMAXPREC + FLT_MAX_10_EXP) 232 | 233 | static void fmt_addpadding(fmt_State *S, int ch, size_t len) { 234 | char *s; 235 | if (ch == 0) ch = ' '; 236 | while (len > LUAL_BUFFERSIZE) { 237 | s = luaL_prepbuffer(&S->B); 238 | memset(s, ch, LUAL_BUFFERSIZE); 239 | luaL_addsize(&S->B, LUAL_BUFFERSIZE); 240 | len -= LUAL_BUFFERSIZE; 241 | } 242 | s = luaL_prepbuffer(&S->B); 243 | memset(s, ch, len); 244 | luaL_addsize(&S->B, len); 245 | } 246 | 247 | static void fmt_addzeroing(fmt_State *S, const fmt_Spec *d, size_t len) { 248 | char *s = luaL_prepbuffer(&S->B); 249 | if (len > (size_t)S->zeroing) { 250 | int pref = (len - S->zeroing) % 4; 251 | if (pref > 2) *s++ = '0', luaL_addsize(&S->B, 1); 252 | if (pref > 0) *s++ = '0', *s++ = d->grouping, luaL_addsize(&S->B, 2); 253 | len -= pref; 254 | while (len > 4) { 255 | size_t curr = len > LUAL_BUFFERSIZE ? LUAL_BUFFERSIZE : len; 256 | s = luaL_prepbuffer(&S->B); 257 | while (curr > 4) { 258 | s[0] = s[1] = s[2] = '0', s[3] = d->grouping; 259 | s += 4, luaL_addsize(&S->B, 4), curr -= 4, len -= 4; 260 | } 261 | } 262 | } 263 | memset(s, '0', len), luaL_addsize(&S->B, len); 264 | } 265 | 266 | static void fmt_addstring(fmt_State *S, int shrink, const fmt_Spec *d, const char *s, size_t len) { 267 | size_t plen; 268 | if (shrink && d->precision) 269 | len = len > (size_t)d->precision ? (size_t)d->precision : len; 270 | if (len > (size_t)d->width) { 271 | luaL_addlstring(&S->B, s, len); 272 | return; 273 | } 274 | plen = d->width - (int)len; 275 | switch (d->align) { 276 | case 0: 277 | case '<': !d->zero || d->grouping == 0 ? 278 | fmt_addpadding(S, d->fill ? d->fill : d->zero, plen) : 279 | fmt_addzeroing(S, d, plen); 280 | luaL_addlstring(&S->B, s, len); break; 281 | case '>': luaL_addlstring(&S->B, s, len); 282 | fmt_addpadding(S, d->fill, plen); break; 283 | case '^': fmt_addpadding(S, d->fill, plen/2); 284 | luaL_addlstring(&S->B, s, len); 285 | fmt_addpadding(S, d->fill, plen - plen/2); break; 286 | } 287 | } 288 | 289 | static void fmt_dumpstr(fmt_State *S, const fmt_Spec *d, const char *s, size_t len) { 290 | fmt_check(S, !d->type || d->type == 's' || d->type == 'p', 291 | "Unknown format code '%c' for object of type 'string'", d->type); 292 | fmt_check(S, !d->sign, 293 | "Sign not allowed in string format specifier"); 294 | fmt_check(S, !d->alter, 295 | "Alternate form (#) not allowed in string format specifier"); 296 | fmt_check(S, !d->zero, 297 | "Zero form (0) not allowed in string format specifier"); 298 | fmt_check(S, !d->grouping, 299 | "Grouping form (%c) not allowed in string format specifier", 300 | d->grouping); 301 | fmt_addstring(S, 1, d, s, len); 302 | } 303 | 304 | static size_t fmt_pushutf8(unsigned long x, char buff[FMT_UTF8BUFFSIZ]) { 305 | char *p = buff + FMT_UTF8BUFFSIZ; 306 | unsigned int mfb = 0x3f; 307 | if (x < 0x80) 308 | *--p = (char)x; 309 | else { 310 | do { 311 | *--p = (char)(0x80 | (x & 0x3f)); 312 | x >>= 6, mfb >>= 1; 313 | } while (x > mfb); 314 | *--p = (char)((~mfb << 1) | x); 315 | } 316 | return p - buff; 317 | } 318 | 319 | static void fmt_dumpchar(fmt_State *S, lua_Integer cp, const fmt_Spec *d) { 320 | char buff[FMT_UTF8BUFFSIZ]; 321 | size_t loc; 322 | fmt_check(S, !d->sign, 323 | "Sign not allowed with integer format specifier 'c'"); 324 | fmt_check(S, !d->alter, 325 | "Alternate form (#) not allowed with integer format specifier 'c'"); 326 | fmt_check(S, !d->zero, 327 | "Zero form (0) not allowed with integer format specifier 'c'"); 328 | fmt_check(S, !d->grouping, 329 | "Cannot specify '%c' with 'c'", d->grouping); 330 | fmt_check(S, cp >= 0 && cp <= INT_MAX, 331 | "'c' arg not in range(%d)", INT_MAX); 332 | loc = fmt_pushutf8((unsigned long)cp, buff); 333 | fmt_addstring(S, 0, d, buff+loc, FMT_UTF8BUFFSIZ-loc); 334 | } 335 | 336 | static int fmt_writesign(int sign, int dsign) { 337 | switch (dsign) { 338 | case '+': return sign ? '+' : '-'; 339 | case ' ': return sign ? ' ' : '-'; 340 | default: return sign ? 0 : '-'; 341 | } 342 | } 343 | 344 | static int fmt_writeint(char **pp, lua_Integer v, const fmt_Spec *d) { 345 | const char *hexa = "0123456789abcdef"; 346 | int radix = 10, zeroing; 347 | char *p = *pp; 348 | switch (d->type) { 349 | case 'X': hexa = "0123456789ABCDEF"; /* FALLTHROUGH */ 350 | case 'x': radix = 16; break; 351 | case 'o': case 'O': radix = 8; break; 352 | case 'b': case 'B': radix = 2; break; 353 | } 354 | zeroing = d->grouping ? FMT_DELIMITPOS : 0; 355 | while (*--p = hexa[v % radix], v /= radix, --zeroing, v) 356 | if (!zeroing) zeroing = FMT_DELIMITPOS, *--p = d->grouping; 357 | *pp = p; 358 | return zeroing; 359 | } 360 | 361 | static void fmt_dumpint(fmt_State *S, lua_Integer v, fmt_Spec *d) { 362 | char buff[FMT_INTBUFFSIZ], *p = buff + FMT_INTBUFFSIZ, *b; 363 | int sign = !(v < 0), width = d->width; 364 | if (!sign) v = -v; 365 | S->zeroing = fmt_writeint(&p, v, d); 366 | b = p; 367 | if (d->alter && d->type != 0 && d->type != 'd') 368 | *--p = d->type, *--p = '0'; 369 | if ((p[-1] = fmt_writesign(sign, d->sign)) != 0) --p; 370 | if (d->zero && d->width > FMT_INTBUFFSIZ - (p-buff)) { 371 | if (b > p) luaL_addlstring(&S->B, p, b - p); 372 | width -= (int)(b - p), p = b; 373 | } 374 | d->width = width; 375 | fmt_addstring(S, 0, d, p, FMT_INTBUFFSIZ-(p-buff)); 376 | } 377 | 378 | static int fmt_writeflt(char *s, size_t n, lua_Number v, fmt_Spec *d) { 379 | int type = d->type ? d->type : 'g'; 380 | int (*ptr_snprintf)(char *s, size_t n, const char *fmt, ...) = snprintf; 381 | char fmt[FMT_FMTLEN]; 382 | const char *percent = ""; 383 | if (d->type == '%') type = 'f', v *= 100.0, percent = "%%"; 384 | if (d->precision) 385 | ptr_snprintf(fmt, FMT_FMTLEN, "%%%s.%d%c%s", 386 | d->alter ? "#" : "", d->precision, type, percent); 387 | else if ((lua_Number)(lua_Integer)v == v) 388 | ptr_snprintf(fmt, FMT_FMTLEN, "%%.1f%s", percent); 389 | else if (!*percent && type == 'g') 390 | return d->alter ? snprintf(s, n, "%#g", v) : 391 | snprintf(s, n, "%g", v); 392 | else 393 | ptr_snprintf(fmt, FMT_FMTLEN, "%%%s%c%s", 394 | d->alter ? "#" : "", type, percent); 395 | return ptr_snprintf(s, n, fmt, v); 396 | } 397 | 398 | static void fmt_dumpflt(fmt_State *S, lua_Number v, fmt_Spec *d) { 399 | int sign = !(v < 0), len, width = d->width; 400 | char buff[FMT_FLTBUFFSIZ], *p = buff, *dp = p; 401 | fmt_check(S, d->precision < FMT_FLTMAXPREC, 402 | "precision specifier too large"); 403 | fmt_check(S, !d->grouping, 404 | "Grouping form (%c) not allowed in float format specifier", 405 | d->grouping); 406 | if (!sign) v = -v; 407 | if ((*dp = fmt_writesign(sign, d->sign)) != 0) ++dp; 408 | len = fmt_writeflt(dp, FMT_FLTBUFFSIZ - (dp-buff), v, d); 409 | if (d->zero && width > len) { 410 | if (dp > p) luaL_addlstring(&S->B, buff, dp - p); 411 | width -= (int)(dp - buff), p = dp; 412 | } 413 | d->width = width; 414 | fmt_addstring(S, 0, d, p, len); 415 | } 416 | 417 | static void fmt_dumpnumber(fmt_State *S, fmt_Spec *d) { 418 | int type = d->type; 419 | if (type == 0) type = lua_isinteger(S->L, fmt_argslot(S)) ? 'd' : 'g'; 420 | switch (type) { 421 | case 'c': 422 | fmt_dumpchar(S, lua_tointeger(S->L, fmt_argslot(S)), d); break; 423 | case 'd': case 'b': case 'B': case 'o': case 'O': case 'x': case 'X': 424 | fmt_dumpint(S, lua_tointeger(S->L, fmt_argslot(S)), d); break; 425 | case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case '%': 426 | fmt_dumpflt(S, lua_tonumber(S->L, fmt_argslot(S)), d); break; 427 | default: 428 | luaL_error(S->L, "Unknown format code '%c' for object of type 'number'", 429 | d->type); 430 | } 431 | } 432 | 433 | static void fmt_dump(fmt_State *S, fmt_Spec *d) { 434 | int type = lua_type(S->L, fmt_argslot(S)); 435 | if (type == LUA_TNUMBER) 436 | fmt_dumpnumber(S, d); 437 | else if (d->type != 'p') { 438 | size_t len; 439 | const char *s = luaL_tolstring(S->L, fmt_argslot(S), &len); 440 | lua_replace(S->L, fmt_argslot(S)); 441 | fmt_dumpstr(S, d, s, len); 442 | } else { 443 | const char *s; 444 | fmt_check(S, type != LUA_TNIL && type != LUA_TBOOLEAN, 445 | "Unknown format code '%c' for object of type '%s'", 446 | d->type, lua_typename(S->L, type)); 447 | s = lua_pushfstring(S->L, "%p", lua_topointer(S->L, fmt_argslot(S))); 448 | lua_replace(S->L, fmt_argslot(S)); 449 | fmt_dumpstr(S, d, s, strlen(s)); 450 | } 451 | } 452 | 453 | /* format */ 454 | 455 | static void fmt_parse(fmt_State *S, fmt_Spec *d, const char **pp, const char *e) { 456 | /* "{" [arg_id] [":" format_spec] "}" */ 457 | const char *p = *pp; 458 | fmt_argid(S, fmt_argslot(S), &p, e); 459 | if (*p == ':' && ++p < e) 460 | fmt_spec(S, d, &p, e); 461 | fmt_check(S, p < e && *p == '}', "expected '}' before end of string3"); 462 | *pp = p + 1; 463 | } 464 | 465 | static int fmt_format(fmt_State *S, const char *p, const char *e) { 466 | lua_settop(S->L, S->top + 2); /* two helper slot */ 467 | luaL_buffinit(S->L, &S->B); 468 | for (;;) { 469 | const char *b = p; 470 | while (p < e && *p != '{' && *p != '}') ++p; 471 | if (b < p) luaL_addlstring(&S->B, b, p - b); 472 | if (p >= e) break; 473 | if (*p == p[1]) 474 | luaL_addchar(&S->B, *p), p += 2; 475 | else { 476 | fmt_Spec d; 477 | if (*p++ == '}' || p >= e) 478 | return luaL_error(S->L, 479 | "Single '%c' encountered in format string", p[-1]); 480 | memset(&d, 0, sizeof(d)); 481 | fmt_parse(S, &d, &p, e); 482 | fmt_dump(S, &d); 483 | } 484 | } 485 | return luaL_pushresult(&S->B), 1; 486 | } 487 | 488 | static int Lformat(lua_State *L) { 489 | fmt_State S; 490 | size_t len; 491 | const char *p = luaL_checklstring(L, 1, &len); 492 | S.L = L; 493 | S.idx = 1; 494 | S.top = lua_gettop(L); 495 | return fmt_format(&S, p, p+len); 496 | } 497 | 498 | LUALIB_API int luaopen_fmt(lua_State *L) { 499 | lua_pushcfunction(L, Lformat); 500 | return 1; 501 | } 502 | 503 | /* cc: flags+='-O3 --coverage -pedantic' 504 | * unixcc: flags+='-shared -fPIC' output='fmt.so' 505 | * maccc: flags+='-undefined dynamic_lookup' 506 | * win32cc: flags+='-s -mdll -DLUA_BUILD_AS_DLL ' 507 | * win32cc: libs+='-llua54' output='fmt.dll' */ 508 | 509 | -------------------------------------------------------------------------------- /luaunit.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | luaunit.lua 3 | 4 | Description: A unit testing framework 5 | Homepage: https://github.com/bluebird75/luaunit 6 | Development by Philippe Fremy 7 | Based on initial work of Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit) 8 | License: BSD License, see LICENSE.txt 9 | ]]-- 10 | 11 | require("math") 12 | local M={} 13 | 14 | -- private exported functions (for testing) 15 | M.private = {} 16 | 17 | M.VERSION='3.4-dev' 18 | M._VERSION=M.VERSION -- For LuaUnit v2 compatibility 19 | 20 | -- a version which distinguish between regular Lua and LuaJit 21 | M._LUAVERSION = (jit and jit.version) or _VERSION 22 | 23 | --[[ Some people like assertEquals( actual, expected ) and some people prefer 24 | assertEquals( expected, actual ). 25 | ]]-- 26 | M.ORDER_ACTUAL_EXPECTED = true 27 | M.PRINT_TABLE_REF_IN_ERROR_MSG = false 28 | M.LINE_LENGTH = 80 29 | M.TABLE_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items 30 | M.LIST_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items 31 | 32 | --[[ EPS is meant to help with Lua's floating point math in simple corner 33 | cases like almostEquals(1.1-0.1, 1), which may not work as-is (e.g. on numbers 34 | with rational binary representation) if the user doesn't provide some explicit 35 | error margin. 36 | 37 | The default margin used by almostEquals() in such cases is EPS; and since 38 | Lua may be compiled with different numeric precisions (single vs. double), we 39 | try to select a useful default for it dynamically. Note: If the initial value 40 | is not acceptable, it can be changed by the user to better suit specific needs. 41 | 42 | See also: https://en.wikipedia.org/wiki/Machine_epsilon 43 | ]] 44 | M.EPS = 2^-52 -- = machine epsilon for "double", ~2.22E-16 45 | if math.abs(1.1 - 1 - 0.1) > M.EPS then 46 | -- rounding error is above EPS, assume single precision 47 | M.EPS = 2^-23 -- = machine epsilon for "float", ~1.19E-07 48 | end 49 | 50 | -- set this to false to debug luaunit 51 | local STRIP_LUAUNIT_FROM_STACKTRACE = true 52 | 53 | M.VERBOSITY_DEFAULT = 10 54 | M.VERBOSITY_LOW = 1 55 | M.VERBOSITY_QUIET = 0 56 | M.VERBOSITY_VERBOSE = 20 57 | M.DEFAULT_DEEP_ANALYSIS = nil 58 | M.FORCE_DEEP_ANALYSIS = true 59 | M.DISABLE_DEEP_ANALYSIS = false 60 | 61 | -- set EXPORT_ASSERT_TO_GLOBALS to have all asserts visible as global values 62 | -- EXPORT_ASSERT_TO_GLOBALS = true 63 | 64 | -- we need to keep a copy of the script args before it is overriden 65 | local cmdline_argv = rawget(_G, "arg") 66 | 67 | M.FAILURE_PREFIX = 'LuaUnit test FAILURE: ' -- prefix string for failed tests 68 | M.SUCCESS_PREFIX = 'LuaUnit test SUCCESS: ' -- prefix string for successful tests finished early 69 | M.SKIP_PREFIX = 'LuaUnit test SKIP: ' -- prefix string for skipped tests 70 | 71 | 72 | 73 | M.USAGE=[[Usage: lua [options] [testname1 [testname2] ... ] 74 | Options: 75 | -h, --help: Print this help 76 | --version: Print version information 77 | -v, --verbose: Increase verbosity 78 | -q, --quiet: Set verbosity to minimum 79 | -e, --error: Stop on first error 80 | -f, --failure: Stop on first failure or error 81 | -s, --shuffle: Shuffle tests before running them 82 | -o, --output OUTPUT: Set output type to OUTPUT 83 | Possible values: text, tap, junit, nil 84 | -n, --name NAME: For junit only, mandatory name of xml file 85 | -r, --repeat NUM: Execute all tests NUM times, e.g. to trig the JIT 86 | -p, --pattern PATTERN: Execute all test names matching the Lua PATTERN 87 | May be repeated to include several patterns 88 | Make sure you escape magic chars like +? with % 89 | -x, --exclude PATTERN: Exclude all test names matching the Lua PATTERN 90 | May be repeated to exclude several patterns 91 | Make sure you escape magic chars like +? with % 92 | testname1, testname2, ... : tests to run in the form of testFunction, 93 | TestClass or TestClass.testMethod 94 | ]] 95 | 96 | local is_equal -- defined here to allow calling from mismatchFormattingPureList 97 | 98 | ---------------------------------------------------------------- 99 | -- 100 | -- general utility functions 101 | -- 102 | ---------------------------------------------------------------- 103 | 104 | local function pcall_or_abort(func, ...) 105 | -- unpack is a global function for Lua 5.1, otherwise use table.unpack 106 | local unpack = rawget(_G, "unpack") or table.unpack 107 | local result = {pcall(func, ...)} 108 | if not result[1] then 109 | -- an error occurred 110 | print(result[2]) -- error message 111 | print() 112 | print(M.USAGE) 113 | os.exit(-1) 114 | end 115 | return unpack(result, 2) 116 | end 117 | 118 | local crossTypeOrdering = { 119 | number = 1, boolean = 2, string = 3, table = 4, other = 5 120 | } 121 | local crossTypeComparison = { 122 | number = function(a, b) return a < b end, 123 | string = function(a, b) return a < b end, 124 | other = function(a, b) return tostring(a) < tostring(b) end, 125 | } 126 | 127 | local function crossTypeSort(a, b) 128 | local type_a, type_b = type(a), type(b) 129 | if type_a == type_b then 130 | local func = crossTypeComparison[type_a] or crossTypeComparison.other 131 | return func(a, b) 132 | end 133 | type_a = crossTypeOrdering[type_a] or crossTypeOrdering.other 134 | type_b = crossTypeOrdering[type_b] or crossTypeOrdering.other 135 | return type_a < type_b 136 | end 137 | 138 | local function __genSortedIndex( t ) 139 | -- Returns a sequence consisting of t's keys, sorted. 140 | local sortedIndex = {} 141 | 142 | for key,_ in pairs(t) do 143 | table.insert(sortedIndex, key) 144 | end 145 | 146 | table.sort(sortedIndex, crossTypeSort) 147 | return sortedIndex 148 | end 149 | M.private.__genSortedIndex = __genSortedIndex 150 | 151 | local function sortedNext(state, control) 152 | -- Equivalent of the next() function of table iteration, but returns the 153 | -- keys in sorted order (see __genSortedIndex and crossTypeSort). 154 | -- The state is a temporary variable during iteration and contains the 155 | -- sorted key table (state.sortedIdx). It also stores the last index (into 156 | -- the keys) used by the iteration, to find the next one quickly. 157 | local key 158 | 159 | --print("sortedNext: control = "..tostring(control) ) 160 | if control == nil then 161 | -- start of iteration 162 | state.count = #state.sortedIdx 163 | state.lastIdx = 1 164 | key = state.sortedIdx[1] 165 | return key, state.t[key] 166 | end 167 | 168 | -- normally, we expect the control variable to match the last key used 169 | if control ~= state.sortedIdx[state.lastIdx] then 170 | -- strange, we have to find the next value by ourselves 171 | -- the key table is sorted in crossTypeSort() order! -> use bisection 172 | local lower, upper = 1, state.count 173 | repeat 174 | state.lastIdx = math.modf((lower + upper) / 2) 175 | key = state.sortedIdx[state.lastIdx] 176 | if key == control then 177 | break -- key found (and thus prev index) 178 | end 179 | if crossTypeSort(key, control) then 180 | -- key < control, continue search "right" (towards upper bound) 181 | lower = state.lastIdx + 1 182 | else 183 | -- key > control, continue search "left" (towards lower bound) 184 | upper = state.lastIdx - 1 185 | end 186 | until lower > upper 187 | if lower > upper then -- only true if the key wasn't found, ... 188 | state.lastIdx = state.count -- ... so ensure no match in code below 189 | end 190 | end 191 | 192 | -- proceed by retrieving the next value (or nil) from the sorted keys 193 | state.lastIdx = state.lastIdx + 1 194 | key = state.sortedIdx[state.lastIdx] 195 | if key then 196 | return key, state.t[key] 197 | end 198 | 199 | -- getting here means returning `nil`, which will end the iteration 200 | end 201 | 202 | local function sortedPairs(tbl) 203 | -- Equivalent of the pairs() function on tables. Allows to iterate in 204 | -- sorted order. As required by "generic for" loops, this will return the 205 | -- iterator (function), an "invariant state", and the initial control value. 206 | -- (see http://www.lua.org/pil/7.2.html) 207 | return sortedNext, {t = tbl, sortedIdx = __genSortedIndex(tbl)}, nil 208 | end 209 | M.private.sortedPairs = sortedPairs 210 | 211 | -- seed the random with a strongly varying seed 212 | math.randomseed(os.clock()*1E11) 213 | 214 | local function randomizeTable( t ) 215 | -- randomize the item orders of the table t 216 | for i = #t, 2, -1 do 217 | local j = math.random(i) 218 | if i ~= j then 219 | t[i], t[j] = t[j], t[i] 220 | end 221 | end 222 | end 223 | M.private.randomizeTable = randomizeTable 224 | 225 | local function strsplit(delimiter, text) 226 | -- Split text into a list consisting of the strings in text, separated 227 | -- by strings matching delimiter (which may _NOT_ be a pattern). 228 | -- Example: strsplit(", ", "Anna, Bob, Charlie, Dolores") 229 | if delimiter == "" or delimiter == nil then -- this would result in endless loops 230 | error("delimiter is nil or empty string!") 231 | end 232 | if text == nil then 233 | return nil 234 | end 235 | 236 | local list, pos, first, last = {}, 1 237 | while true do 238 | first, last = text:find(delimiter, pos, true) 239 | if first then -- found? 240 | table.insert(list, text:sub(pos, first - 1)) 241 | pos = last + 1 242 | else 243 | table.insert(list, text:sub(pos)) 244 | break 245 | end 246 | end 247 | return list 248 | end 249 | M.private.strsplit = strsplit 250 | 251 | local function hasNewLine( s ) 252 | -- return true if s has a newline 253 | return (string.find(s, '\n', 1, true) ~= nil) 254 | end 255 | M.private.hasNewLine = hasNewLine 256 | 257 | local function prefixString( prefix, s ) 258 | -- Prefix all the lines of s with prefix 259 | return prefix .. string.gsub(s, '\n', '\n' .. prefix) 260 | end 261 | M.private.prefixString = prefixString 262 | 263 | local function strMatch(s, pattern, start, final ) 264 | -- return true if s matches completely the pattern from index start to index end 265 | -- return false in every other cases 266 | -- if start is nil, matches from the beginning of the string 267 | -- if final is nil, matches to the end of the string 268 | start = start or 1 269 | final = final or string.len(s) 270 | 271 | local foundStart, foundEnd = string.find(s, pattern, start, false) 272 | return foundStart == start and foundEnd == final 273 | end 274 | M.private.strMatch = strMatch 275 | 276 | local function patternFilter(patterns, expr) 277 | -- Run `expr` through the inclusion and exclusion rules defined in patterns 278 | -- and return true if expr shall be included, false for excluded. 279 | -- Inclusion pattern are defined as normal patterns, exclusions 280 | -- patterns start with `!` and are followed by a normal pattern 281 | 282 | -- result: nil = UNKNOWN (not matched yet), true = ACCEPT, false = REJECT 283 | -- default: true if no explicit "include" is found, set to false otherwise 284 | local default, result = true, nil 285 | 286 | if patterns ~= nil then 287 | for _, pattern in ipairs(patterns) do 288 | local exclude = pattern:sub(1,1) == '!' 289 | if exclude then 290 | pattern = pattern:sub(2) 291 | else 292 | -- at least one include pattern specified, a match is required 293 | default = false 294 | end 295 | -- print('pattern: ',pattern) 296 | -- print('exclude: ',exclude) 297 | -- print('default: ',default) 298 | 299 | if string.find(expr, pattern) then 300 | -- set result to false when excluding, true otherwise 301 | result = not exclude 302 | end 303 | end 304 | end 305 | 306 | if result ~= nil then 307 | return result 308 | end 309 | return default 310 | end 311 | M.private.patternFilter = patternFilter 312 | 313 | local function xmlEscape( s ) 314 | -- Return s escaped for XML attributes 315 | -- escapes table: 316 | -- " " 317 | -- ' ' 318 | -- < < 319 | -- > > 320 | -- & & 321 | 322 | return string.gsub( s, '.', { 323 | ['&'] = "&", 324 | ['"'] = """, 325 | ["'"] = "'", 326 | ['<'] = "<", 327 | ['>'] = ">", 328 | } ) 329 | end 330 | M.private.xmlEscape = xmlEscape 331 | 332 | local function xmlCDataEscape( s ) 333 | -- Return s escaped for CData section, escapes: "]]>" 334 | return string.gsub( s, ']]>', ']]>' ) 335 | end 336 | M.private.xmlCDataEscape = xmlCDataEscape 337 | 338 | local function stripLuaunitTrace( stackTrace ) 339 | --[[ 340 | -- Example of a traceback: 341 | < 344 | [C]: in function 'xpcall' 345 | ./luaunit.lua:1449: in function 'protectedCall' 346 | ./luaunit.lua:1508: in function 'execOneFunction' 347 | ./luaunit.lua:1596: in function 'runSuiteByInstances' 348 | ./luaunit.lua:1660: in function 'runSuiteByNames' 349 | ./luaunit.lua:1736: in function 'runSuite' 350 | example_with_luaunit.lua:140: in main chunk 351 | [C]: in ?>> 352 | 353 | Other example: 354 | < 358 | [C]: in function 'xpcall' 359 | ./luaunit.lua:1517: in function 'protectedCall' 360 | ./luaunit.lua:1578: in function 'execOneFunction' 361 | ./luaunit.lua:1677: in function 'runSuiteByInstances' 362 | ./luaunit.lua:1730: in function 'runSuiteByNames' 363 | ./luaunit.lua:1806: in function 'runSuite' 364 | example_with_luaunit.lua:140: in main chunk 365 | [C]: in ?>> 366 | 367 | < 370 | [C]: in function 'xpcall' 371 | luaunit2/luaunit.lua:1532: in function 'protectedCall' 372 | luaunit2/luaunit.lua:1591: in function 'execOneFunction' 373 | luaunit2/luaunit.lua:1679: in function 'runSuiteByInstances' 374 | luaunit2/luaunit.lua:1743: in function 'runSuiteByNames' 375 | luaunit2/luaunit.lua:1819: in function 'runSuite' 376 | luaunit2/example_with_luaunit.lua:140: in main chunk 377 | [C]: in ?>> 378 | 379 | 380 | -- first line is "stack traceback": KEEP 381 | -- next line may be luaunit line: REMOVE 382 | -- next lines are call in the program under testOk: REMOVE 383 | -- next lines are calls from luaunit to call the program under test: KEEP 384 | 385 | -- Strategy: 386 | -- keep first line 387 | -- remove lines that are part of luaunit 388 | -- kepp lines until we hit a luaunit line 389 | ]] 390 | 391 | local function isLuaunitInternalLine( s ) 392 | -- return true if line of stack trace comes from inside luaunit 393 | return s:find('[/\\]luaunit%.lua:%d+: ') ~= nil 394 | end 395 | 396 | -- print( '<<'..stackTrace..'>>' ) 397 | 398 | local t = strsplit( '\n', stackTrace ) 399 | -- print( prettystr(t) ) 400 | 401 | local idx = 2 402 | 403 | -- remove lines that are still part of luaunit 404 | while t[idx] and isLuaunitInternalLine( t[idx] ) do 405 | -- print('Removing : '..t[idx] ) 406 | table.remove(t, idx) 407 | end 408 | 409 | -- keep lines until we hit luaunit again 410 | while t[idx] and (not isLuaunitInternalLine(t[idx])) do 411 | -- print('Keeping : '..t[idx] ) 412 | idx = idx + 1 413 | end 414 | 415 | -- remove remaining luaunit lines 416 | while t[idx] do 417 | -- print('Removing : '..t[idx] ) 418 | table.remove(t, idx) 419 | end 420 | 421 | -- print( prettystr(t) ) 422 | return table.concat( t, '\n') 423 | 424 | end 425 | M.private.stripLuaunitTrace = stripLuaunitTrace 426 | 427 | 428 | local function prettystr_sub(v, indentLevel, printTableRefs, cycleDetectTable ) 429 | local type_v = type(v) 430 | if "string" == type_v then 431 | -- use clever delimiters according to content: 432 | -- enclose with single quotes if string contains ", but no ' 433 | if v:find('"', 1, true) and not v:find("'", 1, true) then 434 | return "'" .. v .. "'" 435 | end 436 | -- use double quotes otherwise, escape embedded " 437 | return '"' .. v:gsub('"', '\\"') .. '"' 438 | 439 | elseif "table" == type_v then 440 | --if v.__class__ then 441 | -- return string.gsub( tostring(v), 'table', v.__class__ ) 442 | --end 443 | return M.private._table_tostring(v, indentLevel, printTableRefs, cycleDetectTable) 444 | 445 | elseif "number" == type_v then 446 | -- eliminate differences in formatting between various Lua versions 447 | if v ~= v then 448 | return "#NaN" -- "not a number" 449 | end 450 | if v == math.huge then 451 | return "#Inf" -- "infinite" 452 | end 453 | if v == -math.huge then 454 | return "-#Inf" 455 | end 456 | if _VERSION == "Lua 5.3" then 457 | local i = math.tointeger(v) 458 | if i then 459 | return tostring(i) 460 | end 461 | end 462 | end 463 | 464 | return tostring(v) 465 | end 466 | 467 | local function prettystr( v ) 468 | --[[ Pretty string conversion, to display the full content of a variable of any type. 469 | 470 | * string are enclosed with " by default, or with ' if string contains a " 471 | * tables are expanded to show their full content, with indentation in case of nested tables 472 | ]]-- 473 | local cycleDetectTable = {} 474 | local s = prettystr_sub(v, 1, M.PRINT_TABLE_REF_IN_ERROR_MSG, cycleDetectTable) 475 | if cycleDetectTable.detected and not M.PRINT_TABLE_REF_IN_ERROR_MSG then 476 | -- some table contain recursive references, 477 | -- so we must recompute the value by including all table references 478 | -- else the result looks like crap 479 | cycleDetectTable = {} 480 | s = prettystr_sub(v, 1, true, cycleDetectTable) 481 | end 482 | return s 483 | end 484 | M.prettystr = prettystr 485 | 486 | function M.adjust_err_msg_with_iter( err_msg, iter_msg ) 487 | --[[ Adjust the error message err_msg: trim the FAILURE_PREFIX or SUCCESS_PREFIX information if needed, 488 | add the iteration message if any and return the result. 489 | 490 | err_msg: string, error message captured with pcall 491 | iter_msg: a string describing the current iteration ("iteration N") or nil 492 | if there is no iteration in this test. 493 | 494 | Returns: (new_err_msg, test_status) 495 | new_err_msg: string, adjusted error message, or nil in case of success 496 | test_status: M.NodeStatus.FAIL, SUCCESS or ERROR according to the information 497 | contained in the error message. 498 | ]] 499 | if iter_msg then 500 | iter_msg = iter_msg..', ' 501 | else 502 | iter_msg = '' 503 | end 504 | 505 | local RE_FILE_LINE = '.*:%d+: ' 506 | 507 | -- error message is not necessarily a string, 508 | -- so convert the value to string with prettystr() 509 | if type( err_msg ) ~= 'string' then 510 | err_msg = prettystr( err_msg ) 511 | end 512 | 513 | if (err_msg:find( M.SUCCESS_PREFIX ) == 1) or err_msg:match( '('..RE_FILE_LINE..')' .. M.SUCCESS_PREFIX .. ".*" ) then 514 | -- test finished early with success() 515 | return nil, M.NodeStatus.SUCCESS 516 | end 517 | 518 | if (err_msg:find( M.SKIP_PREFIX ) == 1) or (err_msg:match( '('..RE_FILE_LINE..')' .. M.SKIP_PREFIX .. ".*" ) ~= nil) then 519 | -- substitute prefix by iteration message 520 | err_msg = err_msg:gsub('.*'..M.SKIP_PREFIX, iter_msg, 1) 521 | -- print("failure detected") 522 | return err_msg, M.NodeStatus.SKIP 523 | end 524 | 525 | if (err_msg:find( M.FAILURE_PREFIX ) == 1) or (err_msg:match( '('..RE_FILE_LINE..')' .. M.FAILURE_PREFIX .. ".*" ) ~= nil) then 526 | -- substitute prefix by iteration message 527 | err_msg = err_msg:gsub(M.FAILURE_PREFIX, iter_msg, 1) 528 | -- print("failure detected") 529 | return err_msg, M.NodeStatus.FAIL 530 | end 531 | 532 | 533 | 534 | -- print("error detected") 535 | -- regular error, not a failure 536 | if iter_msg then 537 | local match 538 | -- "./test\\test_luaunit.lua:2241: some error msg 539 | match = err_msg:match( '(.*:%d+: ).*' ) 540 | if match then 541 | err_msg = err_msg:gsub( match, match .. iter_msg ) 542 | else 543 | -- no file:line: infromation, just add the iteration info at the beginning of the line 544 | err_msg = iter_msg .. err_msg 545 | end 546 | end 547 | return err_msg, M.NodeStatus.ERROR 548 | end 549 | 550 | local function tryMismatchFormatting( table_a, table_b, doDeepAnalysis ) 551 | --[[ 552 | Prepares a nice error message when comparing tables, performing a deeper 553 | analysis. 554 | 555 | Arguments: 556 | * table_a, table_b: tables to be compared 557 | * doDeepAnalysis: 558 | M.DEFAULT_DEEP_ANALYSIS: (the default if not specified) perform deep analysis only for big lists and big dictionnaries 559 | M.FORCE_DEEP_ANALYSIS : always perform deep analysis 560 | M.DISABLE_DEEP_ANALYSIS: never perform deep analysis 561 | 562 | Returns: {success, result} 563 | * success: false if deep analysis could not be performed 564 | in this case, just use standard assertion message 565 | * result: if success is true, a multi-line string with deep analysis of the two lists 566 | ]] 567 | 568 | -- check if table_a & table_b are suitable for deep analysis 569 | if type(table_a) ~= 'table' or type(table_b) ~= 'table' then 570 | return false 571 | end 572 | 573 | if doDeepAnalysis == M.DISABLE_DEEP_ANALYSIS then 574 | return false 575 | end 576 | 577 | local len_a, len_b, isPureList = #table_a, #table_b, true 578 | 579 | for k1, v1 in pairs(table_a) do 580 | if type(k1) ~= 'number' or k1 > len_a then 581 | -- this table a mapping 582 | isPureList = false 583 | break 584 | end 585 | end 586 | 587 | if isPureList then 588 | for k2, v2 in pairs(table_b) do 589 | if type(k2) ~= 'number' or k2 > len_b then 590 | -- this table a mapping 591 | isPureList = false 592 | break 593 | end 594 | end 595 | end 596 | 597 | if isPureList and math.min(len_a, len_b) < M.LIST_DIFF_ANALYSIS_THRESHOLD then 598 | if not (doDeepAnalysis == M.FORCE_DEEP_ANALYSIS) then 599 | return false 600 | end 601 | end 602 | 603 | if isPureList then 604 | return M.private.mismatchFormattingPureList( table_a, table_b ) 605 | else 606 | -- only work on mapping for the moment 607 | -- return M.private.mismatchFormattingMapping( table_a, table_b, doDeepAnalysis ) 608 | return false 609 | end 610 | end 611 | M.private.tryMismatchFormatting = tryMismatchFormatting 612 | 613 | local function getTaTbDescr() 614 | if not M.ORDER_ACTUAL_EXPECTED then 615 | return 'expected', 'actual' 616 | end 617 | return 'actual', 'expected' 618 | end 619 | 620 | local function extendWithStrFmt( res, ... ) 621 | table.insert( res, string.format( ... ) ) 622 | end 623 | 624 | local function mismatchFormattingMapping( table_a, table_b, doDeepAnalysis ) 625 | --[[ 626 | Prepares a nice error message when comparing tables which are not pure lists, performing a deeper 627 | analysis. 628 | 629 | Returns: {success, result} 630 | * success: false if deep analysis could not be performed 631 | in this case, just use standard assertion message 632 | * result: if success is true, a multi-line string with deep analysis of the two lists 633 | ]] 634 | 635 | -- disable for the moment 636 | --[[ 637 | local result = {} 638 | local descrTa, descrTb = getTaTbDescr() 639 | 640 | local keysCommon = {} 641 | local keysOnlyTa = {} 642 | local keysOnlyTb = {} 643 | local keysDiffTaTb = {} 644 | 645 | local k, v 646 | 647 | for k,v in pairs( table_a ) do 648 | if is_equal( v, table_b[k] ) then 649 | table.insert( keysCommon, k ) 650 | else 651 | if table_b[k] == nil then 652 | table.insert( keysOnlyTa, k ) 653 | else 654 | table.insert( keysDiffTaTb, k ) 655 | end 656 | end 657 | end 658 | 659 | for k,v in pairs( table_b ) do 660 | if not is_equal( v, table_a[k] ) and table_a[k] == nil then 661 | table.insert( keysOnlyTb, k ) 662 | end 663 | end 664 | 665 | local len_a = #keysCommon + #keysDiffTaTb + #keysOnlyTa 666 | local len_b = #keysCommon + #keysDiffTaTb + #keysOnlyTb 667 | local limited_display = (len_a < 5 or len_b < 5) 668 | 669 | if math.min(len_a, len_b) < M.TABLE_DIFF_ANALYSIS_THRESHOLD then 670 | return false 671 | end 672 | 673 | if not limited_display then 674 | if len_a == len_b then 675 | extendWithStrFmt( result, 'Table A (%s) and B (%s) both have %d items', descrTa, descrTb, len_a ) 676 | else 677 | extendWithStrFmt( result, 'Table A (%s) has %d items and table B (%s) has %d items', descrTa, len_a, descrTb, len_b ) 678 | end 679 | 680 | if #keysCommon == 0 and #keysDiffTaTb == 0 then 681 | table.insert( result, 'Table A and B have no keys in common, they are totally different') 682 | else 683 | local s_other = 'other ' 684 | if #keysCommon then 685 | extendWithStrFmt( result, 'Table A and B have %d identical items', #keysCommon ) 686 | else 687 | table.insert( result, 'Table A and B have no identical items' ) 688 | s_other = '' 689 | end 690 | 691 | if #keysDiffTaTb ~= 0 then 692 | result[#result] = string.format( '%s and %d items differing present in both tables', result[#result], #keysDiffTaTb) 693 | else 694 | result[#result] = string.format( '%s and no %sitems differing present in both tables', result[#result], s_other, #keysDiffTaTb) 695 | end 696 | end 697 | 698 | extendWithStrFmt( result, 'Table A has %d keys not present in table B and table B has %d keys not present in table A', #keysOnlyTa, #keysOnlyTb ) 699 | end 700 | 701 | local function keytostring(k) 702 | if "string" == type(k) and k:match("^[_%a][_%w]*$") then 703 | return k 704 | end 705 | return prettystr(k) 706 | end 707 | 708 | if #keysDiffTaTb ~= 0 then 709 | table.insert( result, 'Items differing in A and B:') 710 | for k,v in sortedPairs( keysDiffTaTb ) do 711 | extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) ) 712 | extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) ) 713 | end 714 | end 715 | 716 | if #keysOnlyTa ~= 0 then 717 | table.insert( result, 'Items only in table A:' ) 718 | for k,v in sortedPairs( keysOnlyTa ) do 719 | extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) ) 720 | end 721 | end 722 | 723 | if #keysOnlyTb ~= 0 then 724 | table.insert( result, 'Items only in table B:' ) 725 | for k,v in sortedPairs( keysOnlyTb ) do 726 | extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) ) 727 | end 728 | end 729 | 730 | if #keysCommon ~= 0 then 731 | table.insert( result, 'Items common to A and B:') 732 | for k,v in sortedPairs( keysCommon ) do 733 | extendWithStrFmt( result, ' = A and B [%s]: %s', keytostring(v), prettystr(table_a[v]) ) 734 | end 735 | end 736 | 737 | return true, table.concat( result, '\n') 738 | ]] 739 | end 740 | M.private.mismatchFormattingMapping = mismatchFormattingMapping 741 | 742 | local function mismatchFormattingPureList( table_a, table_b ) 743 | --[[ 744 | Prepares a nice error message when comparing tables which are lists, performing a deeper 745 | analysis. 746 | 747 | Returns: {success, result} 748 | * success: false if deep analysis could not be performed 749 | in this case, just use standard assertion message 750 | * result: if success is true, a multi-line string with deep analysis of the two lists 751 | ]] 752 | local result, descrTa, descrTb = {}, getTaTbDescr() 753 | 754 | local len_a, len_b, refa, refb = #table_a, #table_b, '', '' 755 | if M.PRINT_TABLE_REF_IN_ERROR_MSG then 756 | refa, refb = string.format( '<%s> ', M.private.table_ref(table_a)), string.format('<%s> ', M.private.table_ref(table_b) ) 757 | end 758 | local longest, shortest = math.max(len_a, len_b), math.min(len_a, len_b) 759 | local deltalv = longest - shortest 760 | 761 | local commonUntil = shortest 762 | for i = 1, shortest do 763 | if not is_equal(table_a[i], table_b[i]) then 764 | commonUntil = i - 1 765 | break 766 | end 767 | end 768 | 769 | local commonBackTo = shortest - 1 770 | for i = 0, shortest - 1 do 771 | if not is_equal(table_a[len_a-i], table_b[len_b-i]) then 772 | commonBackTo = i - 1 773 | break 774 | end 775 | end 776 | 777 | 778 | table.insert( result, 'List difference analysis:' ) 779 | if len_a == len_b then 780 | -- TODO: handle expected/actual naming 781 | extendWithStrFmt( result, '* lists %sA (%s) and %sB (%s) have the same size', refa, descrTa, refb, descrTb ) 782 | else 783 | extendWithStrFmt( result, '* list sizes differ: list %sA (%s) has %d items, list %sB (%s) has %d items', refa, descrTa, len_a, refb, descrTb, len_b ) 784 | end 785 | 786 | extendWithStrFmt( result, '* lists A and B start differing at index %d', commonUntil+1 ) 787 | if commonBackTo >= 0 then 788 | if deltalv > 0 then 789 | extendWithStrFmt( result, '* lists A and B are equal again from index %d for A, %d for B', len_a-commonBackTo, len_b-commonBackTo ) 790 | else 791 | extendWithStrFmt( result, '* lists A and B are equal again from index %d', len_a-commonBackTo ) 792 | end 793 | end 794 | 795 | local function insertABValue(ai, bi) 796 | bi = bi or ai 797 | if is_equal( table_a[ai], table_b[bi]) then 798 | return extendWithStrFmt( result, ' = A[%d], B[%d]: %s', ai, bi, prettystr(table_a[ai]) ) 799 | else 800 | extendWithStrFmt( result, ' - A[%d]: %s', ai, prettystr(table_a[ai])) 801 | extendWithStrFmt( result, ' + B[%d]: %s', bi, prettystr(table_b[bi])) 802 | end 803 | end 804 | 805 | -- common parts to list A & B, at the beginning 806 | if commonUntil > 0 then 807 | table.insert( result, '* Common parts:' ) 808 | for i = 1, commonUntil do 809 | insertABValue( i ) 810 | end 811 | end 812 | 813 | -- diffing parts to list A & B 814 | if commonUntil < shortest - commonBackTo - 1 then 815 | table.insert( result, '* Differing parts:' ) 816 | for i = commonUntil + 1, shortest - commonBackTo - 1 do 817 | insertABValue( i ) 818 | end 819 | end 820 | 821 | -- display indexes of one list, with no match on other list 822 | if shortest - commonBackTo <= longest - commonBackTo - 1 then 823 | table.insert( result, '* Present only in one list:' ) 824 | for i = shortest - commonBackTo, longest - commonBackTo - 1 do 825 | if len_a > len_b then 826 | extendWithStrFmt( result, ' - A[%d]: %s', i, prettystr(table_a[i]) ) 827 | -- table.insert( result, '+ (no matching B index)') 828 | else 829 | -- table.insert( result, '- no matching A index') 830 | extendWithStrFmt( result, ' + B[%d]: %s', i, prettystr(table_b[i]) ) 831 | end 832 | end 833 | end 834 | 835 | -- common parts to list A & B, at the end 836 | if commonBackTo >= 0 then 837 | table.insert( result, '* Common parts at the end of the lists' ) 838 | for i = longest - commonBackTo, longest do 839 | if len_a > len_b then 840 | insertABValue( i, i-deltalv ) 841 | else 842 | insertABValue( i-deltalv, i ) 843 | end 844 | end 845 | end 846 | 847 | return true, table.concat( result, '\n') 848 | end 849 | M.private.mismatchFormattingPureList = mismatchFormattingPureList 850 | 851 | local function prettystrPairs(value1, value2, suffix_a, suffix_b) 852 | --[[ 853 | This function helps with the recurring task of constructing the "expected 854 | vs. actual" error messages. It takes two arbitrary values and formats 855 | corresponding strings with prettystr(). 856 | 857 | To keep the (possibly complex) output more readable in case the resulting 858 | strings contain line breaks, they get automatically prefixed with additional 859 | newlines. Both suffixes are optional (default to empty strings), and get 860 | appended to the "value1" string. "suffix_a" is used if line breaks were 861 | encountered, "suffix_b" otherwise. 862 | 863 | Returns the two formatted strings (including padding/newlines). 864 | ]] 865 | local str1, str2 = prettystr(value1), prettystr(value2) 866 | if hasNewLine(str1) or hasNewLine(str2) then 867 | -- line break(s) detected, add padding 868 | return "\n" .. str1 .. (suffix_a or ""), "\n" .. str2 869 | end 870 | return str1 .. (suffix_b or ""), str2 871 | end 872 | M.private.prettystrPairs = prettystrPairs 873 | 874 | local UNKNOWN_REF = 'table 00-unknown ref' 875 | local ref_generator = { value=1, [UNKNOWN_REF]=0 } 876 | 877 | local function table_ref( t ) 878 | -- return the default tostring() for tables, with the table ID, even if the table has a metatable 879 | -- with the __tostring converter 880 | local ref = '' 881 | local mt = getmetatable( t ) 882 | if mt == nil then 883 | ref = tostring(t) 884 | else 885 | local success, result 886 | success, result = pcall(setmetatable, t, nil) 887 | if not success then 888 | -- protected table, if __tostring is defined, we can 889 | -- not get the reference. And we can not know in advance. 890 | ref = tostring(t) 891 | if not ref:match( 'table: 0?x?[%x]+' ) then 892 | return UNKNOWN_REF 893 | end 894 | else 895 | ref = tostring(t) 896 | setmetatable( t, mt ) 897 | end 898 | end 899 | -- strip the "table: " part 900 | ref = ref:sub(8) 901 | if ref ~= UNKNOWN_REF and ref_generator[ref] == nil then 902 | -- Create a new reference number 903 | ref_generator[ref] = ref_generator.value 904 | ref_generator.value = ref_generator.value+1 905 | end 906 | if M.PRINT_TABLE_REF_IN_ERROR_MSG then 907 | return string.format('table %02d-%s', ref_generator[ref], ref) 908 | else 909 | return string.format('table %02d', ref_generator[ref]) 910 | end 911 | end 912 | M.private.table_ref = table_ref 913 | 914 | local TABLE_TOSTRING_SEP = ", " 915 | local TABLE_TOSTRING_SEP_LEN = string.len(TABLE_TOSTRING_SEP) 916 | 917 | local function _table_tostring( tbl, indentLevel, printTableRefs, cycleDetectTable ) 918 | printTableRefs = printTableRefs or M.PRINT_TABLE_REF_IN_ERROR_MSG 919 | cycleDetectTable = cycleDetectTable or {} 920 | cycleDetectTable[tbl] = true 921 | 922 | local result, dispOnMultLines = {}, false 923 | 924 | -- like prettystr but do not enclose with "" if the string is just alphanumerical 925 | -- this is better for displaying table keys who are often simple strings 926 | local function keytostring(k) 927 | if "string" == type(k) and k:match("^[_%a][_%w]*$") then 928 | return k 929 | end 930 | return prettystr_sub(k, indentLevel+1, printTableRefs, cycleDetectTable) 931 | end 932 | 933 | local mt = getmetatable( tbl ) 934 | 935 | if mt and mt.__tostring then 936 | -- if table has a __tostring() function in its metatable, use it to display the table 937 | -- else, compute a regular table 938 | result = tostring(tbl) 939 | if type(result) ~= 'string' then 940 | return string.format( '', prettystr(result) ) 941 | end 942 | result = strsplit( '\n', result ) 943 | return M.private._table_tostring_format_multiline_string( result, indentLevel ) 944 | 945 | else 946 | -- no metatable, compute the table representation 947 | 948 | local entry, count, seq_index = nil, 0, 1 949 | for k, v in sortedPairs( tbl ) do 950 | 951 | -- key part 952 | if k == seq_index then 953 | -- for the sequential part of tables, we'll skip the "=" output 954 | entry = '' 955 | seq_index = seq_index + 1 956 | elseif cycleDetectTable[k] then 957 | -- recursion in the key detected 958 | cycleDetectTable.detected = true 959 | entry = "<"..table_ref(k)..">=" 960 | else 961 | entry = keytostring(k) .. "=" 962 | end 963 | 964 | -- value part 965 | if cycleDetectTable[v] then 966 | -- recursion in the value detected! 967 | cycleDetectTable.detected = true 968 | entry = entry .. "<"..table_ref(v)..">" 969 | else 970 | entry = entry .. 971 | prettystr_sub( v, indentLevel+1, printTableRefs, cycleDetectTable ) 972 | end 973 | count = count + 1 974 | result[count] = entry 975 | end 976 | return M.private._table_tostring_format_result( tbl, result, indentLevel, printTableRefs ) 977 | end 978 | 979 | end 980 | M.private._table_tostring = _table_tostring -- prettystr_sub() needs it 981 | 982 | local function _table_tostring_format_multiline_string( tbl_str, indentLevel ) 983 | local indentString = '\n'..string.rep(" ", indentLevel - 1) 984 | return table.concat( tbl_str, indentString ) 985 | 986 | end 987 | M.private._table_tostring_format_multiline_string = _table_tostring_format_multiline_string 988 | 989 | 990 | local function _table_tostring_format_result( tbl, result, indentLevel, printTableRefs ) 991 | -- final function called in _table_to_string() to format the resulting list of 992 | -- string describing the table. 993 | 994 | local dispOnMultLines = false 995 | 996 | -- set dispOnMultLines to true if the maximum LINE_LENGTH would be exceeded with the values 997 | local totalLength = 0 998 | for k, v in ipairs( result ) do 999 | totalLength = totalLength + string.len( v ) 1000 | if totalLength >= M.LINE_LENGTH then 1001 | dispOnMultLines = true 1002 | break 1003 | end 1004 | end 1005 | 1006 | -- set dispOnMultLines to true if the max LINE_LENGTH would be exceeded 1007 | -- with the values and the separators. 1008 | if not dispOnMultLines then 1009 | -- adjust with length of separator(s): 1010 | -- two items need 1 sep, three items two seps, ... plus len of '{}' 1011 | if #result > 0 then 1012 | totalLength = totalLength + TABLE_TOSTRING_SEP_LEN * (#result - 1) 1013 | end 1014 | dispOnMultLines = (totalLength + 2 >= M.LINE_LENGTH) 1015 | end 1016 | 1017 | -- now reformat the result table (currently holding element strings) 1018 | if dispOnMultLines then 1019 | local indentString = string.rep(" ", indentLevel - 1) 1020 | result = { 1021 | "{\n ", 1022 | indentString, 1023 | table.concat(result, ",\n " .. indentString), 1024 | "\n", 1025 | indentString, 1026 | "}" 1027 | } 1028 | else 1029 | result = {"{", table.concat(result, TABLE_TOSTRING_SEP), "}"} 1030 | end 1031 | if printTableRefs then 1032 | table.insert(result, 1, "<"..table_ref(tbl).."> ") -- prepend table ref 1033 | end 1034 | return table.concat(result) 1035 | end 1036 | M.private._table_tostring_format_result = _table_tostring_format_result -- prettystr_sub() needs it 1037 | 1038 | local function table_findkeyof(t, element) 1039 | -- Return the key k of the given element in table t, so that t[k] == element 1040 | -- (or `nil` if element is not present within t). Note that we use our 1041 | -- 'general' is_equal comparison for matching, so this function should 1042 | -- handle table-type elements gracefully and consistently. 1043 | if type(t) == "table" then 1044 | for k, v in pairs(t) do 1045 | if is_equal(v, element) then 1046 | return k 1047 | end 1048 | end 1049 | end 1050 | return nil 1051 | end 1052 | 1053 | local function _is_table_items_equals(actual, expected ) 1054 | local type_a, type_e = type(actual), type(expected) 1055 | 1056 | if type_a ~= type_e then 1057 | return false 1058 | 1059 | elseif (type_a == 'table') --[[and (type_e == 'table')]] then 1060 | for k, v in pairs(actual) do 1061 | if table_findkeyof(expected, v) == nil then 1062 | return false -- v not contained in expected 1063 | end 1064 | end 1065 | for k, v in pairs(expected) do 1066 | if table_findkeyof(actual, v) == nil then 1067 | return false -- v not contained in actual 1068 | end 1069 | end 1070 | return true 1071 | 1072 | elseif actual ~= expected then 1073 | return false 1074 | end 1075 | 1076 | return true 1077 | end 1078 | 1079 | --[[ 1080 | This is a specialized metatable to help with the bookkeeping of recursions 1081 | in _is_table_equals(). It provides an __index table that implements utility 1082 | functions for easier management of the table. The "cached" method queries 1083 | the state of a specific (actual,expected) pair; and the "store" method sets 1084 | this state to the given value. The state of pairs not "seen" / visited is 1085 | assumed to be `nil`. 1086 | ]] 1087 | local _recursion_cache_MT = { 1088 | __index = { 1089 | -- Return the cached value for an (actual,expected) pair (or `nil`) 1090 | cached = function(t, actual, expected) 1091 | local subtable = t[actual] or {} 1092 | return subtable[expected] 1093 | end, 1094 | 1095 | -- Store cached value for a specific (actual,expected) pair. 1096 | -- Returns the value, so it's easy to use for a "tailcall" (return ...). 1097 | store = function(t, actual, expected, value, asymmetric) 1098 | local subtable = t[actual] 1099 | if not subtable then 1100 | subtable = {} 1101 | t[actual] = subtable 1102 | end 1103 | subtable[expected] = value 1104 | 1105 | -- Unless explicitly marked "asymmetric": Consider the recursion 1106 | -- on (expected,actual) to be equivalent to (actual,expected) by 1107 | -- default, and thus cache the value for both. 1108 | if not asymmetric then 1109 | t:store(expected, actual, value, true) 1110 | end 1111 | 1112 | return value 1113 | end 1114 | } 1115 | } 1116 | 1117 | local function _is_table_equals(actual, expected, cycleDetectTable) 1118 | local type_a, type_e = type(actual), type(expected) 1119 | 1120 | if type_a ~= type_e then 1121 | return false -- different types won't match 1122 | end 1123 | 1124 | if type_a ~= 'table' then 1125 | -- other typtes compare directly 1126 | return actual == expected 1127 | end 1128 | 1129 | -- print('_is_table_equals( \n '..prettystr(actual)..'\n , '..prettystr(expected)..'\n , '..prettystr(recursions)..' \n )') 1130 | 1131 | cycleDetectTable = cycleDetectTable or { actual={}, expected={} } 1132 | if cycleDetectTable.actual[ actual ] then 1133 | -- oh, we hit a cycle in actual 1134 | if cycleDetectTable.expected[ expected ] then 1135 | -- uh, we hit a cycle at the same time in expected 1136 | -- so the two tables have similar structure 1137 | return true 1138 | end 1139 | 1140 | -- cycle was hit only in actual, the structure differs from expected 1141 | return false 1142 | end 1143 | 1144 | if cycleDetectTable.expected[ expected ] then 1145 | -- no cycle in actual, but cycle in expected 1146 | -- the structure differ 1147 | return false 1148 | end 1149 | 1150 | -- at this point, no table cycle detected, we are 1151 | -- seeing this table for the first time 1152 | 1153 | -- mark the cycle detection 1154 | cycleDetectTable.actual[ actual ] = true 1155 | cycleDetectTable.expected[ expected ] = true 1156 | 1157 | 1158 | local actualKeysMatched = {} 1159 | for k, v in pairs(actual) do 1160 | actualKeysMatched[k] = true -- Keep track of matched keys 1161 | if not _is_table_equals(v, expected[k], cycleDetectTable) then 1162 | -- table differs on this key 1163 | -- clear the cycle detection before returning 1164 | cycleDetectTable.actual[ actual ] = nil 1165 | cycleDetectTable.expected[ expected ] = nil 1166 | return false 1167 | end 1168 | end 1169 | 1170 | for k, v in pairs(expected) do 1171 | if not actualKeysMatched[k] then 1172 | -- Found a key that we did not see in "actual" -> mismatch 1173 | -- clear the cycle detection before returning 1174 | cycleDetectTable.actual[ actual ] = nil 1175 | cycleDetectTable.expected[ expected ] = nil 1176 | return false 1177 | end 1178 | -- Otherwise actual[k] was already matched against v = expected[k]. 1179 | end 1180 | 1181 | -- all key match, we have a match ! 1182 | cycleDetectTable.actual[ actual ] = nil 1183 | cycleDetectTable.expected[ expected ] = nil 1184 | return true 1185 | end 1186 | M.private._is_table_equals = _is_table_equals 1187 | is_equal = _is_table_equals 1188 | 1189 | local function failure(main_msg, extra_msg_or_nil, level) 1190 | -- raise an error indicating a test failure 1191 | -- for error() compatibility we adjust "level" here (by +1), to report the 1192 | -- calling context 1193 | local msg 1194 | if type(extra_msg_or_nil) == 'string' and extra_msg_or_nil:len() > 0 then 1195 | msg = extra_msg_or_nil .. '\n' .. main_msg 1196 | else 1197 | msg = main_msg 1198 | end 1199 | error(M.FAILURE_PREFIX .. msg, (level or 1) + 1) 1200 | end 1201 | 1202 | local function fail_fmt(level, extra_msg_or_nil, ...) 1203 | -- failure with printf-style formatted message and given error level 1204 | failure(string.format(...), extra_msg_or_nil, (level or 1) + 1) 1205 | end 1206 | M.private.fail_fmt = fail_fmt 1207 | 1208 | local function error_fmt(level, ...) 1209 | -- printf-style error() 1210 | error(string.format(...), (level or 1) + 1) 1211 | end 1212 | 1213 | ---------------------------------------------------------------- 1214 | -- 1215 | -- assertions 1216 | -- 1217 | ---------------------------------------------------------------- 1218 | 1219 | local function errorMsgEquality(actual, expected, doDeepAnalysis) 1220 | 1221 | if not M.ORDER_ACTUAL_EXPECTED then 1222 | expected, actual = actual, expected 1223 | end 1224 | if type(expected) == 'string' or type(expected) == 'table' then 1225 | local strExpected, strActual = prettystrPairs(expected, actual) 1226 | local result = string.format("expected: %s\nactual: %s", strExpected, strActual) 1227 | 1228 | -- extend with mismatch analysis if possible: 1229 | local success, mismatchResult 1230 | success, mismatchResult = tryMismatchFormatting( actual, expected, doDeepAnalysis ) 1231 | if success then 1232 | result = table.concat( { result, mismatchResult }, '\n' ) 1233 | end 1234 | return result 1235 | end 1236 | return string.format("expected: %s, actual: %s", 1237 | prettystr(expected), prettystr(actual)) 1238 | end 1239 | 1240 | function M.assertError(f, ...) 1241 | -- assert that calling f with the arguments will raise an error 1242 | -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error 1243 | if pcall( f, ... ) then 1244 | failure( "Expected an error when calling function but no error generated", nil, 2 ) 1245 | end 1246 | end 1247 | 1248 | function M.fail( msg ) 1249 | -- stops a test due to a failure 1250 | failure( msg, nil, 2 ) 1251 | end 1252 | 1253 | function M.failIf( cond, msg ) 1254 | -- Fails a test with "msg" if condition is true 1255 | if cond then 1256 | failure( msg, nil, 2 ) 1257 | end 1258 | end 1259 | 1260 | function M.skip(msg) 1261 | -- skip a running test 1262 | error(M.SKIP_PREFIX .. msg, 2) 1263 | end 1264 | 1265 | function M.skipIf( cond, msg ) 1266 | -- skip a running test if condition is met 1267 | if cond then 1268 | error(M.SKIP_PREFIX .. msg, 2) 1269 | end 1270 | end 1271 | 1272 | function M.runOnlyIf( cond, msg ) 1273 | -- continue a running test if condition is met, else skip it 1274 | if not cond then 1275 | error(M.SKIP_PREFIX .. prettystr(msg), 2) 1276 | end 1277 | end 1278 | 1279 | function M.success() 1280 | -- stops a test with a success 1281 | error(M.SUCCESS_PREFIX, 2) 1282 | end 1283 | 1284 | function M.successIf( cond ) 1285 | -- stops a test with a success if condition is met 1286 | if cond then 1287 | error(M.SUCCESS_PREFIX, 2) 1288 | end 1289 | end 1290 | 1291 | 1292 | ------------------------------------------------------------------ 1293 | -- Equality assertions 1294 | ------------------------------------------------------------------ 1295 | 1296 | function M.assertEquals(actual, expected, extra_msg_or_nil, doDeepAnalysis) 1297 | if type(actual) == 'table' and type(expected) == 'table' then 1298 | if not _is_table_equals(actual, expected) then 1299 | failure( errorMsgEquality(actual, expected, doDeepAnalysis), extra_msg_or_nil, 2 ) 1300 | end 1301 | elseif type(actual) ~= type(expected) then 1302 | failure( errorMsgEquality(actual, expected), extra_msg_or_nil, 2 ) 1303 | elseif actual ~= expected then 1304 | failure( errorMsgEquality(actual, expected), extra_msg_or_nil, 2 ) 1305 | end 1306 | end 1307 | 1308 | function M.almostEquals( actual, expected, margin ) 1309 | if type(actual) ~= 'number' or type(expected) ~= 'number' or type(margin) ~= 'number' then 1310 | error_fmt(3, 'almostEquals: must supply only number arguments.\nArguments supplied: %s, %s, %s', 1311 | prettystr(actual), prettystr(expected), prettystr(margin)) 1312 | end 1313 | if margin < 0 then 1314 | error('almostEquals: margin must not be negative, current value is ' .. margin, 3) 1315 | end 1316 | return math.abs(expected - actual) <= margin 1317 | end 1318 | 1319 | function M.assertAlmostEquals( actual, expected, margin, extra_msg_or_nil ) 1320 | -- check that two floats are close by margin 1321 | margin = margin or M.EPS 1322 | if not M.almostEquals(actual, expected, margin) then 1323 | if not M.ORDER_ACTUAL_EXPECTED then 1324 | expected, actual = actual, expected 1325 | end 1326 | local delta = math.abs(actual - expected) 1327 | fail_fmt(2, extra_msg_or_nil, 'Values are not almost equal\n' .. 1328 | 'Actual: %s, expected: %s, delta %s above margin of %s', 1329 | actual, expected, delta, margin) 1330 | end 1331 | end 1332 | 1333 | function M.assertNotEquals(actual, expected, extra_msg_or_nil) 1334 | if type(actual) ~= type(expected) then 1335 | return 1336 | end 1337 | 1338 | if type(actual) == 'table' and type(expected) == 'table' then 1339 | if not _is_table_equals(actual, expected) then 1340 | return 1341 | end 1342 | elseif actual ~= expected then 1343 | return 1344 | end 1345 | fail_fmt(2, extra_msg_or_nil, 'Received the not expected value: %s', prettystr(actual)) 1346 | end 1347 | 1348 | function M.assertNotAlmostEquals( actual, expected, margin, extra_msg_or_nil ) 1349 | -- check that two floats are not close by margin 1350 | margin = margin or M.EPS 1351 | if M.almostEquals(actual, expected, margin) then 1352 | if not M.ORDER_ACTUAL_EXPECTED then 1353 | expected, actual = actual, expected 1354 | end 1355 | local delta = math.abs(actual - expected) 1356 | fail_fmt(2, extra_msg_or_nil, 'Values are almost equal\nActual: %s, expected: %s' .. 1357 | ', delta %s below margin of %s', 1358 | actual, expected, delta, margin) 1359 | end 1360 | end 1361 | 1362 | function M.assertItemsEquals(actual, expected, extra_msg_or_nil) 1363 | -- checks that the items of table expected 1364 | -- are contained in table actual. Warning, this function 1365 | -- is at least O(n^2) 1366 | if not _is_table_items_equals(actual, expected ) then 1367 | expected, actual = prettystrPairs(expected, actual) 1368 | fail_fmt(2, extra_msg_or_nil, 'Content of the tables are not identical:\nExpected: %s\nActual: %s', 1369 | expected, actual) 1370 | end 1371 | end 1372 | 1373 | ------------------------------------------------------------------ 1374 | -- String assertion 1375 | ------------------------------------------------------------------ 1376 | 1377 | function M.assertStrContains( str, sub, isPattern, extra_msg_or_nil ) 1378 | -- this relies on lua string.find function 1379 | -- a string always contains the empty string 1380 | -- assert( type(str) == 'string', 'Argument 1 of assertStrContains() should be a string.' ) ) 1381 | -- assert( type(sub) == 'string', 'Argument 2 of assertStrContains() should be a string.' ) ) 1382 | if not string.find(str, sub, 1, not isPattern) then 1383 | sub, str = prettystrPairs(sub, str, '\n') 1384 | fail_fmt(2, extra_msg_or_nil, 'Could not find %s %s in string %s', 1385 | isPattern and 'pattern' or 'substring', sub, str) 1386 | end 1387 | end 1388 | 1389 | function M.assertStrIContains( str, sub, extra_msg_or_nil ) 1390 | -- this relies on lua string.find function 1391 | -- a string always contains the empty string 1392 | if not string.find(str:lower(), sub:lower(), 1, true) then 1393 | sub, str = prettystrPairs(sub, str, '\n') 1394 | fail_fmt(2, extra_msg_or_nil, 'Could not find (case insensitively) substring %s in string %s', 1395 | sub, str) 1396 | end 1397 | end 1398 | 1399 | function M.assertNotStrContains( str, sub, isPattern, extra_msg_or_nil ) 1400 | -- this relies on lua string.find function 1401 | -- a string always contains the empty string 1402 | if string.find(str, sub, 1, not isPattern) then 1403 | sub, str = prettystrPairs(sub, str, '\n') 1404 | fail_fmt(2, extra_msg_or_nil, 'Found the not expected %s %s in string %s', 1405 | isPattern and 'pattern' or 'substring', sub, str) 1406 | end 1407 | end 1408 | 1409 | function M.assertNotStrIContains( str, sub, extra_msg_or_nil ) 1410 | -- this relies on lua string.find function 1411 | -- a string always contains the empty string 1412 | if string.find(str:lower(), sub:lower(), 1, true) then 1413 | sub, str = prettystrPairs(sub, str, '\n') 1414 | fail_fmt(2, extra_msg_or_nil, 'Found (case insensitively) the not expected substring %s in string %s', 1415 | sub, str) 1416 | end 1417 | end 1418 | 1419 | function M.assertStrMatches( str, pattern, start, final, extra_msg_or_nil ) 1420 | -- Verify a full match for the string 1421 | if not strMatch( str, pattern, start, final ) then 1422 | pattern, str = prettystrPairs(pattern, str, '\n') 1423 | fail_fmt(2, extra_msg_or_nil, 'Could not match pattern %s with string %s', 1424 | pattern, str) 1425 | end 1426 | end 1427 | 1428 | local function _assertErrorMsgEquals( stripFileAndLine, expectedMsg, func, ... ) 1429 | local no_error, error_msg = pcall( func, ... ) 1430 | if no_error then 1431 | failure( 'No error generated when calling function but expected error: '..M.prettystr(expectedMsg), nil, 3 ) 1432 | end 1433 | if type(expectedMsg) == "string" and type(error_msg) ~= "string" then 1434 | -- table are converted to string automatically 1435 | error_msg = tostring(error_msg) 1436 | end 1437 | local differ = false 1438 | if stripFileAndLine then 1439 | if error_msg:gsub("^.+:%d+: ", "") ~= expectedMsg then 1440 | differ = true 1441 | end 1442 | else 1443 | if error_msg ~= expectedMsg then 1444 | local tr = type(error_msg) 1445 | local te = type(expectedMsg) 1446 | if te == 'table' then 1447 | if tr ~= 'table' then 1448 | differ = true 1449 | else 1450 | local ok = pcall(M.assertItemsEquals, error_msg, expectedMsg) 1451 | if not ok then 1452 | differ = true 1453 | end 1454 | end 1455 | else 1456 | differ = true 1457 | end 1458 | end 1459 | end 1460 | 1461 | if differ then 1462 | error_msg, expectedMsg = prettystrPairs(error_msg, expectedMsg) 1463 | fail_fmt(3, nil, 'Error message expected: %s\nError message received: %s\n', 1464 | expectedMsg, error_msg) 1465 | end 1466 | end 1467 | 1468 | function M.assertErrorMsgEquals( expectedMsg, func, ... ) 1469 | -- assert that calling f with the arguments will raise an error 1470 | -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error 1471 | _assertErrorMsgEquals(false, expectedMsg, func, ...) 1472 | end 1473 | 1474 | function M.assertErrorMsgContentEquals(expectedMsg, func, ...) 1475 | _assertErrorMsgEquals(true, expectedMsg, func, ...) 1476 | end 1477 | 1478 | function M.assertErrorMsgContains( partialMsg, func, ... ) 1479 | -- assert that calling f with the arguments will raise an error 1480 | -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error 1481 | local no_error, error_msg = pcall( func, ... ) 1482 | if no_error then 1483 | failure( 'No error generated when calling function but expected error containing: '..prettystr(partialMsg), nil, 2 ) 1484 | end 1485 | if type(error_msg) ~= "string" then 1486 | error_msg = tostring(error_msg) 1487 | end 1488 | if not string.find( error_msg, partialMsg, nil, true ) then 1489 | error_msg, partialMsg = prettystrPairs(error_msg, partialMsg) 1490 | fail_fmt(2, nil, 'Error message does not contain: %s\nError message received: %s\n', 1491 | partialMsg, error_msg) 1492 | end 1493 | end 1494 | 1495 | function M.assertErrorMsgMatches( expectedMsg, func, ... ) 1496 | -- assert that calling f with the arguments will raise an error 1497 | -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error 1498 | local no_error, error_msg = pcall( func, ... ) 1499 | if no_error then 1500 | failure( 'No error generated when calling function but expected error matching: "'..expectedMsg..'"', nil, 2 ) 1501 | end 1502 | if type(error_msg) ~= "string" then 1503 | error_msg = tostring(error_msg) 1504 | end 1505 | if not strMatch( error_msg, expectedMsg ) then 1506 | expectedMsg, error_msg = prettystrPairs(expectedMsg, error_msg) 1507 | fail_fmt(2, nil, 'Error message does not match pattern: %s\nError message received: %s\n', 1508 | expectedMsg, error_msg) 1509 | end 1510 | end 1511 | 1512 | ------------------------------------------------------------------ 1513 | -- Type assertions 1514 | ------------------------------------------------------------------ 1515 | 1516 | function M.assertEvalToTrue(value, extra_msg_or_nil) 1517 | if not value then 1518 | failure("expected: a value evaluating to true, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1519 | end 1520 | end 1521 | 1522 | function M.assertEvalToFalse(value, extra_msg_or_nil) 1523 | if value then 1524 | failure("expected: false or nil, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1525 | end 1526 | end 1527 | 1528 | function M.assertIsTrue(value, extra_msg_or_nil) 1529 | if value ~= true then 1530 | failure("expected: true, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1531 | end 1532 | end 1533 | 1534 | function M.assertNotIsTrue(value, extra_msg_or_nil) 1535 | if value == true then 1536 | failure("expected: not true, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1537 | end 1538 | end 1539 | 1540 | function M.assertIsFalse(value, extra_msg_or_nil) 1541 | if value ~= false then 1542 | failure("expected: false, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1543 | end 1544 | end 1545 | 1546 | function M.assertNotIsFalse(value, extra_msg_or_nil) 1547 | if value == false then 1548 | failure("expected: not false, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1549 | end 1550 | end 1551 | 1552 | function M.assertIsNil(value, extra_msg_or_nil) 1553 | if value ~= nil then 1554 | failure("expected: nil, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1555 | end 1556 | end 1557 | 1558 | function M.assertNotIsNil(value, extra_msg_or_nil) 1559 | if value == nil then 1560 | failure("expected: not nil, actual: nil", extra_msg_or_nil, 2) 1561 | end 1562 | end 1563 | 1564 | --[[ 1565 | Add type assertion functions to the module table M. Each of these functions 1566 | takes a single parameter "value", and checks that its Lua type matches the 1567 | expected string (derived from the function name): 1568 | 1569 | M.assertIsXxx(value) -> ensure that type(value) conforms to "xxx" 1570 | ]] 1571 | for _, funcName in ipairs( 1572 | {'assertIsNumber', 'assertIsString', 'assertIsTable', 'assertIsBoolean', 1573 | 'assertIsFunction', 'assertIsUserdata', 'assertIsThread'} 1574 | ) do 1575 | local typeExpected = funcName:match("^assertIs([A-Z]%a*)$") 1576 | -- Lua type() always returns lowercase, also make sure the match() succeeded 1577 | typeExpected = typeExpected and typeExpected:lower() 1578 | or error("bad function name '"..funcName.."' for type assertion") 1579 | 1580 | M[funcName] = function(value, extra_msg_or_nil) 1581 | if type(value) ~= typeExpected then 1582 | if type(value) == 'nil' then 1583 | fail_fmt(2, extra_msg_or_nil, 'expected: a %s value, actual: nil', 1584 | typeExpected, type(value), prettystrPairs(value)) 1585 | else 1586 | fail_fmt(2, extra_msg_or_nil, 'expected: a %s value, actual: type %s, value %s', 1587 | typeExpected, type(value), prettystrPairs(value)) 1588 | end 1589 | end 1590 | end 1591 | end 1592 | 1593 | --[[ 1594 | Add shortcuts for verifying type of a variable, without failure (luaunit v2 compatibility) 1595 | M.isXxx(value) -> returns true if type(value) conforms to "xxx" 1596 | ]] 1597 | for _, typeExpected in ipairs( 1598 | {'Number', 'String', 'Table', 'Boolean', 1599 | 'Function', 'Userdata', 'Thread', 'Nil' } 1600 | ) do 1601 | local typeExpectedLower = typeExpected:lower() 1602 | local isType = function(value) 1603 | return (type(value) == typeExpectedLower) 1604 | end 1605 | M['is'..typeExpected] = isType 1606 | M['is_'..typeExpectedLower] = isType 1607 | end 1608 | 1609 | --[[ 1610 | Add non-type assertion functions to the module table M. Each of these functions 1611 | takes a single parameter "value", and checks that its Lua type differs from the 1612 | expected string (derived from the function name): 1613 | 1614 | M.assertNotIsXxx(value) -> ensure that type(value) is not "xxx" 1615 | ]] 1616 | for _, funcName in ipairs( 1617 | {'assertNotIsNumber', 'assertNotIsString', 'assertNotIsTable', 'assertNotIsBoolean', 1618 | 'assertNotIsFunction', 'assertNotIsUserdata', 'assertNotIsThread'} 1619 | ) do 1620 | local typeUnexpected = funcName:match("^assertNotIs([A-Z]%a*)$") 1621 | -- Lua type() always returns lowercase, also make sure the match() succeeded 1622 | typeUnexpected = typeUnexpected and typeUnexpected:lower() 1623 | or error("bad function name '"..funcName.."' for type assertion") 1624 | 1625 | M[funcName] = function(value, extra_msg_or_nil) 1626 | if type(value) == typeUnexpected then 1627 | fail_fmt(2, extra_msg_or_nil, 'expected: not a %s type, actual: value %s', 1628 | typeUnexpected, prettystrPairs(value)) 1629 | end 1630 | end 1631 | end 1632 | 1633 | function M.assertIs(actual, expected, extra_msg_or_nil) 1634 | if actual ~= expected then 1635 | if not M.ORDER_ACTUAL_EXPECTED then 1636 | actual, expected = expected, actual 1637 | end 1638 | local old_print_table_ref_in_error_msg = M.PRINT_TABLE_REF_IN_ERROR_MSG 1639 | M.PRINT_TABLE_REF_IN_ERROR_MSG = true 1640 | expected, actual = prettystrPairs(expected, actual, '\n', '') 1641 | M.PRINT_TABLE_REF_IN_ERROR_MSG = old_print_table_ref_in_error_msg 1642 | fail_fmt(2, extra_msg_or_nil, 'expected and actual object should not be different\nExpected: %s\nReceived: %s', 1643 | expected, actual) 1644 | end 1645 | end 1646 | 1647 | function M.assertNotIs(actual, expected, extra_msg_or_nil) 1648 | if actual == expected then 1649 | local old_print_table_ref_in_error_msg = M.PRINT_TABLE_REF_IN_ERROR_MSG 1650 | M.PRINT_TABLE_REF_IN_ERROR_MSG = true 1651 | local s_expected 1652 | if not M.ORDER_ACTUAL_EXPECTED then 1653 | s_expected = prettystrPairs(actual) 1654 | else 1655 | s_expected = prettystrPairs(expected) 1656 | end 1657 | M.PRINT_TABLE_REF_IN_ERROR_MSG = old_print_table_ref_in_error_msg 1658 | fail_fmt(2, extra_msg_or_nil, 'expected and actual object should be different: %s', s_expected ) 1659 | end 1660 | end 1661 | 1662 | 1663 | ------------------------------------------------------------------ 1664 | -- Scientific assertions 1665 | ------------------------------------------------------------------ 1666 | 1667 | 1668 | function M.assertIsNaN(value, extra_msg_or_nil) 1669 | if type(value) ~= "number" or value == value then 1670 | failure("expected: NaN, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1671 | end 1672 | end 1673 | 1674 | function M.assertNotIsNaN(value, extra_msg_or_nil) 1675 | if type(value) == "number" and value ~= value then 1676 | failure("expected: not NaN, actual: NaN", extra_msg_or_nil, 2) 1677 | end 1678 | end 1679 | 1680 | function M.assertIsInf(value, extra_msg_or_nil) 1681 | if type(value) ~= "number" or math.abs(value) ~= math.huge then 1682 | failure("expected: #Inf, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1683 | end 1684 | end 1685 | 1686 | function M.assertIsPlusInf(value, extra_msg_or_nil) 1687 | if type(value) ~= "number" or value ~= math.huge then 1688 | failure("expected: #Inf, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1689 | end 1690 | end 1691 | 1692 | function M.assertIsMinusInf(value, extra_msg_or_nil) 1693 | if type(value) ~= "number" or value ~= -math.huge then 1694 | failure("expected: -#Inf, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1695 | end 1696 | end 1697 | 1698 | function M.assertNotIsPlusInf(value, extra_msg_or_nil) 1699 | if type(value) == "number" and value == math.huge then 1700 | failure("expected: not #Inf, actual: #Inf", extra_msg_or_nil, 2) 1701 | end 1702 | end 1703 | 1704 | function M.assertNotIsMinusInf(value, extra_msg_or_nil) 1705 | if type(value) == "number" and value == -math.huge then 1706 | failure("expected: not -#Inf, actual: -#Inf", extra_msg_or_nil, 2) 1707 | end 1708 | end 1709 | 1710 | function M.assertNotIsInf(value, extra_msg_or_nil) 1711 | if type(value) == "number" and math.abs(value) == math.huge then 1712 | failure("expected: not infinity, actual: " .. prettystr(value), extra_msg_or_nil, 2) 1713 | end 1714 | end 1715 | 1716 | function M.assertIsPlusZero(value, extra_msg_or_nil) 1717 | if type(value) ~= 'number' or value ~= 0 then 1718 | failure("expected: +0.0, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1719 | else if (1/value == -math.huge) then 1720 | -- more precise error diagnosis 1721 | failure("expected: +0.0, actual: -0.0", extra_msg_or_nil, 2) 1722 | else if (1/value ~= math.huge) then 1723 | -- strange, case should have already been covered 1724 | failure("expected: +0.0, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1725 | end 1726 | end 1727 | end 1728 | end 1729 | 1730 | function M.assertIsMinusZero(value, extra_msg_or_nil) 1731 | if type(value) ~= 'number' or value ~= 0 then 1732 | failure("expected: -0.0, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1733 | else if (1/value == math.huge) then 1734 | -- more precise error diagnosis 1735 | failure("expected: -0.0, actual: +0.0", extra_msg_or_nil, 2) 1736 | else if (1/value ~= -math.huge) then 1737 | -- strange, case should have already been covered 1738 | failure("expected: -0.0, actual: " ..prettystr(value), extra_msg_or_nil, 2) 1739 | end 1740 | end 1741 | end 1742 | end 1743 | 1744 | function M.assertNotIsPlusZero(value, extra_msg_or_nil) 1745 | if type(value) == 'number' and (1/value == math.huge) then 1746 | failure("expected: not +0.0, actual: +0.0", extra_msg_or_nil, 2) 1747 | end 1748 | end 1749 | 1750 | function M.assertNotIsMinusZero(value, extra_msg_or_nil) 1751 | if type(value) == 'number' and (1/value == -math.huge) then 1752 | failure("expected: not -0.0, actual: -0.0", extra_msg_or_nil, 2) 1753 | end 1754 | end 1755 | 1756 | function M.assertTableContains(t, expected) 1757 | -- checks that table t contains the expected element 1758 | if table_findkeyof(t, expected) == nil then 1759 | t, expected = prettystrPairs(t, expected) 1760 | fail_fmt(2, 'Table %s does NOT contain the expected element %s', 1761 | t, expected) 1762 | end 1763 | end 1764 | 1765 | function M.assertNotTableContains(t, expected) 1766 | -- checks that table t doesn't contain the expected element 1767 | local k = table_findkeyof(t, expected) 1768 | if k ~= nil then 1769 | t, expected = prettystrPairs(t, expected) 1770 | fail_fmt(2, 'Table %s DOES contain the unwanted element %s (at key %s)', 1771 | t, expected, prettystr(k)) 1772 | end 1773 | end 1774 | 1775 | ---------------------------------------------------------------- 1776 | -- Compatibility layer 1777 | ---------------------------------------------------------------- 1778 | 1779 | -- for compatibility with LuaUnit v2.x 1780 | function M.wrapFunctions() 1781 | -- In LuaUnit version <= 2.1 , this function was necessary to include 1782 | -- a test function inside the global test suite. Nowadays, the functions 1783 | -- are simply run directly as part of the test discovery process. 1784 | -- so just do nothing ! 1785 | io.stderr:write[[Use of WrapFunctions() is no longer needed. 1786 | Just prefix your test function names with "test" or "Test" and they 1787 | will be picked up and run by LuaUnit. 1788 | ]] 1789 | end 1790 | 1791 | local list_of_funcs = { 1792 | -- { official function name , alias } 1793 | 1794 | -- general assertions 1795 | { 'assertEquals' , 'assert_equals' }, 1796 | { 'assertItemsEquals' , 'assert_items_equals' }, 1797 | { 'assertNotEquals' , 'assert_not_equals' }, 1798 | { 'assertAlmostEquals' , 'assert_almost_equals' }, 1799 | { 'assertNotAlmostEquals' , 'assert_not_almost_equals' }, 1800 | { 'assertEvalToTrue' , 'assert_eval_to_true' }, 1801 | { 'assertEvalToFalse' , 'assert_eval_to_false' }, 1802 | { 'assertStrContains' , 'assert_str_contains' }, 1803 | { 'assertStrIContains' , 'assert_str_icontains' }, 1804 | { 'assertNotStrContains' , 'assert_not_str_contains' }, 1805 | { 'assertNotStrIContains' , 'assert_not_str_icontains' }, 1806 | { 'assertStrMatches' , 'assert_str_matches' }, 1807 | { 'assertError' , 'assert_error' }, 1808 | { 'assertErrorMsgEquals' , 'assert_error_msg_equals' }, 1809 | { 'assertErrorMsgContains' , 'assert_error_msg_contains' }, 1810 | { 'assertErrorMsgMatches' , 'assert_error_msg_matches' }, 1811 | { 'assertErrorMsgContentEquals', 'assert_error_msg_content_equals' }, 1812 | { 'assertIs' , 'assert_is' }, 1813 | { 'assertNotIs' , 'assert_not_is' }, 1814 | { 'assertTableContains' , 'assert_table_contains' }, 1815 | { 'assertNotTableContains' , 'assert_not_table_contains' }, 1816 | { 'wrapFunctions' , 'WrapFunctions' }, 1817 | { 'wrapFunctions' , 'wrap_functions' }, 1818 | 1819 | -- type assertions: assertIsXXX -> assert_is_xxx 1820 | { 'assertIsNumber' , 'assert_is_number' }, 1821 | { 'assertIsString' , 'assert_is_string' }, 1822 | { 'assertIsTable' , 'assert_is_table' }, 1823 | { 'assertIsBoolean' , 'assert_is_boolean' }, 1824 | { 'assertIsNil' , 'assert_is_nil' }, 1825 | { 'assertIsTrue' , 'assert_is_true' }, 1826 | { 'assertIsFalse' , 'assert_is_false' }, 1827 | { 'assertIsNaN' , 'assert_is_nan' }, 1828 | { 'assertIsInf' , 'assert_is_inf' }, 1829 | { 'assertIsPlusInf' , 'assert_is_plus_inf' }, 1830 | { 'assertIsMinusInf' , 'assert_is_minus_inf' }, 1831 | { 'assertIsPlusZero' , 'assert_is_plus_zero' }, 1832 | { 'assertIsMinusZero' , 'assert_is_minus_zero' }, 1833 | { 'assertIsFunction' , 'assert_is_function' }, 1834 | { 'assertIsThread' , 'assert_is_thread' }, 1835 | { 'assertIsUserdata' , 'assert_is_userdata' }, 1836 | 1837 | -- type assertions: assertIsXXX -> assertXxx 1838 | { 'assertIsNumber' , 'assertNumber' }, 1839 | { 'assertIsString' , 'assertString' }, 1840 | { 'assertIsTable' , 'assertTable' }, 1841 | { 'assertIsBoolean' , 'assertBoolean' }, 1842 | { 'assertIsNil' , 'assertNil' }, 1843 | { 'assertIsTrue' , 'assertTrue' }, 1844 | { 'assertIsFalse' , 'assertFalse' }, 1845 | { 'assertIsNaN' , 'assertNaN' }, 1846 | { 'assertIsInf' , 'assertInf' }, 1847 | { 'assertIsPlusInf' , 'assertPlusInf' }, 1848 | { 'assertIsMinusInf' , 'assertMinusInf' }, 1849 | { 'assertIsPlusZero' , 'assertPlusZero' }, 1850 | { 'assertIsMinusZero' , 'assertMinusZero'}, 1851 | { 'assertIsFunction' , 'assertFunction' }, 1852 | { 'assertIsThread' , 'assertThread' }, 1853 | { 'assertIsUserdata' , 'assertUserdata' }, 1854 | 1855 | -- type assertions: assertIsXXX -> assert_xxx (luaunit v2 compat) 1856 | { 'assertIsNumber' , 'assert_number' }, 1857 | { 'assertIsString' , 'assert_string' }, 1858 | { 'assertIsTable' , 'assert_table' }, 1859 | { 'assertIsBoolean' , 'assert_boolean' }, 1860 | { 'assertIsNil' , 'assert_nil' }, 1861 | { 'assertIsTrue' , 'assert_true' }, 1862 | { 'assertIsFalse' , 'assert_false' }, 1863 | { 'assertIsNaN' , 'assert_nan' }, 1864 | { 'assertIsInf' , 'assert_inf' }, 1865 | { 'assertIsPlusInf' , 'assert_plus_inf' }, 1866 | { 'assertIsMinusInf' , 'assert_minus_inf' }, 1867 | { 'assertIsPlusZero' , 'assert_plus_zero' }, 1868 | { 'assertIsMinusZero' , 'assert_minus_zero' }, 1869 | { 'assertIsFunction' , 'assert_function' }, 1870 | { 'assertIsThread' , 'assert_thread' }, 1871 | { 'assertIsUserdata' , 'assert_userdata' }, 1872 | 1873 | -- type assertions: assertNotIsXXX -> assert_not_is_xxx 1874 | { 'assertNotIsNumber' , 'assert_not_is_number' }, 1875 | { 'assertNotIsString' , 'assert_not_is_string' }, 1876 | { 'assertNotIsTable' , 'assert_not_is_table' }, 1877 | { 'assertNotIsBoolean' , 'assert_not_is_boolean' }, 1878 | { 'assertNotIsNil' , 'assert_not_is_nil' }, 1879 | { 'assertNotIsTrue' , 'assert_not_is_true' }, 1880 | { 'assertNotIsFalse' , 'assert_not_is_false' }, 1881 | { 'assertNotIsNaN' , 'assert_not_is_nan' }, 1882 | { 'assertNotIsInf' , 'assert_not_is_inf' }, 1883 | { 'assertNotIsPlusInf' , 'assert_not_plus_inf' }, 1884 | { 'assertNotIsMinusInf' , 'assert_not_minus_inf' }, 1885 | { 'assertNotIsPlusZero' , 'assert_not_plus_zero' }, 1886 | { 'assertNotIsMinusZero' , 'assert_not_minus_zero' }, 1887 | { 'assertNotIsFunction' , 'assert_not_is_function' }, 1888 | { 'assertNotIsThread' , 'assert_not_is_thread' }, 1889 | { 'assertNotIsUserdata' , 'assert_not_is_userdata' }, 1890 | 1891 | -- type assertions: assertNotIsXXX -> assertNotXxx (luaunit v2 compat) 1892 | { 'assertNotIsNumber' , 'assertNotNumber' }, 1893 | { 'assertNotIsString' , 'assertNotString' }, 1894 | { 'assertNotIsTable' , 'assertNotTable' }, 1895 | { 'assertNotIsBoolean' , 'assertNotBoolean' }, 1896 | { 'assertNotIsNil' , 'assertNotNil' }, 1897 | { 'assertNotIsTrue' , 'assertNotTrue' }, 1898 | { 'assertNotIsFalse' , 'assertNotFalse' }, 1899 | { 'assertNotIsNaN' , 'assertNotNaN' }, 1900 | { 'assertNotIsInf' , 'assertNotInf' }, 1901 | { 'assertNotIsPlusInf' , 'assertNotPlusInf' }, 1902 | { 'assertNotIsMinusInf' , 'assertNotMinusInf' }, 1903 | { 'assertNotIsPlusZero' , 'assertNotPlusZero' }, 1904 | { 'assertNotIsMinusZero' , 'assertNotMinusZero' }, 1905 | { 'assertNotIsFunction' , 'assertNotFunction' }, 1906 | { 'assertNotIsThread' , 'assertNotThread' }, 1907 | { 'assertNotIsUserdata' , 'assertNotUserdata' }, 1908 | 1909 | -- type assertions: assertNotIsXXX -> assert_not_xxx 1910 | { 'assertNotIsNumber' , 'assert_not_number' }, 1911 | { 'assertNotIsString' , 'assert_not_string' }, 1912 | { 'assertNotIsTable' , 'assert_not_table' }, 1913 | { 'assertNotIsBoolean' , 'assert_not_boolean' }, 1914 | { 'assertNotIsNil' , 'assert_not_nil' }, 1915 | { 'assertNotIsTrue' , 'assert_not_true' }, 1916 | { 'assertNotIsFalse' , 'assert_not_false' }, 1917 | { 'assertNotIsNaN' , 'assert_not_nan' }, 1918 | { 'assertNotIsInf' , 'assert_not_inf' }, 1919 | { 'assertNotIsPlusInf' , 'assert_not_plus_inf' }, 1920 | { 'assertNotIsMinusInf' , 'assert_not_minus_inf' }, 1921 | { 'assertNotIsPlusZero' , 'assert_not_plus_zero' }, 1922 | { 'assertNotIsMinusZero' , 'assert_not_minus_zero' }, 1923 | { 'assertNotIsFunction' , 'assert_not_function' }, 1924 | { 'assertNotIsThread' , 'assert_not_thread' }, 1925 | { 'assertNotIsUserdata' , 'assert_not_userdata' }, 1926 | 1927 | -- all assertions with Coroutine duplicate Thread assertions 1928 | { 'assertIsThread' , 'assertIsCoroutine' }, 1929 | { 'assertIsThread' , 'assertCoroutine' }, 1930 | { 'assertIsThread' , 'assert_is_coroutine' }, 1931 | { 'assertIsThread' , 'assert_coroutine' }, 1932 | { 'assertNotIsThread' , 'assertNotIsCoroutine' }, 1933 | { 'assertNotIsThread' , 'assertNotCoroutine' }, 1934 | { 'assertNotIsThread' , 'assert_not_is_coroutine' }, 1935 | { 'assertNotIsThread' , 'assert_not_coroutine' }, 1936 | } 1937 | 1938 | -- Create all aliases in M 1939 | for _,v in ipairs( list_of_funcs ) do 1940 | local funcname, alias = v[1], v[2] 1941 | M[alias] = M[funcname] 1942 | 1943 | if EXPORT_ASSERT_TO_GLOBALS then 1944 | _G[funcname] = M[funcname] 1945 | _G[alias] = M[funcname] 1946 | end 1947 | end 1948 | 1949 | ---------------------------------------------------------------- 1950 | -- 1951 | -- Outputters 1952 | -- 1953 | ---------------------------------------------------------------- 1954 | 1955 | -- A common "base" class for outputters 1956 | -- For concepts involved (class inheritance) see http://www.lua.org/pil/16.2.html 1957 | 1958 | local genericOutput = { __class__ = 'genericOutput' } -- class 1959 | local genericOutput_MT = { __index = genericOutput } -- metatable 1960 | M.genericOutput = genericOutput -- publish, so that custom classes may derive from it 1961 | 1962 | function genericOutput.new(runner, default_verbosity) 1963 | -- runner is the "parent" object controlling the output, usually a LuaUnit instance 1964 | local t = { runner = runner } 1965 | if runner then 1966 | t.result = runner.result 1967 | t.verbosity = runner.verbosity or default_verbosity 1968 | t.fname = runner.fname 1969 | else 1970 | t.verbosity = default_verbosity 1971 | end 1972 | return setmetatable( t, genericOutput_MT) 1973 | end 1974 | 1975 | -- abstract ("empty") methods 1976 | function genericOutput:startSuite() 1977 | -- Called once, when the suite is started 1978 | end 1979 | 1980 | function genericOutput:startClass(className) 1981 | -- Called each time a new test class is started 1982 | end 1983 | 1984 | function genericOutput:startTest(testName) 1985 | -- called each time a new test is started, right before the setUp() 1986 | -- the current test status node is already created and available in: self.result.currentNode 1987 | end 1988 | 1989 | function genericOutput:updateStatus(node) 1990 | -- called with status failed or error as soon as the error/failure is encountered 1991 | -- this method is NOT called for a successful test because a test is marked as successful by default 1992 | -- and does not need to be updated 1993 | end 1994 | 1995 | function genericOutput:endTest(node) 1996 | -- called when the test is finished, after the tearDown() method 1997 | end 1998 | 1999 | function genericOutput:endClass() 2000 | -- called when executing the class is finished, before moving on to the next class of at the end of the test execution 2001 | end 2002 | 2003 | function genericOutput:endSuite() 2004 | -- called at the end of the test suite execution 2005 | end 2006 | 2007 | 2008 | ---------------------------------------------------------------- 2009 | -- class TapOutput 2010 | ---------------------------------------------------------------- 2011 | 2012 | local TapOutput = genericOutput.new() -- derived class 2013 | local TapOutput_MT = { __index = TapOutput } -- metatable 2014 | TapOutput.__class__ = 'TapOutput' 2015 | 2016 | -- For a good reference for TAP format, check: http://testanything.org/tap-specification.html 2017 | 2018 | function TapOutput.new(runner) 2019 | local t = genericOutput.new(runner, M.VERBOSITY_LOW) 2020 | return setmetatable( t, TapOutput_MT) 2021 | end 2022 | function TapOutput:startSuite() 2023 | print("1.."..self.result.selectedCount) 2024 | print('# Started on '..self.result.startDate) 2025 | end 2026 | function TapOutput:startClass(className) 2027 | if className ~= '[TestFunctions]' then 2028 | print('# Starting class: '..className) 2029 | end 2030 | end 2031 | 2032 | function TapOutput:updateStatus( node ) 2033 | if node:isSkipped() then 2034 | io.stdout:write("ok ", self.result.currentTestNumber, "\t# SKIP ", node.msg, "\n" ) 2035 | return 2036 | end 2037 | 2038 | io.stdout:write("not ok ", self.result.currentTestNumber, "\t", node.testName, "\n") 2039 | if self.verbosity > M.VERBOSITY_LOW then 2040 | print( prefixString( '# ', node.msg ) ) 2041 | end 2042 | if (node:isFailure() or node:isError()) and self.verbosity > M.VERBOSITY_DEFAULT then 2043 | print( prefixString( '# ', node.stackTrace ) ) 2044 | end 2045 | end 2046 | 2047 | function TapOutput:endTest( node ) 2048 | if node:isSuccess() then 2049 | io.stdout:write("ok ", self.result.currentTestNumber, "\t", node.testName, "\n") 2050 | end 2051 | end 2052 | 2053 | function TapOutput:endSuite() 2054 | print( '# '..M.LuaUnit.statusLine( self.result ) ) 2055 | return self.result.notSuccessCount 2056 | end 2057 | 2058 | 2059 | -- class TapOutput end 2060 | 2061 | ---------------------------------------------------------------- 2062 | -- class JUnitOutput 2063 | ---------------------------------------------------------------- 2064 | 2065 | -- See directory junitxml for more information about the junit format 2066 | local JUnitOutput = genericOutput.new() -- derived class 2067 | local JUnitOutput_MT = { __index = JUnitOutput } -- metatable 2068 | JUnitOutput.__class__ = 'JUnitOutput' 2069 | 2070 | function JUnitOutput.new(runner) 2071 | local t = genericOutput.new(runner, M.VERBOSITY_LOW) 2072 | t.testList = {} 2073 | return setmetatable( t, JUnitOutput_MT ) 2074 | end 2075 | 2076 | function JUnitOutput:startSuite() 2077 | -- open xml file early to deal with errors 2078 | if self.fname == nil then 2079 | error('With Junit, an output filename must be supplied with --name!') 2080 | end 2081 | if string.sub(self.fname,-4) ~= '.xml' then 2082 | self.fname = self.fname..'.xml' 2083 | end 2084 | self.fd = io.open(self.fname, "w") 2085 | if self.fd == nil then 2086 | error("Could not open file for writing: "..self.fname) 2087 | end 2088 | 2089 | print('# XML output to '..self.fname) 2090 | print('# Started on '..self.result.startDate) 2091 | end 2092 | function JUnitOutput:startClass(className) 2093 | if className ~= '[TestFunctions]' then 2094 | print('# Starting class: '..className) 2095 | end 2096 | end 2097 | function JUnitOutput:startTest(testName) 2098 | print('# Starting test: '..testName) 2099 | end 2100 | 2101 | function JUnitOutput:updateStatus( node ) 2102 | if node:isFailure() then 2103 | print( '# Failure: ' .. prefixString( '# ', node.msg ):sub(4, nil) ) 2104 | -- print('# ' .. node.stackTrace) 2105 | elseif node:isError() then 2106 | print( '# Error: ' .. prefixString( '# ' , node.msg ):sub(4, nil) ) 2107 | -- print('# ' .. node.stackTrace) 2108 | end 2109 | end 2110 | 2111 | function JUnitOutput:endSuite() 2112 | print( '# '..M.LuaUnit.statusLine(self.result)) 2113 | 2114 | -- XML file writing 2115 | self.fd:write('\n') 2116 | self.fd:write('\n') 2117 | self.fd:write(string.format( 2118 | ' \n', 2119 | self.result.runCount, self.result.startIsodate, self.result.duration, self.result.errorCount, self.result.failureCount, self.result.skippedCount )) 2120 | self.fd:write(" \n") 2121 | self.fd:write(string.format(' \n', _VERSION ) ) 2122 | self.fd:write(string.format(' \n', M.VERSION) ) 2123 | -- XXX please include system name and version if possible 2124 | self.fd:write(" \n") 2125 | 2126 | for i,node in ipairs(self.result.allTests) do 2127 | self.fd:write(string.format(' \n', 2128 | node.className, node.testName, node.duration ) ) 2129 | if node:isNotSuccess() then 2130 | self.fd:write(node:statusXML()) 2131 | end 2132 | self.fd:write(' \n') 2133 | end 2134 | 2135 | -- Next two lines are needed to validate junit ANT xsd, but really not useful in general: 2136 | self.fd:write(' \n') 2137 | self.fd:write(' \n') 2138 | 2139 | self.fd:write(' \n') 2140 | self.fd:write('\n') 2141 | self.fd:close() 2142 | return self.result.notSuccessCount 2143 | end 2144 | 2145 | 2146 | -- class TapOutput end 2147 | 2148 | ---------------------------------------------------------------- 2149 | -- class TextOutput 2150 | ---------------------------------------------------------------- 2151 | 2152 | --[[ Example of other unit-tests suite text output 2153 | 2154 | -- Python Non verbose: 2155 | 2156 | For each test: . or F or E 2157 | 2158 | If some failed tests: 2159 | ============== 2160 | ERROR / FAILURE: TestName (testfile.testclass) 2161 | --------- 2162 | Stack trace 2163 | 2164 | 2165 | then -------------- 2166 | then "Ran x tests in 0.000s" 2167 | then OK or FAILED (failures=1, error=1) 2168 | 2169 | -- Python Verbose: 2170 | testname (filename.classname) ... ok 2171 | testname (filename.classname) ... FAIL 2172 | testname (filename.classname) ... ERROR 2173 | 2174 | then -------------- 2175 | then "Ran x tests in 0.000s" 2176 | then OK or FAILED (failures=1, error=1) 2177 | 2178 | -- Ruby: 2179 | Started 2180 | . 2181 | Finished in 0.002695 seconds. 2182 | 2183 | 1 tests, 2 assertions, 0 failures, 0 errors 2184 | 2185 | -- Ruby: 2186 | >> ruby tc_simple_number2.rb 2187 | Loaded suite tc_simple_number2 2188 | Started 2189 | F.. 2190 | Finished in 0.038617 seconds. 2191 | 2192 | 1) Failure: 2193 | test_failure(TestSimpleNumber) [tc_simple_number2.rb:16]: 2194 | Adding doesn't work. 2195 | <3> expected but was 2196 | <4>. 2197 | 2198 | 3 tests, 4 assertions, 1 failures, 0 errors 2199 | 2200 | -- Java Junit 2201 | .......F. 2202 | Time: 0,003 2203 | There was 1 failure: 2204 | 1) testCapacity(junit.samples.VectorTest)junit.framework.AssertionFailedError 2205 | at junit.samples.VectorTest.testCapacity(VectorTest.java:87) 2206 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 2207 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 2208 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 2209 | 2210 | FAILURES!!! 2211 | Tests run: 8, Failures: 1, Errors: 0 2212 | 2213 | 2214 | -- Maven 2215 | 2216 | # mvn test 2217 | ------------------------------------------------------- 2218 | T E S T S 2219 | ------------------------------------------------------- 2220 | Running math.AdditionTest 2221 | Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 2222 | 0.03 sec <<< FAILURE! 2223 | 2224 | Results : 2225 | 2226 | Failed tests: 2227 | testLireSymbole(math.AdditionTest) 2228 | 2229 | Tests run: 2, Failures: 1, Errors: 0, Skipped: 0 2230 | 2231 | 2232 | -- LuaUnit 2233 | ---- non verbose 2234 | * display . or F or E when running tests 2235 | ---- verbose 2236 | * display test name + ok/fail 2237 | ---- 2238 | * blank line 2239 | * number) ERROR or FAILURE: TestName 2240 | Stack trace 2241 | * blank line 2242 | * number) ERROR or FAILURE: TestName 2243 | Stack trace 2244 | 2245 | then -------------- 2246 | then "Ran x tests in 0.000s (%d not selected, %d skipped)" 2247 | then OK or FAILED (failures=1, error=1) 2248 | 2249 | 2250 | ]] 2251 | 2252 | local TextOutput = genericOutput.new() -- derived class 2253 | local TextOutput_MT = { __index = TextOutput } -- metatable 2254 | TextOutput.__class__ = 'TextOutput' 2255 | 2256 | function TextOutput.new(runner) 2257 | local t = genericOutput.new(runner, M.VERBOSITY_DEFAULT) 2258 | t.errorList = {} 2259 | return setmetatable( t, TextOutput_MT ) 2260 | end 2261 | 2262 | function TextOutput:startSuite() 2263 | if self.verbosity > M.VERBOSITY_DEFAULT then 2264 | print( 'Started on '.. self.result.startDate ) 2265 | end 2266 | end 2267 | 2268 | function TextOutput:startTest(testName) 2269 | if self.verbosity > M.VERBOSITY_DEFAULT then 2270 | io.stdout:write( " ", self.result.currentNode.testName, " ... " ) 2271 | end 2272 | end 2273 | 2274 | function TextOutput:endTest( node ) 2275 | if node:isSuccess() then 2276 | if self.verbosity > M.VERBOSITY_DEFAULT then 2277 | io.stdout:write("Ok\n") 2278 | else 2279 | io.stdout:write(".") 2280 | io.stdout:flush() 2281 | end 2282 | else 2283 | if self.verbosity > M.VERBOSITY_DEFAULT then 2284 | print( node.status ) 2285 | print( node.msg ) 2286 | --[[ 2287 | -- find out when to do this: 2288 | if self.verbosity > M.VERBOSITY_DEFAULT then 2289 | print( node.stackTrace ) 2290 | end 2291 | ]] 2292 | else 2293 | -- write only the first character of status E, F or S 2294 | io.stdout:write(string.sub(node.status, 1, 1)) 2295 | io.stdout:flush() 2296 | end 2297 | end 2298 | end 2299 | 2300 | function TextOutput:displayOneFailedTest( index, fail ) 2301 | print(index..") "..fail.testName ) 2302 | print( fail.msg ) 2303 | print( fail.stackTrace ) 2304 | print() 2305 | end 2306 | 2307 | function TextOutput:displayErroredTests() 2308 | if #self.result.errorTests ~= 0 then 2309 | print("Tests with errors:") 2310 | print("------------------") 2311 | for i, v in ipairs(self.result.errorTests) do 2312 | self:displayOneFailedTest(i, v) 2313 | end 2314 | end 2315 | end 2316 | 2317 | function TextOutput:displayFailedTests() 2318 | if #self.result.failedTests ~= 0 then 2319 | print("Failed tests:") 2320 | print("-------------") 2321 | for i, v in ipairs(self.result.failedTests) do 2322 | self:displayOneFailedTest(i, v) 2323 | end 2324 | end 2325 | end 2326 | 2327 | function TextOutput:endSuite() 2328 | if self.verbosity > M.VERBOSITY_DEFAULT then 2329 | print("=========================================================") 2330 | else 2331 | print() 2332 | end 2333 | self:displayErroredTests() 2334 | self:displayFailedTests() 2335 | print( M.LuaUnit.statusLine( self.result ) ) 2336 | if self.result.notSuccessCount == 0 then 2337 | print('OK') 2338 | end 2339 | end 2340 | 2341 | -- class TextOutput end 2342 | 2343 | 2344 | ---------------------------------------------------------------- 2345 | -- class NilOutput 2346 | ---------------------------------------------------------------- 2347 | 2348 | local function nopCallable() 2349 | --print(42) 2350 | return nopCallable 2351 | end 2352 | 2353 | local NilOutput = { __class__ = 'NilOuptut' } -- class 2354 | local NilOutput_MT = { __index = nopCallable } -- metatable 2355 | 2356 | function NilOutput.new(runner) 2357 | return setmetatable( { __class__ = 'NilOutput' }, NilOutput_MT ) 2358 | end 2359 | 2360 | ---------------------------------------------------------------- 2361 | -- 2362 | -- class LuaUnit 2363 | -- 2364 | ---------------------------------------------------------------- 2365 | 2366 | M.LuaUnit = { 2367 | outputType = TextOutput, 2368 | verbosity = M.VERBOSITY_DEFAULT, 2369 | __class__ = 'LuaUnit' 2370 | } 2371 | local LuaUnit_MT = { __index = M.LuaUnit } 2372 | 2373 | if EXPORT_ASSERT_TO_GLOBALS then 2374 | LuaUnit = M.LuaUnit 2375 | end 2376 | 2377 | function M.LuaUnit.new() 2378 | return setmetatable( {}, LuaUnit_MT ) 2379 | end 2380 | 2381 | -----------------[[ Utility methods ]]--------------------- 2382 | 2383 | function M.LuaUnit.asFunction(aObject) 2384 | -- return "aObject" if it is a function, and nil otherwise 2385 | if 'function' == type(aObject) then 2386 | return aObject 2387 | end 2388 | end 2389 | 2390 | function M.LuaUnit.splitClassMethod(someName) 2391 | --[[ 2392 | Return a pair of className, methodName strings for a name in the form 2393 | "class.method". If no class part (or separator) is found, will return 2394 | nil, someName instead (the latter being unchanged). 2395 | 2396 | This convention thus also replaces the older isClassMethod() test: 2397 | You just have to check for a non-nil className (return) value. 2398 | ]] 2399 | local separator = string.find(someName, '.', 1, true) 2400 | if separator then 2401 | return someName:sub(1, separator - 1), someName:sub(separator + 1) 2402 | end 2403 | return nil, someName 2404 | end 2405 | 2406 | function M.LuaUnit.isMethodTestName( s ) 2407 | -- return true is the name matches the name of a test method 2408 | -- default rule is that is starts with 'Test' or with 'test' 2409 | return string.sub(s, 1, 4):lower() == 'test' 2410 | end 2411 | 2412 | function M.LuaUnit.isTestName( s ) 2413 | -- return true is the name matches the name of a test 2414 | -- default rule is that is starts with 'Test' or with 'test' 2415 | return string.sub(s, 1, 4):lower() == 'test' 2416 | end 2417 | 2418 | function M.LuaUnit.collectTests() 2419 | -- return a list of all test names in the global namespace 2420 | -- that match LuaUnit.isTestName 2421 | 2422 | local testNames = {} 2423 | for k, _ in pairs(_G) do 2424 | if type(k) == "string" and M.LuaUnit.isTestName( k ) then 2425 | table.insert( testNames , k ) 2426 | end 2427 | end 2428 | table.sort( testNames ) 2429 | return testNames 2430 | end 2431 | 2432 | function M.LuaUnit.parseCmdLine( cmdLine ) 2433 | -- parse the command line 2434 | -- Supported command line parameters: 2435 | -- --verbose, -v: increase verbosity 2436 | -- --quiet, -q: silence output 2437 | -- --error, -e: treat errors as fatal (quit program) 2438 | -- --output, -o, + name: select output type 2439 | -- --pattern, -p, + pattern: run test matching pattern, may be repeated 2440 | -- --exclude, -x, + pattern: run test not matching pattern, may be repeated 2441 | -- --shuffle, -s, : shuffle tests before reunning them 2442 | -- --name, -n, + fname: name of output file for junit, default to stdout 2443 | -- --repeat, -r, + num: number of times to execute each test 2444 | -- [testnames, ...]: run selected test names 2445 | -- 2446 | -- Returns a table with the following fields: 2447 | -- verbosity: nil, M.VERBOSITY_DEFAULT, M.VERBOSITY_QUIET, M.VERBOSITY_VERBOSE 2448 | -- output: nil, 'tap', 'junit', 'text', 'nil' 2449 | -- testNames: nil or a list of test names to run 2450 | -- exeRepeat: num or 1 2451 | -- pattern: nil or a list of patterns 2452 | -- exclude: nil or a list of patterns 2453 | 2454 | local result, state = {}, nil 2455 | local SET_OUTPUT = 1 2456 | local SET_PATTERN = 2 2457 | local SET_EXCLUDE = 3 2458 | local SET_FNAME = 4 2459 | local SET_REPEAT = 5 2460 | 2461 | if cmdLine == nil then 2462 | return result 2463 | end 2464 | 2465 | local function parseOption( option ) 2466 | if option == '--help' or option == '-h' then 2467 | result['help'] = true 2468 | return 2469 | elseif option == '--version' then 2470 | result['version'] = true 2471 | return 2472 | elseif option == '--verbose' or option == '-v' then 2473 | result['verbosity'] = M.VERBOSITY_VERBOSE 2474 | return 2475 | elseif option == '--quiet' or option == '-q' then 2476 | result['verbosity'] = M.VERBOSITY_QUIET 2477 | return 2478 | elseif option == '--error' or option == '-e' then 2479 | result['quitOnError'] = true 2480 | return 2481 | elseif option == '--failure' or option == '-f' then 2482 | result['quitOnFailure'] = true 2483 | return 2484 | elseif option == '--shuffle' or option == '-s' then 2485 | result['shuffle'] = true 2486 | return 2487 | elseif option == '--output' or option == '-o' then 2488 | state = SET_OUTPUT 2489 | return state 2490 | elseif option == '--name' or option == '-n' then 2491 | state = SET_FNAME 2492 | return state 2493 | elseif option == '--repeat' or option == '-r' then 2494 | state = SET_REPEAT 2495 | return state 2496 | elseif option == '--pattern' or option == '-p' then 2497 | state = SET_PATTERN 2498 | return state 2499 | elseif option == '--exclude' or option == '-x' then 2500 | state = SET_EXCLUDE 2501 | return state 2502 | end 2503 | error('Unknown option: '..option,3) 2504 | end 2505 | 2506 | local function setArg( cmdArg, state ) 2507 | if state == SET_OUTPUT then 2508 | result['output'] = cmdArg 2509 | return 2510 | elseif state == SET_FNAME then 2511 | result['fname'] = cmdArg 2512 | return 2513 | elseif state == SET_REPEAT then 2514 | result['exeRepeat'] = tonumber(cmdArg) 2515 | or error('Malformed -r argument: '..cmdArg) 2516 | return 2517 | elseif state == SET_PATTERN then 2518 | if result['pattern'] then 2519 | table.insert( result['pattern'], cmdArg ) 2520 | else 2521 | result['pattern'] = { cmdArg } 2522 | end 2523 | return 2524 | elseif state == SET_EXCLUDE then 2525 | local notArg = '!'..cmdArg 2526 | if result['pattern'] then 2527 | table.insert( result['pattern'], notArg ) 2528 | else 2529 | result['pattern'] = { notArg } 2530 | end 2531 | return 2532 | end 2533 | error('Unknown parse state: '.. state) 2534 | end 2535 | 2536 | 2537 | for i, cmdArg in ipairs(cmdLine) do 2538 | if state ~= nil then 2539 | setArg( cmdArg, state, result ) 2540 | state = nil 2541 | else 2542 | if cmdArg:sub(1,1) == '-' then 2543 | state = parseOption( cmdArg ) 2544 | else 2545 | if result['testNames'] then 2546 | table.insert( result['testNames'], cmdArg ) 2547 | else 2548 | result['testNames'] = { cmdArg } 2549 | end 2550 | end 2551 | end 2552 | end 2553 | 2554 | if result['help'] then 2555 | M.LuaUnit.help() 2556 | end 2557 | 2558 | if result['version'] then 2559 | M.LuaUnit.version() 2560 | end 2561 | 2562 | if state ~= nil then 2563 | error('Missing argument after '..cmdLine[ #cmdLine ],2 ) 2564 | end 2565 | 2566 | return result 2567 | end 2568 | 2569 | function M.LuaUnit.help() 2570 | print(M.USAGE) 2571 | os.exit(0) 2572 | end 2573 | 2574 | function M.LuaUnit.version() 2575 | print('LuaUnit v'..M.VERSION..' by Philippe Fremy ') 2576 | os.exit(0) 2577 | end 2578 | 2579 | ---------------------------------------------------------------- 2580 | -- class NodeStatus 2581 | ---------------------------------------------------------------- 2582 | 2583 | local NodeStatus = { __class__ = 'NodeStatus' } -- class 2584 | local NodeStatus_MT = { __index = NodeStatus } -- metatable 2585 | M.NodeStatus = NodeStatus 2586 | 2587 | -- values of status 2588 | NodeStatus.SUCCESS = 'SUCCESS' 2589 | NodeStatus.SKIP = 'SKIP' 2590 | NodeStatus.FAIL = 'FAIL' 2591 | NodeStatus.ERROR = 'ERROR' 2592 | 2593 | function NodeStatus.new( number, testName, className ) 2594 | -- default constructor, test are PASS by default 2595 | local t = { number = number, testName = testName, className = className } 2596 | setmetatable( t, NodeStatus_MT ) 2597 | t:success() 2598 | return t 2599 | end 2600 | 2601 | function NodeStatus:success() 2602 | self.status = self.SUCCESS 2603 | -- useless because lua does this for us, but it helps me remembering the relevant field names 2604 | self.msg = nil 2605 | self.stackTrace = nil 2606 | end 2607 | 2608 | function NodeStatus:skip(msg) 2609 | self.status = self.SKIP 2610 | self.msg = msg 2611 | self.stackTrace = nil 2612 | end 2613 | 2614 | function NodeStatus:fail(msg, stackTrace) 2615 | self.status = self.FAIL 2616 | self.msg = msg 2617 | self.stackTrace = stackTrace 2618 | end 2619 | 2620 | function NodeStatus:error(msg, stackTrace) 2621 | self.status = self.ERROR 2622 | self.msg = msg 2623 | self.stackTrace = stackTrace 2624 | end 2625 | 2626 | function NodeStatus:isSuccess() 2627 | return self.status == NodeStatus.SUCCESS 2628 | end 2629 | 2630 | function NodeStatus:isNotSuccess() 2631 | -- Return true if node is either failure or error or skip 2632 | return (self.status == NodeStatus.FAIL or self.status == NodeStatus.ERROR or self.status == NodeStatus.SKIP) 2633 | end 2634 | 2635 | function NodeStatus:isSkipped() 2636 | return self.status == NodeStatus.SKIP 2637 | end 2638 | 2639 | function NodeStatus:isFailure() 2640 | return self.status == NodeStatus.FAIL 2641 | end 2642 | 2643 | function NodeStatus:isError() 2644 | return self.status == NodeStatus.ERROR 2645 | end 2646 | 2647 | function NodeStatus:statusXML() 2648 | if self:isError() then 2649 | return table.concat( 2650 | {' \n', 2651 | ' \n'}) 2653 | elseif self:isFailure() then 2654 | return table.concat( 2655 | {' \n', 2656 | ' \n'}) 2658 | elseif self:isSkipped() then 2659 | return table.concat({' ', xmlEscape(self.msg),'\n' } ) 2660 | end 2661 | return ' \n' -- (not XSD-compliant! normally shouldn't get here) 2662 | end 2663 | 2664 | --------------[[ Output methods ]]------------------------- 2665 | 2666 | local function conditional_plural(number, singular) 2667 | -- returns a grammatically well-formed string "%d " 2668 | local suffix = '' 2669 | if number ~= 1 then -- use plural 2670 | suffix = (singular:sub(-2) == 'ss') and 'es' or 's' 2671 | end 2672 | return string.format('%d %s%s', number, singular, suffix) 2673 | end 2674 | 2675 | function M.LuaUnit.statusLine(result) 2676 | -- return status line string according to results 2677 | local s = { 2678 | string.format('Ran %d tests in %0.3f seconds', 2679 | result.runCount, result.duration), 2680 | conditional_plural(result.successCount, 'success'), 2681 | } 2682 | if result.notSuccessCount > 0 then 2683 | if result.failureCount > 0 then 2684 | table.insert(s, conditional_plural(result.failureCount, 'failure')) 2685 | end 2686 | if result.errorCount > 0 then 2687 | table.insert(s, conditional_plural(result.errorCount, 'error')) 2688 | end 2689 | else 2690 | table.insert(s, '0 failures') 2691 | end 2692 | if result.skippedCount > 0 then 2693 | table.insert(s, string.format("%d skipped", result.skippedCount)) 2694 | end 2695 | if result.nonSelectedCount > 0 then 2696 | table.insert(s, string.format("%d non-selected", result.nonSelectedCount)) 2697 | end 2698 | return table.concat(s, ', ') 2699 | end 2700 | 2701 | function M.LuaUnit:startSuite(selectedCount, nonSelectedCount) 2702 | self.result = { 2703 | selectedCount = selectedCount, 2704 | nonSelectedCount = nonSelectedCount, 2705 | successCount = 0, 2706 | runCount = 0, 2707 | currentTestNumber = 0, 2708 | currentClassName = "", 2709 | currentNode = nil, 2710 | suiteStarted = true, 2711 | startTime = os.clock(), 2712 | startDate = os.date(os.getenv('LUAUNIT_DATEFMT')), 2713 | startIsodate = os.date('%Y-%m-%dT%H:%M:%S'), 2714 | patternIncludeFilter = self.patternIncludeFilter, 2715 | 2716 | -- list of test node status 2717 | allTests = {}, 2718 | failedTests = {}, 2719 | errorTests = {}, 2720 | skippedTests = {}, 2721 | 2722 | failureCount = 0, 2723 | errorCount = 0, 2724 | notSuccessCount = 0, 2725 | skippedCount = 0, 2726 | } 2727 | 2728 | self.outputType = self.outputType or TextOutput 2729 | self.output = self.outputType.new(self) 2730 | self.output:startSuite() 2731 | end 2732 | 2733 | function M.LuaUnit:startClass( className ) 2734 | self.result.currentClassName = className 2735 | self.output:startClass( className ) 2736 | end 2737 | 2738 | function M.LuaUnit:startTest( testName ) 2739 | self.result.currentTestNumber = self.result.currentTestNumber + 1 2740 | self.result.runCount = self.result.runCount + 1 2741 | self.result.currentNode = NodeStatus.new( 2742 | self.result.currentTestNumber, 2743 | testName, 2744 | self.result.currentClassName 2745 | ) 2746 | self.result.currentNode.startTime = os.clock() 2747 | table.insert( self.result.allTests, self.result.currentNode ) 2748 | self.output:startTest( testName ) 2749 | end 2750 | 2751 | function M.LuaUnit:updateStatus( err ) 2752 | -- "err" is expected to be a table / result from protectedCall() 2753 | if err.status == NodeStatus.SUCCESS then 2754 | return 2755 | end 2756 | 2757 | local node = self.result.currentNode 2758 | 2759 | --[[ As a first approach, we will report only one error or one failure for one test. 2760 | 2761 | However, we can have the case where the test is in failure, and the teardown is in error. 2762 | In such case, it's a good idea to report both a failure and an error in the test suite. This is 2763 | what Python unittest does for example. However, it mixes up counts so need to be handled carefully: for 2764 | example, there could be more (failures + errors) count that tests. What happens to the current node ? 2765 | 2766 | We will do this more intelligent version later. 2767 | ]] 2768 | 2769 | -- if the node is already in failure/error, just don't report the new error (see above) 2770 | if node.status ~= NodeStatus.SUCCESS then 2771 | return 2772 | end 2773 | 2774 | if err.status == NodeStatus.FAIL then 2775 | node:fail( err.msg, err.trace ) 2776 | table.insert( self.result.failedTests, node ) 2777 | elseif err.status == NodeStatus.ERROR then 2778 | node:error( err.msg, err.trace ) 2779 | table.insert( self.result.errorTests, node ) 2780 | elseif err.status == NodeStatus.SKIP then 2781 | node:skip( err.msg ) 2782 | table.insert( self.result.skippedTests, node ) 2783 | else 2784 | error('No such status: ' .. prettystr(err.status)) 2785 | end 2786 | 2787 | self.output:updateStatus( node ) 2788 | end 2789 | 2790 | function M.LuaUnit:endTest() 2791 | local node = self.result.currentNode 2792 | -- print( 'endTest() '..prettystr(node)) 2793 | -- print( 'endTest() '..prettystr(node:isNotSuccess())) 2794 | node.duration = os.clock() - node.startTime 2795 | node.startTime = nil 2796 | self.output:endTest( node ) 2797 | 2798 | if node:isSuccess() then 2799 | self.result.successCount = self.result.successCount + 1 2800 | elseif node:isError() then 2801 | if self.quitOnError or self.quitOnFailure then 2802 | -- Runtime error - abort test execution as requested by 2803 | -- "--error" option. This is done by setting a special 2804 | -- flag that gets handled in runSuiteByInstances(). 2805 | print("\nERROR during LuaUnit test execution:\n" .. node.msg) 2806 | self.result.aborted = true 2807 | end 2808 | elseif node:isFailure() then 2809 | if self.quitOnFailure then 2810 | -- Failure - abort test execution as requested by 2811 | -- "--failure" option. This is done by setting a special 2812 | -- flag that gets handled in runSuiteByInstances(). 2813 | print("\nFailure during LuaUnit test execution:\n" .. node.msg) 2814 | self.result.aborted = true 2815 | end 2816 | elseif node:isSkipped() then 2817 | self.result.runCount = self.result.runCount - 1 2818 | else 2819 | error('No such node status: ' .. prettystr(node.status)) 2820 | end 2821 | self.result.currentNode = nil 2822 | end 2823 | 2824 | function M.LuaUnit:endClass() 2825 | self.output:endClass() 2826 | end 2827 | 2828 | function M.LuaUnit:endSuite() 2829 | if self.result.suiteStarted == false then 2830 | error('LuaUnit:endSuite() -- suite was already ended' ) 2831 | end 2832 | self.result.duration = os.clock()-self.result.startTime 2833 | self.result.suiteStarted = false 2834 | 2835 | -- Expose test counts for outputter's endSuite(). This could be managed 2836 | -- internally instead by using the length of the lists of failed tests 2837 | -- but unit tests rely on these fields being present. 2838 | self.result.failureCount = #self.result.failedTests 2839 | self.result.errorCount = #self.result.errorTests 2840 | self.result.notSuccessCount = self.result.failureCount + self.result.errorCount 2841 | self.result.skippedCount = #self.result.skippedTests 2842 | 2843 | self.output:endSuite() 2844 | end 2845 | 2846 | function M.LuaUnit:setOutputType(outputType, fname) 2847 | -- Configures LuaUnit runner output 2848 | -- outputType is one of: NIL, TAP, JUNIT, TEXT 2849 | -- when outputType is junit, the additional argument fname is used to set the name of junit output file 2850 | -- for other formats, fname is ignored 2851 | if outputType:upper() == "NIL" then 2852 | self.outputType = NilOutput 2853 | return 2854 | end 2855 | if outputType:upper() == "TAP" then 2856 | self.outputType = TapOutput 2857 | return 2858 | end 2859 | if outputType:upper() == "JUNIT" then 2860 | self.outputType = JUnitOutput 2861 | if fname then 2862 | self.fname = fname 2863 | end 2864 | return 2865 | end 2866 | if outputType:upper() == "TEXT" then 2867 | self.outputType = TextOutput 2868 | return 2869 | end 2870 | error( 'No such format: '..outputType,2) 2871 | end 2872 | 2873 | --------------[[ Runner ]]----------------- 2874 | 2875 | function M.LuaUnit:protectedCall(classInstance, methodInstance, prettyFuncName) 2876 | -- if classInstance is nil, this is just a function call 2877 | -- else, it's method of a class being called. 2878 | 2879 | local function err_handler(e) 2880 | -- transform error into a table, adding the traceback information 2881 | return { 2882 | status = NodeStatus.ERROR, 2883 | msg = e, 2884 | trace = string.sub(debug.traceback("", 3), 2) 2885 | } 2886 | end 2887 | 2888 | local ok, err 2889 | if classInstance then 2890 | -- stupid Lua < 5.2 does not allow xpcall with arguments so let's use a workaround 2891 | ok, err = xpcall( function () methodInstance(classInstance) end, err_handler ) 2892 | else 2893 | ok, err = xpcall( function () methodInstance() end, err_handler ) 2894 | end 2895 | if ok then 2896 | return {status = NodeStatus.SUCCESS} 2897 | end 2898 | 2899 | local iter_msg 2900 | iter_msg = self.exeRepeat and 'iteration '..self.currentCount 2901 | 2902 | err.msg, err.status = M.adjust_err_msg_with_iter( err.msg, iter_msg ) 2903 | 2904 | if err.status == NodeStatus.SUCCESS or err.status == NodeStatus.SKIP then 2905 | err.trace = nil 2906 | return err 2907 | end 2908 | 2909 | -- reformat / improve the stack trace 2910 | if prettyFuncName then -- we do have the real method name 2911 | err.trace = err.trace:gsub("in (%a+) 'methodInstance'", "in %1 '"..prettyFuncName.."'") 2912 | end 2913 | if STRIP_LUAUNIT_FROM_STACKTRACE then 2914 | err.trace = stripLuaunitTrace(err.trace) 2915 | end 2916 | 2917 | return err -- return the error "object" (table) 2918 | end 2919 | 2920 | 2921 | function M.LuaUnit:execOneFunction(className, methodName, classInstance, methodInstance) 2922 | -- When executing a test function, className and classInstance must be nil 2923 | -- When executing a class method, all parameters must be set 2924 | 2925 | if type(methodInstance) ~= 'function' then 2926 | error( tostring(methodName)..' must be a function, not '..type(methodInstance)) 2927 | end 2928 | 2929 | local prettyFuncName 2930 | if className == nil then 2931 | className = '[TestFunctions]' 2932 | prettyFuncName = methodName 2933 | else 2934 | prettyFuncName = className..'.'..methodName 2935 | end 2936 | 2937 | if self.lastClassName ~= className then 2938 | if self.lastClassName ~= nil then 2939 | self:endClass() 2940 | end 2941 | self:startClass( className ) 2942 | self.lastClassName = className 2943 | end 2944 | 2945 | self:startTest(prettyFuncName) 2946 | 2947 | local node = self.result.currentNode 2948 | for iter_n = 1, self.exeRepeat or 1 do 2949 | if node:isNotSuccess() then 2950 | break 2951 | end 2952 | self.currentCount = iter_n 2953 | 2954 | -- run setUp first (if any) 2955 | if classInstance then 2956 | local func = self.asFunction( classInstance.setUp ) or 2957 | self.asFunction( classInstance.Setup ) or 2958 | self.asFunction( classInstance.setup ) or 2959 | self.asFunction( classInstance.SetUp ) 2960 | if func then 2961 | self:updateStatus(self:protectedCall(classInstance, func, className..'.setUp')) 2962 | end 2963 | end 2964 | 2965 | -- run testMethod() 2966 | if node:isSuccess() then 2967 | self:updateStatus(self:protectedCall(classInstance, methodInstance, prettyFuncName)) 2968 | end 2969 | 2970 | -- lastly, run tearDown (if any) 2971 | if classInstance then 2972 | local func = self.asFunction( classInstance.tearDown ) or 2973 | self.asFunction( classInstance.TearDown ) or 2974 | self.asFunction( classInstance.teardown ) or 2975 | self.asFunction( classInstance.Teardown ) 2976 | if func then 2977 | self:updateStatus(self:protectedCall(classInstance, func, className..'.tearDown')) 2978 | end 2979 | end 2980 | end 2981 | 2982 | self:endTest() 2983 | end 2984 | 2985 | function M.LuaUnit.expandOneClass( result, className, classInstance ) 2986 | --[[ 2987 | Input: a list of { name, instance }, a class name, a class instance 2988 | Ouptut: modify result to add all test method instance in the form: 2989 | { className.methodName, classInstance } 2990 | ]] 2991 | for methodName, methodInstance in sortedPairs(classInstance) do 2992 | if M.LuaUnit.asFunction(methodInstance) and M.LuaUnit.isMethodTestName( methodName ) then 2993 | table.insert( result, { className..'.'..methodName, classInstance } ) 2994 | end 2995 | end 2996 | end 2997 | 2998 | function M.LuaUnit.expandClasses( listOfNameAndInst ) 2999 | --[[ 3000 | -- expand all classes (provided as {className, classInstance}) to a list of {className.methodName, classInstance} 3001 | -- functions and methods remain untouched 3002 | 3003 | Input: a list of { name, instance } 3004 | 3005 | Output: 3006 | * { function name, function instance } : do nothing 3007 | * { class.method name, class instance }: do nothing 3008 | * { class name, class instance } : add all method names in the form of (className.methodName, classInstance) 3009 | ]] 3010 | local result = {} 3011 | 3012 | for i,v in ipairs( listOfNameAndInst ) do 3013 | local name, instance = v[1], v[2] 3014 | if M.LuaUnit.asFunction(instance) then 3015 | table.insert( result, { name, instance } ) 3016 | else 3017 | if type(instance) ~= 'table' then 3018 | error( 'Instance must be a table or a function, not a '..type(instance)..' with value '..prettystr(instance)) 3019 | end 3020 | local className, methodName = M.LuaUnit.splitClassMethod( name ) 3021 | if className then 3022 | local methodInstance = instance[methodName] 3023 | if methodInstance == nil then 3024 | error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) ) 3025 | end 3026 | table.insert( result, { name, instance } ) 3027 | else 3028 | M.LuaUnit.expandOneClass( result, name, instance ) 3029 | end 3030 | end 3031 | end 3032 | 3033 | return result 3034 | end 3035 | 3036 | function M.LuaUnit.applyPatternFilter( patternIncFilter, listOfNameAndInst ) 3037 | local included, excluded = {}, {} 3038 | for i, v in ipairs( listOfNameAndInst ) do 3039 | -- local name, instance = v[1], v[2] 3040 | if patternFilter( patternIncFilter, v[1] ) then 3041 | table.insert( included, v ) 3042 | else 3043 | table.insert( excluded, v ) 3044 | end 3045 | end 3046 | return included, excluded 3047 | end 3048 | 3049 | function M.LuaUnit:runSuiteByInstances( listOfNameAndInst ) 3050 | --[[ Run an explicit list of tests. Each item of the list must be one of: 3051 | * { function name, function instance } 3052 | * { class name, class instance } 3053 | * { class.method name, class instance } 3054 | ]] 3055 | 3056 | local expandedList = self.expandClasses( listOfNameAndInst ) 3057 | if self.shuffle then 3058 | randomizeTable( expandedList ) 3059 | end 3060 | local filteredList, filteredOutList = self.applyPatternFilter( 3061 | self.patternIncludeFilter, expandedList ) 3062 | 3063 | self:startSuite( #filteredList, #filteredOutList ) 3064 | 3065 | for i,v in ipairs( filteredList ) do 3066 | local name, instance = v[1], v[2] 3067 | if M.LuaUnit.asFunction(instance) then 3068 | self:execOneFunction( nil, name, nil, instance ) 3069 | else 3070 | -- expandClasses() should have already taken care of sanitizing the input 3071 | assert( type(instance) == 'table' ) 3072 | local className, methodName = M.LuaUnit.splitClassMethod( name ) 3073 | assert( className ~= nil ) 3074 | local methodInstance = instance[methodName] 3075 | assert(methodInstance ~= nil) 3076 | self:execOneFunction( className, methodName, instance, methodInstance ) 3077 | end 3078 | if self.result.aborted then 3079 | break -- "--error" or "--failure" option triggered 3080 | end 3081 | end 3082 | 3083 | if self.lastClassName ~= nil then 3084 | self:endClass() 3085 | end 3086 | 3087 | self:endSuite() 3088 | 3089 | if self.result.aborted then 3090 | print("LuaUnit ABORTED (as requested by --error or --failure option)") 3091 | os.exit(-2) 3092 | end 3093 | end 3094 | 3095 | function M.LuaUnit:runSuiteByNames( listOfName ) 3096 | --[[ Run LuaUnit with a list of generic names, coming either from command-line or from global 3097 | namespace analysis. Convert the list into a list of (name, valid instances (table or function)) 3098 | and calls runSuiteByInstances. 3099 | ]] 3100 | 3101 | local instanceName, instance 3102 | local listOfNameAndInst = {} 3103 | 3104 | for i,name in ipairs( listOfName ) do 3105 | local className, methodName = M.LuaUnit.splitClassMethod( name ) 3106 | if className then 3107 | instanceName = className 3108 | instance = _G[instanceName] 3109 | 3110 | if instance == nil then 3111 | error( "No such name in global space: "..instanceName ) 3112 | end 3113 | 3114 | if type(instance) ~= 'table' then 3115 | error( 'Instance of '..instanceName..' must be a table, not '..type(instance)) 3116 | end 3117 | 3118 | local methodInstance = instance[methodName] 3119 | if methodInstance == nil then 3120 | error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) ) 3121 | end 3122 | 3123 | else 3124 | -- for functions and classes 3125 | instanceName = name 3126 | instance = _G[instanceName] 3127 | end 3128 | 3129 | if instance == nil then 3130 | error( "No such name in global space: "..instanceName ) 3131 | end 3132 | 3133 | if (type(instance) ~= 'table' and type(instance) ~= 'function') then 3134 | error( 'Name must match a function or a table: '..instanceName ) 3135 | end 3136 | 3137 | table.insert( listOfNameAndInst, { name, instance } ) 3138 | end 3139 | 3140 | self:runSuiteByInstances( listOfNameAndInst ) 3141 | end 3142 | 3143 | function M.LuaUnit.run(...) 3144 | -- Run some specific test classes. 3145 | -- If no arguments are passed, run the class names specified on the 3146 | -- command line. If no class name is specified on the command line 3147 | -- run all classes whose name starts with 'Test' 3148 | -- 3149 | -- If arguments are passed, they must be strings of the class names 3150 | -- that you want to run or generic command line arguments (-o, -p, -v, ...) 3151 | 3152 | local runner = M.LuaUnit.new() 3153 | return runner:runSuite(...) 3154 | end 3155 | 3156 | function M.LuaUnit:runSuite( ... ) 3157 | 3158 | local args = {...} 3159 | if type(args[1]) == 'table' and args[1].__class__ == 'LuaUnit' then 3160 | -- run was called with the syntax M.LuaUnit:runSuite() 3161 | -- we support both M.LuaUnit.run() and M.LuaUnit:run() 3162 | -- strip out the first argument 3163 | table.remove(args,1) 3164 | end 3165 | 3166 | if #args == 0 then 3167 | args = cmdline_argv 3168 | end 3169 | 3170 | local options = pcall_or_abort( M.LuaUnit.parseCmdLine, args ) 3171 | 3172 | -- We expect these option fields to be either `nil` or contain 3173 | -- valid values, so it's safe to always copy them directly. 3174 | self.verbosity = options.verbosity 3175 | self.quitOnError = options.quitOnError 3176 | self.quitOnFailure = options.quitOnFailure 3177 | 3178 | self.exeRepeat = options.exeRepeat 3179 | self.patternIncludeFilter = options.pattern 3180 | self.shuffle = options.shuffle 3181 | 3182 | if options.output then 3183 | if options.output:lower() == 'junit' and options.fname == nil then 3184 | print('With junit output, a filename must be supplied with -n or --name') 3185 | os.exit(-1) 3186 | end 3187 | pcall_or_abort(self.setOutputType, self, options.output, options.fname) 3188 | end 3189 | 3190 | self:runSuiteByNames( options.testNames or M.LuaUnit.collectTests() ) 3191 | 3192 | return self.result.notSuccessCount 3193 | end 3194 | -- class LuaUnit 3195 | 3196 | -- For compatbility with LuaUnit v2 3197 | M.run = M.LuaUnit.run 3198 | M.Run = M.LuaUnit.run 3199 | 3200 | function M:setVerbosity( verbosity ) 3201 | M.LuaUnit.verbosity = verbosity 3202 | end 3203 | M.set_verbosity = M.setVerbosity 3204 | M.SetVerbosity = M.setVerbosity 3205 | 3206 | 3207 | return M 3208 | -------------------------------------------------------------------------------- /rockspecs/lua-fmt-0.1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-fmt" 2 | version = "0.1.0-1" 3 | source = { 4 | url = "https://github.com/starwing/lua-fmt/archive/refs/tags/0.1.0.tar.gz", 5 | dir = "lua-fmt-0.1.0" 6 | } 7 | description = { 8 | summary = "A fmtlib implement for Lua", 9 | detailed = [[ 10 | A simple C module for Lua to format string. 11 | ]], 12 | homepage = "https://github.com/starwing/lua-fmt", 13 | license = "MIT" 14 | } 15 | dependencies = { 16 | "lua >= 5.1" 17 | } 18 | build = { 19 | type = "builtin", 20 | modules = { 21 | fmt = "lfmt.c" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rockspecs/lua-fmt-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-fmt" 2 | version = "scm-1" 3 | 4 | source = { 5 | url = "git://github.com/starwing/lua-fmt.git", 6 | } 7 | 8 | description = { 9 | summary = "A fmtlib implement for Lua", 10 | detailed = [[ 11 | A simple C module for Lua to format string. 12 | ]], 13 | homepage = "https://github.com/starwing/lua-fmt", 14 | license = "MIT", 15 | } 16 | 17 | dependencies = { 18 | "lua >= 5.1" 19 | } 20 | 21 | build = { 22 | type = "builtin", 23 | modules = { 24 | fmt = "lfmt.c"; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | local u = require 'luaunit' 2 | local fmt = require "fmt" 3 | 4 | local eq = u.assertEquals 5 | local fail = u.assertErrorMsgContains 6 | 7 | function _G.test_basic() 8 | eq(fmt("{} {} {}", 1, 2, 3), "1 2 3") 9 | eq(fmt("{1} {1} {1}", 1), "1 1 1") 10 | eq(fmt("{{"), "{") 11 | eq(fmt("}}"), "}") 12 | eq(fmt("{:}", 1), "1") 13 | eq(fmt("{a}", {a = 1}), "1") 14 | eq(fmt("{a[1]}", {a = {1}}), "1") 15 | eq(fmt("{a[a]}", {a = {a = 1}}), "1") 16 | eq(fmt("{a[!]}", {a = {["!"] = 1}}), "1") eq(fmt("{a.1}", {a = {1}}), "1") 17 | eq(fmt("{a.a}", {a = {a = 1}}), "1") 18 | fail("Single '{' encountered in format string", 19 | function() fmt "{" end) 20 | fail("Single '}' encountered in format string", 21 | function() fmt "}" end) 22 | fail("expected '}' before end of string", 23 | --fail("unmatched '{' in format spec", 24 | function() fmt("{:", 1) end) 25 | fail("argument index out of range", 26 | function() fmt("{0", 1) end) 27 | fail("expected '}' before end of string", 28 | function() fmt("{1", 1) end) 29 | fail("Format specifier missing precision", 30 | function() fmt("{:.}", 1) end) 31 | fail("Invalid format specifier: 'dd}'", 32 | function() fmt("{:dd}", 1) end) 33 | fail("unmatched '{' in format spec", 34 | function() fmt("{:dd", 1) end) 35 | fail("integer expected for width, got table", 36 | function() fmt("{:{}}", 1, {}) end) 37 | fail("unexpected ':' in field name", 38 | function() fmt("{:{:}}", 1, {}) end) 39 | fail("unmatched '{' in format spec", 40 | function() fmt("{:#", 1) end) 41 | fail("automatic index out of range", 42 | function() fmt("{} {} {}", 1) end) 43 | fail("unexpected '!' in field name", 44 | function() fmt("{!}", 1) end) 45 | fail("unexpected '!' in field name", 46 | function() fmt("{a.!}", { a = {} }) end) 47 | fail("expected '}' before end of string", 48 | function() fmt("{a[}", { a = {} }) end) 49 | fail("cannot switch from automatic field numbering to manual field specification", 50 | function() fmt("{} {1}", 1) end) 51 | fail("cannot switch from manual field specification to automatic field numbering", 52 | function() fmt("{1} {}", 1) end) 53 | fail("Too many decimal digits in format string", 54 | function() fmt("{:10000000000d}", 1) end) 55 | fail("expected '}' before end of string", 56 | function() fmt("{:{", 1) end) 57 | end 58 | 59 | function _G.test_int() 60 | eq(fmt("{:c}", 12345), '\227\128\185') 61 | eq(fmt("{:c}", 97), 'a') 62 | eq(fmt("{:b}", 97), '1100001') 63 | eq(fmt("{:#b}", 97), '0b1100001') 64 | eq(fmt("{:#B}", 97), '0B1100001') 65 | eq(fmt("{:o}", 97), '141') 66 | eq(fmt("{:#o}", 97), '0o141') 67 | eq(fmt("{:#O}", 97), '0O141') 68 | eq(fmt("{:x}", 97), '61') 69 | eq(fmt("{:#x}", 97), '0x61') 70 | eq(fmt("{:#X}", 97), '0X61') 71 | eq(fmt("{:#X}", -100), '-0X64') 72 | eq(fmt("{:#10X}", -100), ' -0X64') 73 | eq(fmt("{:#010X}", -100), '-0X0000064') 74 | eq(fmt("{:+}", 100), "+100") 75 | eq(fmt("{:+}", -100), "-100") 76 | eq(fmt("{:-}", 100), "100") 77 | eq(fmt("{:-}", -100), "-100") 78 | eq(fmt("{: }", 100), " 100") 79 | eq(fmt("{: }", -100), "-100") 80 | eq(fmt("{:0100_}", -100), "-000_000_000_000_000_000_000_000_000_000_".. 81 | "000_000_000_000_000_000_000_000_000_000_000_000_000_0000100") 82 | eq(fmt("{:0101_}", -100), "-0_000_000_000_000_000_000_000_000_000_000_".. 83 | "000_000_000_000_000_000_000_000_000_000_000_000_000_0000100") 84 | fail("Unknown format code 'z' for object of type 'number'", 85 | function() fmt("{:z}", 1) end) 86 | fail("Sign not allowed with integer format specifier 'c'", 87 | function() fmt("{:+c}", 1) end) 88 | fail("Alternate form (#) not allowed with integer format specifier 'c'", 89 | function() fmt("{:#c}", 1) end) 90 | fail("Zero form (0) not allowed with integer format specifier 'c'", 91 | function() fmt("{:0c}", 1) end) 92 | fail("Cannot specify ',' with 'c'", 93 | function() fmt("{:,c}", 1) end) 94 | fail("'c' arg not in range(2147483647)", 95 | function() fmt("{:c}", -1) end) 96 | end 97 | 98 | function _G.test_flt() 99 | if _VERSION >= "Lua 5.3" then 100 | eq(fmt("{}", 1.0), "1.0") 101 | end 102 | eq(fmt("{}", 1.1), "1.1") 103 | eq(fmt("{:.10f}", 1.0), "1.0000000000") 104 | eq(fmt("{:010.1f}", 1.0), "00000001.0") 105 | eq(fmt("{:%}", 1.0), "100.0%") 106 | fail("precision specifier too large", 107 | function() fmt("{:.100}", 1.1) end) 108 | fail("Grouping form (_) not allowed in float format specifier", 109 | function() fmt("{:_}", 1.1) end) 110 | end 111 | 112 | function _G.test_string() 113 | eq(fmt("{}", "foo"), "foo") 114 | eq(fmt("{:-<10}", "foo"), "-------foo") 115 | eq(fmt("{:->10}", "foo"), "foo-------") 116 | eq(fmt("{:-^10}", "foo"), "---foo----") 117 | eq(fmt("{:<<10}", "foo"), "<<<<<<>10}", "foo"), "foo>>>>>>>") 119 | eq(fmt("{:^^10}", "foo"), "^^^foo^^^^") 120 | eq(fmt("{:s<10000}", ""), ("s"):rep(10000)) 121 | eq(fmt("{:<10000}", ""), (" "):rep(10000)) 122 | eq(fmt("{:{}}", "", 10), (" "):rep(10)) 123 | eq(fmt("{:{}.{}}", "abc", 10, 1), " a") 124 | fail("Unknown format code 'x' for object of type 'string'", 125 | function() fmt("{:x}", "") end) 126 | fail("Sign not allowed in string format specifier", 127 | function() fmt("{:+}", "") end) 128 | fail("Alternate form (#) not allowed in string format specifier", 129 | function() fmt("{:#}", "") end) 130 | fail("Zero form (0) not allowed in string format specifier", 131 | function() fmt("{:0}", "") end) 132 | fail("Grouping form (_) not allowed in string format specifier", 133 | function() fmt("{:_}", "") end) 134 | end 135 | 136 | function _G.test_other() 137 | eq(fmt("{}", nil), "nil") 138 | eq(fmt("{}", true), "true") 139 | eq(fmt("{}", false), "false") 140 | fail("Unknown format code 'p' for object of type 'nil'", 141 | function() fmt("{:p}", nil) end) 142 | local t = {} 143 | local ts = tostring(t):match "table: (%w+)" 144 | eq(fmt("{}", t), tostring(t)) 145 | eq(fmt("{:p}", t), ts) 146 | 147 | local t1 = setmetatable({}, {__tostring = function() return "foo" end}) 148 | local t2 = setmetatable({}, {__tostring = function() return {} end}) 149 | eq(fmt("{}", t1), "foo") 150 | if _VERSION ~= "Lua 5.2" then 151 | fail("'__tostring' must return a string", 152 | function() fmt("{}", t2) end) 153 | end 154 | end 155 | 156 | if _VERSION == "Lua 5.1" and not _G.jit then 157 | u.LuaUnit.run() 158 | else 159 | os.exit(u.LuaUnit.run(), true) 160 | end 161 | 162 | -- cc: run='rm -f *.gcda; time lua test.lua; gcov lfmt.c' 163 | --------------------------------------------------------------------------------