Write more redbean unit tests

- Fix DescribeSigset()
- Introduce new unix.rmrf() API
- Fix redbean sigaction() doc example code
- Fix unix.sigaction() w/ more than two args
- Improve redbean re module API (non-breaking)
- Enhance Lua with Python string multiplication
- Make third parameter of unix.socket() default to 0
This commit is contained in:
Justine Tunney 2022-07-08 23:06:46 -07:00
parent c5b9902ac9
commit 1c83670229
20 changed files with 738 additions and 204 deletions

View file

@ -357,6 +357,9 @@ LUA ENHANCEMENTS
example, you can say `"hello %s" % {"world"}` instead of
`string.format("hello %s", "world")`.
- redbean supports a string multiply operator, like Python. For
example, you can say `"hi" * 2` instead of `string.rep("hi", 2)`.
- redbean supports octal (base 8) integer literals. For example
`0644 == 420` is the case in redbean, whereas in upstream Lua
`0644 == 644` would be the case.
@ -1495,6 +1498,7 @@ CONSTANTS
Logging anything at this level will result in a backtrace and
process exit.
────────────────────────────────────────────────────────────────────────────────
LSQLITE3 MODULE
@ -1531,6 +1535,7 @@ LSQLITE3 MODULE
we provide an APE build of the SQLite shell which you can use to
administrate your redbean database. See the sqlite3.com download above.
────────────────────────────────────────────────────────────────────────────────
RE MODULE
@ -1540,29 +1545,144 @@ RE MODULE
# Example IPv4 Address Regular Expression (see also ParseIP)
p = re.compile([[^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$]])
m,a,b,c,d = p:search(𝑠)
m,a,b,c,d = assert(p:search(𝑠))
if m then
print("ok", tonumber(a), tonumber(b), tonumber(c), tonumber(d))
else
print("not ok")
end
re.search(regex:str,text:str[,flags:int]) → [match[,group_1,...]]
Shortcut for re.compile plus regex_t*:search.
re.search(regex:str, text:str[, flags:int])
├─→ match:str[, group1:str, ...]
└─→ nil, re.Errno
re.compile(regex:str[,flags:int]) → regex_t*
Compiles regular expression, using the POSIX extended syntax. This
has an O(2^𝑛) cost, so it's a good idea to do this from your
/.init.lua file. Flags may contain re.BASIC, re.ICASE, re.NOSUB,
and/or re.NEWLINE. See also regcomp() from libc.
Searches for regular expression match in text.
regex_t*:search(text:str[,flags:int]) → [match[,group_1,...]]
Executes regular expression. This has an O(𝑛) cost. This returns
nothing (nil) if the pattern doesn't match anything. Otherwise it
pushes the matched substring and any parenthesis-captured values
too. Flags may contain re.NOTBOL or re.NOTEOL to indicate whether
or not text should be considered at the start and/or end of a
line.
This is a shorthand notation roughly equivalent to:
preg = re.compile(regex)
patt = preg:search(re, text)
`flags` defaults to zero and may have any of:
- `re.BASIC`
- `re.ICASE`
- `re.NEWLINE`
- `re.NOSUB`
- `re.NOTBOL`
- `re.NOTEOL`
This has exponential complexity. Please use re.compile() to
compile your regular expressions once from `/.init.lua`. This
API exists for convenience. This isn't recommended for prod.
This uses POSIX extended syntax by default.
re.compile(regex:str[, flags:int]) → re.Regex
├─→ preg:re.Regex
└─→ nil, re.Errno
Compiles regular expression.
`flags` defaults to zero and may have any of:
- `re.BASIC`
- `re.ICASE`
- `re.NEWLINE`
- `re.NOSUB`
This has an O(2^𝑛) cost. Consider compiling regular
expressions once from your `/.init.lua` file.
If `regex` is an untrusted user value, then `unix.setrlimit`
should be used to impose cpu and memory quotas for security.
This uses POSIX extended syntax by default.
────────────────────────────────────────────────────────────────────────────────
RE REGEX OBJECT
re.Regex:search(text:str[, flags:int])
├─→ match:str[, group1:str, ...]
└─→ nil, re.Errno
Executes precompiled regular expression.
Returns nothing (nil) if the pattern doesn't match anything.
Otherwise it pushes the matched substring and any
parenthesis-captured values too. Flags may contain re.NOTBOL
or re.NOTEOL to indicate whether or not text should be
considered at the start and/or end of a line.
`flags` defaults to zero and may have any of:
- `re.NOTBOL`
- `re.NOTEOL`
This has an O(𝑛) cost.
────────────────────────────────────────────────────────────────────────────────
RE ERRNO OBJECT
re.Errno:errno()
└─→ errno:int
Returns regex error number.
re.Errno:doc()
└─→ description:str
Returns English string describing error code.
re.Errno:__tostring()
└─→ str
Delegates to re.Errno:doc()
────────────────────────────────────────────────────────────────────────────────
RE ERRORS
re.NOMATCH
No match
re.BADPAT
Invalid regex
re.ECOLLATE
Unknown collating element
re.ECTYPE
Unknown character class name
re.EESCAPE
Trailing backslash
re.ESUBREG
Invalid back reference
re.EBRACK
Missing `]`
re.EPAREN
Missing `)`
re.EBRACE
Missing `}`
re.BADBR
Invalid contents of `{}`
re.ERANGE
Invalid character range.
re.ESPACE
Out of memory
re.BADRPT
Repetition not preceded by valid expression
────────────────────────────────────────────────────────────────────────────────
RE FLAGS
re.BASIC
Use this flag if you prefer the default POSIX regex syntax. We use
@ -1578,27 +1698,29 @@ RE MODULE
may only be used with re.compile and re.search.
re.NEWLINE
Use this flag to change the handling of NEWLINE (\x0a) characters.
When this flag is set, (1) a NEWLINE shall not be matched by a "."
or any form of a non-matching list, (2) a "^" shall match the
zero-length string immediately after a NEWLINE (regardless of
re.NOTBOL), and (3) a "$" shall match the zero-length string
immediately before a NEWLINE (regardless of re.NOTEOL).
Use this flag to change the handling of NEWLINE (\x0a)
characters. When this flag is set, (1) a NEWLINE shall not be
matched by a "." or any form of a non-matching list, (2) a "^"
shall match the zero-length string immediately after a NEWLINE
(regardless of re.NOTBOL), and (3) a "$" shall match the
zero-length string immediately before a NEWLINE (regardless of
re.NOTEOL).
re.NOSUB
Causes re.search to only report success and failure. This is
reported via the API by returning empty string for success. This
flag may only be used with re.compile and re.search.
reported via the API by returning empty string for success.
This flag may only be used with re.compile and re.search.
re.NOTBOL
The first character of the string pointed to by string is not the
beginning of the line. This flag may only be used with re.search
and regex_t*:search.
The first character of the string pointed to by string is not
the beginning of the line. This flag may only be used with
re.search and re.Regex:search.
re.NOTEOL
The last character of the string pointed to by string is not the
end of the line. This flag may only be used with re.search and
regex_t*:search.
The last character of the string pointed to by string is not
the end of the line. This flag may only be used with re.search
and re.Regex:search.
────────────────────────────────────────────────────────────────────────────────
MAXMIND MODULE
@ -1621,6 +1743,7 @@ MAXMIND MODULE
For further details, please see maxmind.lua in redbean-demo.com.
────────────────────────────────────────────────────────────────────────────────
ARGON2 MODULE
@ -1688,6 +1811,7 @@ ARGON2 MODULE
"password")
true
────────────────────────────────────────────────────────────────────────────────
UNIX MODULE
@ -2276,6 +2400,18 @@ UNIX MODULE
thereby assisting with simple absolute filename checks in addition
to enabling one to exceed the traditional 260 character limit.
unix.rmrf(path:str)
├─→ true
└─→ nil, unix.Errno
Recursively removes filesystem path.
Like unix.makedirs() this function isn't actually a system call but
rather is a Libc convenience wrapper. It's intended to be equivalent
to using the UNIX shell's `rm -rf path` command.
`path` is the file or directory path you wish to destroy.
unix.fcntl(fd:int, cmd:int, ...)
├─→ ...
└─→ nil, unix.Errno
@ -2532,7 +2668,7 @@ UNIX MODULE
`whence` can be one of:
- `SEEK_SET`: Sets the file position to `offset`
- `SEEK_SET`: Sets the file position to `offset` [default]
- `SEEK_CUR`: Sets the file position to `position + offset`
- `SEEK_END`: Sets the file position to `filesize + offset`
@ -2580,14 +2716,14 @@ UNIX MODULE
- `SOCK_CLOEXEC`
- `SOCK_NONBLOCK`
`protocol` defaults to `IPPROTO_TCP` for AF_INET` and `0` for
`AF_UNIX`. It can also be:
`protocol` may be any of:
- `IPPROTO_IP`
- `IPPROTO_ICMP`
- `0` to let kernel choose [default]
- `IPPROTO_TCP`
- `IPPROTO_UDP`
- `IPPROTO_RAW`
- `IPPROTO_IP`
- `IPPROTO_ICMP`
unix.socketpair([family:int[, type:int[, protocol:int]]])
├─→ fd1:int, fd2:int
@ -2964,18 +3100,19 @@ UNIX MODULE
Example:
assert(unix.sigaction(unix.SIGUSR1, function(sig)
gotsigusr1 = true
end))
gotsigusr1 = false
assert(unix.raise(unix.SIGUSR1))
ok, err = unix.sigsuspend()
assert(err:errno == unix.EINTR)
if gotsigusr1
print('hooray the signal was delivered')
else
print('oh no some other signal was handled')
function OnSigUsr1(sig)
gotsigusr1 = true
end
gotsigusr1 = false
oldmask = assert(unix.sigprocmask(unix.SIG_BLOCK, unix.Sigset(unix.SIGUSR1)))
assert(unix.sigaction(unix.SIGUSR1, OnSigUsr1))
assert(unix.raise(unix.SIGUSR1))
assert(not gotsigusr1)
ok, err = unix.sigsuspend(oldmask)
assert(not ok)
assert(err:errno() == unix.EINTR)
assert(gotsigusr1)
assert(unix.sigprocmask(unix.SIG_SETMASK, oldmask))
It's a good idea to not do too much work in a signal handler.

View file

@ -405,7 +405,7 @@ int LuaResolveIp(lua_State *L) {
}
static int LuaCheckControlFlags(lua_State *L, int idx) {
int f = luaL_checkinteger(L, idx);
int f = luaL_optinteger(L, idx, 0);
if (f & ~(kControlWs | kControlC0 | kControlC1)) {
luaL_argerror(L, idx, "invalid control flags");
unreachable;

View file

@ -17,13 +17,12 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/macros.internal.h"
#include "libc/x/x.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/regex/regex.h"
static const char kRegCode[][8] = {
"OK", "NOMATCH", "BADPAT", "COLLATE", "ECTYPE", "EESCAPE", "ESUBREG",
"EBRACK", "EPAREN", "EBRACE", "BADBR", "ERANGE", "ESPACE", "BADRPT",
struct ReErrno {
int err;
char doc[64];
};
static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) {
@ -31,40 +30,53 @@ static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) {
lua_setfield(L, -2, k);
}
static int LuaReReturnError(lua_State *L, regex_t *r, int rc) {
struct ReErrno *e;
lua_pushnil(L);
e = lua_newuserdatauv(L, sizeof(struct ReErrno), 0);
luaL_setmetatable(L, "re.Errno");
e->err = rc;
regerror(rc, r, e->doc, sizeof(e->doc));
return 2;
}
static regex_t *LuaReCompileImpl(lua_State *L, const char *p, int f) {
int e;
int rc;
regex_t *r;
r = xmalloc(sizeof(regex_t));
r = lua_newuserdatauv(L, sizeof(regex_t), 0);
luaL_setmetatable(L, "re.Regex");
f &= REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB;
f ^= REG_EXTENDED;
if ((e = regcomp(r, p, f)) != REG_OK) {
free(r);
luaL_error(L, "regcomp(%s) → REG_%s", p,
kRegCode[MAX(0, MIN(ARRAYLEN(kRegCode) - 1, e))]);
unreachable;
if ((rc = regcomp(r, p, f)) == REG_OK) {
return r;
} else {
LuaReReturnError(L, r, rc);
return NULL;
}
return r;
}
static int LuaReSearchImpl(lua_State *L, regex_t *r, const char *s, int f) {
int i, n;
int rc, i, n;
regmatch_t *m;
n = r->re_nsub + 1;
m = xcalloc(n, sizeof(regmatch_t));
if (regexec(r, s, n, m, f >> 8) == REG_OK) {
luaL_Buffer tmp;
n = 1 + r->re_nsub;
m = (regmatch_t *)luaL_buffinitsize(L, &tmp, n * sizeof(regmatch_t));
if ((rc = regexec(r, s, n, m, f >> 8)) == REG_OK) {
for (i = 0; i < n; ++i) {
lua_pushlstring(L, s + m[i].rm_so, m[i].rm_eo - m[i].rm_so);
}
return n;
} else {
n = 0;
return LuaReReturnError(L, r, rc);
}
free(m);
return n;
}
////////////////////////////////////////////////////////////////////////////////
// re
static int LuaReSearch(lua_State *L) {
int f;
regex_t *r;
int i, e, n, f;
const char *p, *s;
p = luaL_checkstring(L, 1);
s = luaL_checkstring(L, 2);
@ -74,56 +86,51 @@ static int LuaReSearch(lua_State *L) {
luaL_argerror(L, 3, "invalid flags");
unreachable;
}
r = LuaReCompileImpl(L, p, f);
n = LuaReSearchImpl(L, r, s, f);
regfree(r);
free(r);
return n;
if ((r = LuaReCompileImpl(L, p, f))) {
return LuaReSearchImpl(L, r, s, f);
} else {
return 2;
}
}
static int LuaReCompile(lua_State *L) {
int f, e;
int f;
regex_t *r;
const char *p;
regex_t *r, **u;
p = luaL_checkstring(L, 1);
f = luaL_optinteger(L, 2, 0);
if (f & ~(REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB)) {
luaL_argerror(L, 3, "invalid flags");
luaL_argerror(L, 2, "invalid flags");
unreachable;
}
r = LuaReCompileImpl(L, p, f);
u = lua_newuserdata(L, sizeof(regex_t *));
luaL_setmetatable(L, "regex_t*");
*u = r;
return 1;
if ((r = LuaReCompileImpl(L, p, f))) {
return 1;
} else {
return 2;
}
}
////////////////////////////////////////////////////////////////////////////////
// re.Regex
static int LuaReRegexSearch(lua_State *L) {
int f;
regex_t **u;
regex_t *r;
const char *s;
u = luaL_checkudata(L, 1, "regex_t*");
r = luaL_checkudata(L, 1, "re.Regex");
s = luaL_checkstring(L, 2);
f = luaL_optinteger(L, 3, 0);
if (!*u) {
luaL_argerror(L, 1, "destroyed");
unreachable;
}
if (f & ~(REG_NOTBOL << 8 | REG_NOTEOL << 8)) {
luaL_argerror(L, 3, "invalid flags");
unreachable;
}
return LuaReSearchImpl(L, *u, s, f);
return LuaReSearchImpl(L, r, s, f);
}
static int LuaReRegexGc(lua_State *L) {
regex_t **u;
u = luaL_checkudata(L, 1, "regex_t*");
if (*u) {
regfree(*u);
free(*u);
*u = NULL;
}
regex_t *r;
r = luaL_checkudata(L, 1, "re.Regex");
regfree(r);
return 0;
}
@ -143,8 +150,8 @@ static const luaL_Reg kLuaReRegexMeta[] = {
{NULL, NULL}, //
};
static void LuaReRegex(lua_State *L) {
luaL_newmetatable(L, "regex_t*");
static void LuaReRegexObj(lua_State *L) {
luaL_newmetatable(L, "re.Regex");
luaL_setfuncs(L, kLuaReRegexMeta, 0);
luaL_newlibtable(L, kLuaReRegexMeth);
luaL_setfuncs(L, kLuaReRegexMeth, 0);
@ -152,14 +159,84 @@ static void LuaReRegex(lua_State *L) {
lua_pop(L, 1);
}
int LuaRe(lua_State *L) {
luaL_newlib(L, kLuaRe);
LuaSetIntField(L, "BASIC", REG_EXTENDED);
LuaSetIntField(L, "ICASE", REG_ICASE);
LuaSetIntField(L, "NEWLINE", REG_NEWLINE);
LuaSetIntField(L, "NOSUB", REG_NOSUB);
LuaSetIntField(L, "NOTBOL", REG_NOTBOL << 8);
LuaSetIntField(L, "NOTEOL", REG_NOTEOL << 8);
LuaReRegex(L);
////////////////////////////////////////////////////////////////////////////////
// re.Errno
static struct ReErrno *GetReErrno(lua_State *L) {
return luaL_checkudata(L, 1, "re.Errno");
}
// re.Errno:errno()
// └─→ errno:int
static int LuaReErrnoErrno(lua_State *L) {
lua_pushinteger(L, GetReErrno(L)->err);
return 1;
}
// re.Errno:doc()
// └─→ description:str
static int LuaReErrnoDoc(lua_State *L) {
lua_pushstring(L, GetReErrno(L)->doc);
return 1;
}
static const luaL_Reg kLuaReErrnoMeth[] = {
{"errno", LuaReErrnoErrno}, //
{"doc", LuaReErrnoDoc}, //
{0}, //
};
static const luaL_Reg kLuaReErrnoMeta[] = {
{"__tostring", LuaReErrnoDoc}, //
{0}, //
};
static void LuaReErrnoObj(lua_State *L) {
luaL_newmetatable(L, "re.Errno");
luaL_setfuncs(L, kLuaReErrnoMeta, 0);
luaL_newlibtable(L, kLuaReErrnoMeth);
luaL_setfuncs(L, kLuaReErrnoMeth, 0);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
}
////////////////////////////////////////////////////////////////////////////////
_Alignas(1) static const struct thatispacked {
const char s[8];
char x;
} kReMagnums[] = {
{"BASIC", REG_EXTENDED}, // compile flag
{"ICASE", REG_ICASE}, // compile flag
{"NEWLINE", REG_NEWLINE}, // compile flag
{"NOSUB", REG_NOSUB}, // compile flag
{"NOMATCH", REG_NOMATCH}, // error
{"BADPAT", REG_BADPAT}, // error
{"ECOLLATE", REG_ECOLLATE}, // error
{"ECTYPE", REG_ECTYPE}, // error
{"EESCAPE", REG_EESCAPE}, // error
{"ESUBREG", REG_ESUBREG}, // error
{"EBRACK", REG_EBRACK}, // error
{"EPAREN", REG_EPAREN}, // error
{"EBRACE", REG_EBRACE}, // error
{"BADBR", REG_BADBR}, // error
{"ERANGE", REG_ERANGE}, // error
{"ESPACE", REG_ESPACE}, // error
{"BADRPT", REG_BADRPT}, // error
};
int LuaRe(lua_State *L) {
int i;
char buf[9];
luaL_newlib(L, kLuaRe);
LuaSetIntField(L, "NOTBOL", REG_NOTBOL << 8); // search flag
LuaSetIntField(L, "NOTEOL", REG_NOTEOL << 8); // search flag
for (i = 0; i < ARRAYLEN(kReMagnums); ++i) {
memcpy(buf, kReMagnums[i].s, 8);
buf[8] = 0;
LuaSetIntField(L, buf, kReMagnums[i].x);
}
LuaReRegexObj(L);
LuaReErrnoObj(L);
return 1;
}

View file

@ -5398,7 +5398,11 @@ static void LuaInit(void) {
lua_State *L = GL;
LuaSetArgv(L);
if (interpretermode) {
exit(LuaInterpreter(L));
int rc = LuaInterpreter(L);
if (IsModeDbg()) {
CheckForMemoryLeaks();
}
exit(rc);
}
if (LuaRunAsset("/.init.lua", true)) {
hasonhttprequest = IsHookDefined("OnHttpRequest");