# HG changeset patch # User David Demelier # Date 1611840058 -3600 # Node ID 371e1cc2c697ebb867040c9d2f775ebbb23d06f0 # Parent 63208f5bb0f652bbb99244b33c4f95f4e3acacf1 tests: add 80% of the Javascript API diff -r 63208f5bb0f6 -r 371e1cc2c697 MIGRATING.md --- a/MIGRATING.md Mon Jan 25 22:56:05 2021 +0100 +++ b/MIGRATING.md Thu Jan 28 14:20:58 2021 +0100 @@ -42,6 +42,8 @@ ### Module File - The method `File.readline` is no longer marked as slow. +- Methods `File.lines`, `File.read`, `File.readline` and `File.seek`, now throw + an exception if the file was closed. ### Module Chrono @@ -51,6 +53,8 @@ ### Module Util - The method `Util.ticks` as been removed. +- The method `Util.cut` now throws a `RangeError` exception if the number of + lines exceed `maxl` argument instead of returning null. ### Module Server diff -r 63208f5bb0f6 -r 371e1cc2c697 irccd/main.c --- a/irccd/main.c Mon Jan 25 22:56:05 2021 +0100 +++ b/irccd/main.c Thu Jan 28 14:20:58 2021 +0100 @@ -28,19 +28,23 @@ #include #include +static int +run(int argc, char **argv) +{ + (void)argc; + + if (strcmp(argv[0], "version") == 0) + puts(IRCCD_VERSION); + + return 0; +} + int main(int argc, char **argv) { - (void)argc; - (void)argv; - - struct irc_server *s; - - irc_bot_init(); + --argc; + ++argv; - s = irc_server_new("mlk", "circ", "circ", "circ", "malikania.fr", 6667); - irc_server_join(s, "#test", NULL); - irc_bot_server_add(s); - irc_bot_plugin_add(irc_js_plugin_open("/Users/markand/test.js")); - irc_bot_run(); + if (argc > 0) + return run(argc, argv); } diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/CMakeLists.txt --- a/lib/CMakeLists.txt Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/CMakeLists.txt Thu Jan 28 14:20:58 2021 +0100 @@ -60,6 +60,8 @@ irccd/js-plugin.h irccd/jsapi-chrono.c irccd/jsapi-chrono.h + irccd/jsapi-directory.c + irccd/jsapi-directory.h irccd/jsapi-file.c irccd/jsapi-file.h irccd/jsapi-irccd.c diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/config.h.in --- a/lib/irccd/config.h.in Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/config.h.in Thu Jan 28 14:20:58 2021 +0100 @@ -19,9 +19,10 @@ #ifndef IRCCD_CONFIG_H #define IRCCD_CONFIG_H -#define IRCCD_VERSION_MAJOR @IRCCD_VERSION_MAJOR@ -#define IRCCD_VERSION_MINOR @IRCCD_VERSION_MINOR@ -#define IRCCD_VERSION_PATCH @IRCCD_VERSION_PATCH@ +#define IRCCD_VERSION_MAJOR @IRCCD_VERSION_MAJOR@ +#define IRCCD_VERSION_MINOR @IRCCD_VERSION_MINOR@ +#define IRCCD_VERSION_PATCH @IRCCD_VERSION_PATCH@ +#define IRCCD_VERSION "@IRCCD_VERSION_MAJOR@.@IRCCD_VERSION_MINOR@.@IRCCD_VERSION_PATCH@" #cmakedefine IRCCD_WITH_JS #cmakedefine IRCCD_WITH_SSL diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/js-plugin.c --- a/lib/irccd/js-plugin.c Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/js-plugin.c Thu Jan 28 14:20:58 2021 +0100 @@ -24,11 +24,11 @@ #include #include -#include - #include "channel.h" #include "event.h" #include "js-plugin.h" +#include "jsapi-chrono.h" +#include "jsapi-directory.h" #include "jsapi-file.h" #include "jsapi-irccd.h" #include "jsapi-logger.h" @@ -44,17 +44,6 @@ #include "server.h" #include "util.h" -struct self { - duk_context *ctx; - char **options; - char **templates; - char **paths; - char *license; - char *version; - char *author; - char *description; -}; - static void freelist(char **table) { @@ -187,7 +176,7 @@ static void set_template(struct irc_plugin *plg, const char *key, const char *value) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; set_key_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_TEMPLATES, key, value); } @@ -195,7 +184,7 @@ static const char * get_template(struct irc_plugin *plg, const char *key) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; return get_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_TEMPLATES, key); } @@ -203,7 +192,7 @@ static const char ** get_templates(struct irc_plugin *plg) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; return get_table(js->ctx, IRC_JSAPI_PLUGIN_PROP_TEMPLATES, &js->templates); } @@ -211,7 +200,7 @@ static void set_path(struct irc_plugin *plg, const char *key, const char *value) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; set_key_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_PATHS, key, value); } @@ -219,7 +208,7 @@ static const char * get_path(struct irc_plugin *plg, const char *key) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; return get_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_PATHS, key); } @@ -227,7 +216,7 @@ static const char ** get_paths(struct irc_plugin *plg) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; return get_table(js->ctx, IRC_JSAPI_PLUGIN_PROP_PATHS, &js->paths); } @@ -235,7 +224,7 @@ static void set_option(struct irc_plugin *plg, const char *key, const char *value) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; set_key_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_OPTIONS, key, value); } @@ -243,7 +232,7 @@ static const char * get_option(struct irc_plugin *plg, const char *key) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; return get_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_OPTIONS, key); } @@ -251,7 +240,7 @@ static const char ** get_options(struct irc_plugin *plg) { - struct self *js = plg->data; + struct irc_js_plugin_data *js = plg->data; return get_table(js->ctx, IRC_JSAPI_PLUGIN_PROP_OPTIONS, &js->options); } @@ -259,7 +248,7 @@ static void vcall(struct irc_plugin *plg, const char *function, const char *fmt, va_list ap) { - struct self *self = plg->data; + struct irc_js_plugin_data *self = plg->data; int nargs = 0; duk_get_global_string(self->ctx, function); @@ -430,11 +419,13 @@ static bool init(struct irc_plugin *plg, const char *script) { - struct self js = {0}; + struct irc_js_plugin_data js = {0}; /* Load all modules. */ js.ctx = duk_create_heap(wrap_malloc, wrap_realloc, wrap_free, NULL, NULL); irc_jsapi_load(js.ctx); + irc_jsapi_chrono_load(js.ctx); + irc_jsapi_directory_load(js.ctx); irc_jsapi_file_load(js.ctx); irc_jsapi_logger_load(js.ctx); irc_jsapi_plugin_load(js.ctx, plg); @@ -480,7 +471,7 @@ static void finish(struct irc_plugin *plg) { - struct self *self = plg->data; + struct irc_js_plugin_data *self = plg->data; if (self->ctx) duk_destroy_heap(self->ctx); diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/js-plugin.h --- a/lib/irccd/js-plugin.h Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/js-plugin.h Thu Jan 28 14:20:58 2021 +0100 @@ -21,8 +21,21 @@ #include +#include + struct irc_plugin; +struct irc_js_plugin_data { + duk_context *ctx; + char **options; + char **templates; + char **paths; + char *license; + char *version; + char *author; + char *description; +}; + struct irc_plugin * irc_js_plugin_open(const char *); diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/jsapi-chrono.c --- a/lib/irccd/jsapi-chrono.c Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/jsapi-chrono.c Thu Jan 28 14:20:58 2021 +0100 @@ -73,18 +73,18 @@ { struct timer *timer; - timer = irc_util_calloc(1, sizeof (*self)); + timer = irc_util_calloc(1, sizeof (*timer)); timespec_get(&timer->start, TIME_UTC); duk_push_this(ctx); duk_push_pointer(ctx, timer); duk_put_prop_string(ctx, -2, SIGNATURE); - duk_pop(ctx); /* this.elapsed property. */ duk_push_string(ctx, "elapsed"); duk_push_c_function(ctx, Chrono_prototype_elapsed, 0); duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_GETTER); + duk_pop(ctx); return 0; } @@ -106,7 +106,7 @@ }; void -irc_js_chrono_load(duk_context *ctx) +irc_jsapi_chrono_load(duk_context *ctx) { duk_get_global_string(ctx, "Irccd"); duk_push_c_function(ctx, Chrono_constructor, 0); diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/jsapi-directory.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/jsapi-directory.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,425 @@ +/* + * jsapi-directory.c -- Irccd.Directory API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +#endif + +#include + +#include "jsapi-system.h" + +enum { + LIST_DOT = (1 << 0), + LIST_DOT_DOT = (1 << 1) +}; + +struct cursor { + char path[PATH_MAX]; + char entry[FILENAME_MAX]; + bool recursive; + void *data; + bool (*fn)(const struct cursor *); +}; + +struct finder { + union { + const char *pattern; + regex_t regex; + }; + struct cursor curs; + void (*finish)(struct finder *); +}; + +static int +recursedir(int dirfd, struct cursor *cs) +{ + DIR *dp; + struct dirent *entry; + struct stat st; + size_t entrylen; + int childfd, ret = 0; + + if (!(dp = fdopendir(dirfd))) + return -1; + + while ((entry = readdir(dp))) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + if (fstatat(dirfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + continue; + + entrylen = strlen(entry->d_name); + + /* + * Append full path for the given entry. + * e.g. /foo/bar/ -> /foo/bar/quux.txt + */ + strlcat(cs->path, entry->d_name, sizeof (cs->path)); + + /* Go recursively if it's a directory and activated. */ + if (S_ISDIR(st.st_mode) && cs->recursive) { + strlcat(cs->path, "/", sizeof (cs->path)); + + entrylen += 1; + + if ((childfd = openat(dirfd, entry->d_name, O_RDONLY | O_DIRECTORY)) < 0) + continue; + if ((ret = recursedir(childfd, cs))) { + close(childfd); + goto stop; + } + + close(childfd); + } + + strlcpy(cs->entry, entry->d_name, sizeof (cs->entry)); + + if (cs->fn(cs)) { + ret = 1; + goto stop; + } + + cs->path[strlen(cs->path) - entrylen] = '\0'; + } +stop: + closedir(dp); + + return ret; +} + +static int +recurse(const char *path, struct cursor *cs) +{ + int fd, ret; + size_t pathlen; + + if ((fd = open(path, O_RDONLY | O_DIRECTORY)) < 0) + return -1; + + pathlen = strlen(path); + + if (strlcpy(cs->path, path, sizeof (cs->path)) >= sizeof (cs->path)) + return errno = ENOMEM, -1; + if (cs->path[pathlen - 1] != '/' && strlcat(cs->path, "/", sizeof (cs->path)) >= sizeof (cs->path)) + return errno = ENOMEM, -1; + + ret = recursedir(fd, cs); + close(fd); + + return ret; +} + +static inline const char * +path(duk_context *ctx) +{ + const char *ret; + + duk_push_this(ctx); + duk_get_prop_string(ctx, -1, "path"); + + if (duk_get_type(ctx, -1) != DUK_TYPE_STRING) + duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Directory object"); + + ret = duk_get_string(ctx, -1); + + if (!ret || !ret[0]) + duk_error(ctx, DUK_ERR_TYPE_ERROR, "directory object has empty path"); + + duk_pop_n(ctx, 2); + + return ret; +} + +static bool +find_regex(const struct cursor *curs) +{ + const struct finder *fd = curs->data; + + return regexec(&fd->regex, curs->entry, 0, NULL, 0) == 0; +} + +static bool +find_name(const struct cursor *curs) +{ + const struct finder *fd = curs->data; + + return strcmp(curs->entry, fd->pattern) == 0; +} + +static void +find_regex_finish(struct finder *fd) +{ + regfree(&fd->regex); +} + +static int +find_helper(duk_context *ctx, const char *base, bool recursive, int pattern_index) +{ + struct finder finder = { + .curs = { + .recursive = recursive, + .data = &finder, + } + }; + int status; + + if (duk_is_string(ctx, pattern_index)) { + finder.pattern = duk_get_string(ctx, pattern_index); + finder.curs.fn = find_name; + } else { + /* Check if it's a RegExp object. */ + duk_get_global_string(ctx, "RegExp"); + + if (!duk_instanceof(ctx, pattern_index, -1)) + /* TODO: better error. */ + return duk_error(ctx, DUK_ERR_TYPE_ERROR, "pattern arg error"); + + duk_get_prop_string(ctx, pattern_index, "source"); + + if (regcomp(&finder.regex, duk_to_string(ctx, -1), REG_EXTENDED) != 0) + return duk_error(ctx, DUK_ERR_ERROR, "RegExp error"); + + finder.curs.fn = find_regex; + finder.finish = find_regex_finish; + duk_pop_n(ctx, 2); + } + + status = recurse(base, &finder.curs); + + if (finder.finish) + finder.finish(&finder); + + if (status == 1) + duk_push_string(ctx, finder.curs.path); + else + duk_push_null(ctx); + + return 1; +} + +static bool +rm(const struct cursor *curs) +{ + return remove(curs->path), false; +} + +static int +rm_helper(duk_context *ctx, const char *base, bool recursive) +{ + struct stat st; + struct cursor curs = { + .recursive = true, + .fn = rm + }; + + if (stat(base, &st) < 0) + return irc_jsapi_system_raise(ctx), 0; + else if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + return irc_jsapi_system_raise(ctx), 0; + } + + if (recursive) + recurse(base, &curs); + + remove(base); + + return 0; +} + +static inline void +mkpath(duk_context *ctx, const char *path) +{ +#ifdef _WIN32 + /* TODO: convert code to errno. */ + if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { + errno = EPERM; + irc_jsapi_system_raise(ctx); +#else + if (mkdir(path, 0755) < 0 && errno != EEXIST) + irc_jsapi_system_raise(ctx); +#endif +} + +static inline char * +normalize(char *str) +{ + for (char *p = str; *p; ++p) + if (*p == '\\') + *p = '/'; + + return str; +} + +static int +Directory_prototype_find(duk_context *ctx) +{ + return find_helper(ctx, path(ctx), duk_opt_boolean(ctx, 1, false), 0); +} + +static int +Directory_prototype_remove(duk_context *ctx) +{ + return rm_helper(ctx, path(ctx), duk_opt_boolean(ctx, 0, false)); +} + +static int +Directory_constructor(duk_context *ctx) +{ + const char *path = duk_require_string(ctx, 0); + const int flags = duk_opt_int(ctx, 1, 0); + DIR *dp; + struct dirent *entry; + + if (!duk_is_constructor_call(ctx)) + return 0; + + duk_push_this(ctx); + + /* this.entries property. */ + duk_push_string(ctx, "entries"); + duk_push_array(ctx); + + if (!(dp = opendir(path))) + irc_jsapi_system_raise(ctx); + + for (int i = 0; (entry = readdir(dp)); ) { + if (strcmp(entry->d_name, ".") == 0 && !(flags & LIST_DOT)) + continue; + if (strcmp(entry->d_name, "..") == 0 && !(flags & LIST_DOT_DOT)) + continue; + + duk_push_object(ctx); + duk_push_string(ctx, entry->d_name); + duk_put_prop_string(ctx, -2, "name"); + duk_push_int(ctx, entry->d_type); + duk_put_prop_string(ctx, -2, "type"); + duk_put_prop_index(ctx, -2, i++); + } + + closedir(dp); + duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE); + + /* this.path property. */ + duk_push_string(ctx, "path"); + duk_push_string(ctx, path); + duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE); + duk_pop(ctx); + + return 0; +} + +static duk_ret_t +Directory_find(duk_context *ctx) +{ + const char *path = duk_require_string(ctx, 0); + bool recursive = duk_opt_boolean(ctx, 2, false); + + return find_helper(ctx, path, recursive, 1); +} + +static duk_ret_t +Directory_remove(duk_context* ctx) +{ + return rm_helper(ctx, duk_require_string(ctx, 0), duk_opt_boolean(ctx, 1, false)); +} + +static duk_ret_t +Directory_mkdir(duk_context* ctx) +{ + char path[PATH_MAX], *p; + + /* Copy the directory to normalize and iterate over '/'. */ + strlcpy(path, duk_require_string(ctx, 0), sizeof (path)); + normalize(path); + +#if defined(_WIN32) + /* Remove drive letter that we don't need. */ + if ((p = strchr(path, ':'))) + ++p; + else + p = path; +#else + p = path; +#endif + + for (p = p + 1; *p; ++p) { + if (*p == '/') { + *p = 0; + mkpath(ctx, path); + *p = '/'; + } + } + + mkpath(ctx, path); + + return 0; +} + +static const duk_function_list_entry methods[] = { + { "find", Directory_prototype_find, DUK_VARARGS }, + { "remove", Directory_prototype_remove, 1 }, + { NULL, NULL, 0 } +}; + +static const duk_function_list_entry functions[] = { + { "find", Directory_find, DUK_VARARGS }, + { "mkdir", Directory_mkdir, DUK_VARARGS }, + { "remove", Directory_remove, DUK_VARARGS }, + { NULL, NULL, 0 } +}; + +static const duk_number_list_entry constants[] = { + { "Dot", LIST_DOT }, + { "DotDot", LIST_DOT_DOT }, + { "TypeFile", DT_REG }, + { "TypeDir", DT_DIR }, + { "TypeLink", DT_LNK }, + { "TypeBlock", DT_BLK }, + { "TypeCharacter", DT_CHR }, + { "TypeFifo", DT_FIFO }, + { "TypeSocket", DT_SOCK }, + { "TypeUnknown", DT_UNKNOWN }, + { NULL, 0 } +}; + +void +irc_jsapi_directory_load(duk_context *ctx) +{ + assert(ctx); + + duk_get_global_string(ctx, "Irccd"); + duk_push_c_function(ctx, Directory_constructor, 2); + duk_put_number_list(ctx, -1, constants); + duk_put_function_list(ctx, -1, functions); + duk_push_object(ctx); + duk_put_function_list(ctx, -1, methods); + duk_put_prop_string(ctx, -2, "prototype"); + duk_put_prop_string(ctx, -2, "Directory"); + duk_pop(ctx); +} diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/jsapi-directory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/jsapi-directory.h Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,27 @@ +/* + * jsapi-directory.h -- Irccd.Directory API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_JSAPI_DIRECTORY_H +#define IRCCD_JSAPI_DIRECTORY_H + +#include + +void +irc_jsapi_directory_load(duk_context *); + +#endif /* !IRCCD_JSAPI_DIRECTORY_H */ diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/jsapi-file.c --- a/lib/irccd/jsapi-file.c Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/jsapi-file.c Thu Jan 28 14:20:58 2021 +0100 @@ -257,8 +257,12 @@ irc_jsapi_system_raise(ctx); } - if (getline(&line, &linesz, file->fp) < 0 || ferror(file->fp)) { + if (getline(&line, &linesz, file->fp) < 0) { free(line); + + if (feof(file->fp)) + return 0; + irc_jsapi_system_raise(ctx); } @@ -312,7 +316,7 @@ long position; if (!file->fp || (position = ftell(file->fp)) < 0) - irc_jsapi_system_raise(ctx); + return irc_jsapi_system_raise(ctx), 0; duk_push_number(ctx, position); diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/jsapi-irccd.c --- a/lib/irccd/jsapi-irccd.c Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/jsapi-irccd.c Thu Jan 28 14:20:58 2021 +0100 @@ -24,7 +24,7 @@ #include "config.h" #include "util.h" -static duk_ret_t +static int SystemError_constructor(duk_context *ctx) { duk_push_this(ctx); @@ -283,6 +283,14 @@ /* }}} */ +static int +print(duk_context *ctx) +{ + puts(duk_require_string(ctx, 0)); + + return 0; +} + void irc_jsapi_load(duk_context *ctx) { @@ -318,4 +326,8 @@ /* Set Irccd as global. */ duk_put_global_string(ctx, "Irccd"); + + /* Convenient global "print" function. */ + duk_push_c_function(ctx, print, 1); + duk_put_global_string(ctx, "print"); } diff -r 63208f5bb0f6 -r 371e1cc2c697 lib/irccd/jsapi-util.c --- a/lib/irccd/jsapi-util.c Mon Jan 25 22:56:05 2021 +0100 +++ b/lib/irccd/jsapi-util.c Thu Jan 28 14:20:58 2021 +0100 @@ -159,18 +159,20 @@ } static int -limit(duk_context *ctx, duk_idx_t index, const char *name, int value) +limit(duk_context *ctx, duk_idx_t index, const char *name, size_t value) { + int newvalue; + if (duk_get_top(ctx) < index || !duk_is_number(ctx, index)) return value; - value = duk_to_int(ctx, index); + newvalue = duk_to_int(ctx, index); - if (value <= 0) + if (newvalue <= 0) (void)duk_error(ctx, DUK_ERR_RANGE_ERROR, "argument %d (%s) must be positive", index, name); - return value; + return newvalue; } static char * @@ -178,7 +180,7 @@ { FILE *fp; char *out = NULL; - size_t outsz = 0, linesz = 0, tokensz; + size_t outsz = 0, linesz = 0, tokensz, lineavail = maxl; struct string *token; if (!(fp = open_memstream(&out, &outsz))) @@ -187,10 +189,10 @@ TAILQ_FOREACH(token, tokens, link) { tokensz = strlen(token->value); - if (tokensz >= maxc) { + if (tokensz > maxc) { fclose(fp); duk_push_error_object(ctx, DUK_ERR_RANGE_ERROR, - "token '%s' could not fit in maxc (%zu)", token, maxc); + "token '%s' could not fit in maxc limit (%zu)", token->value, maxc); return NULL; } @@ -207,15 +209,14 @@ * a "new" one. */ if (linesz + tokensz > maxc) { - if (maxl == 0) { + if (--lineavail == 0) { fclose(fp); - duk_push_error_object(ctx, "lines exceeds maxl (%zu)", maxl); + duk_push_error_object(ctx, DUK_ERR_RANGE_ERROR, "number of lines exceeds maxl (%zu)", maxl); return NULL; } fputc('\n', fp); linesz = 0; - maxl -= 1; } linesz += fprintf(fp, "%s%s", linesz > 0 ? " " : "", token->value); diff -r 63208f5bb0f6 -r 371e1cc2c697 man/irccd-api-util.3 --- a/man/irccd-api-util.3 Mon Jan 25 22:56:05 2021 +0100 +++ b/man/irccd-api-util.3 Thu Jan 28 14:20:58 2021 +0100 @@ -42,19 +42,11 @@ The argument .Fa maxc controls the maximum of characters allowed per line, it can be a positive -integer. If undefined is given, a default of 72 is used. -.Pp -The argument +integer. If undefined is given, a default of 72 is used. The argument .Fa maxl -controls the maximum of lines allowed. It can be a positive integer or undefined -for an infinite list. -.Pp -If -.Fa maxl -is used as a limit and the data can not fit within the bounds, -undefined is returned. -.Pp -An empty list may be returned if empty strings were found. +controls the maximum of lines allowed. It can be a positive integer or +undefined for an infinite list. An empty list may be returned if empty strings +were found. .Pp .\" Irccd.Util.format The @@ -118,7 +110,10 @@ are negative numbers. .It Bq Er RangeError If one word length was bigger than -.Fa maxc . +.Fa maxc +or the number of lines would exceed +.Fa maxl +argument. .It Bq Er TypeError If .Fa data diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/CMakeLists.txt --- a/tests/CMakeLists.txt Mon Jan 25 22:56:05 2021 +0100 +++ b/tests/CMakeLists.txt Thu Jan 28 14:20:58 2021 +0100 @@ -30,9 +30,31 @@ test-util ) +if (IRCCD_WITH_JS) + list( + APPEND TESTS + test-jsapi-chrono + test-jsapi-directory + test-jsapi-file + test-jsapi-irccd + test-jsapi-system + test-jsapi-timer + test-jsapi-unicode + test-jsapi-util + ) +endif () + foreach (t ${TESTS}) add_executable(${t} ${t}.c) add_test(${t} ${t}) target_link_libraries(${t} libirccd libirccd-greatest) set_target_properties(${t} PROPERTIES FOLDER "tests") + target_compile_definitions( + ${t} + PRIVATE + IRCCD_EXECUTABLE="$" + BINARY="${tests_BINARY_DIR}" + SOURCE="${tests_SOURCE_DIR}" + ) + add_dependencies(${t} irccd) endforeach () diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/data/example-dl-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/example-dl-plugin.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,160 @@ +/* + * example-dl-plugin.c -- simple plugin for unit tests + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +struct kw { + const char *key; + char value[256]; +}; + +/* + * Options. + */ +static struct kw options[] = { + { "option-1", "value-1" } +}; + +static const char *options_list[] = { + "option-1", + NULL +}; + +/* + * Templates. + */ +static struct kw templates[] = { + { "template-1", "Welcome #{target}" } +}; + +static const char *templates_list[] = { + "template-1", + NULL +}; + +/* + * Paths. + */ +static struct kw paths[] = { + { "path-1", "/usr/local/etc" } +}; + +static const char *paths_list[] = { + "path-1", + NULL +}; + +static void +set(struct kw *table, size_t tablesz, const char *key, const char *value) +{ + for (size_t i = 0; i < tablesz; ++i) { + if (strcmp(table[i].key, key) == 0) { + strlcpy(table[i].value, value, sizeof (table[i].value)); + break; + } + } +} + +static const char * +get(const struct kw *table, size_t tablesz, const char *key) +{ + for (size_t i = 0; i < tablesz; ++i) + if (strcmp(table[i].key, key) == 0) + return table[i].value; + + return NULL; +} + +IRC_DL_EXPORT void +example_dl_plugin_set_option(const char *key, const char *value) +{ + set(options, IRC_UTIL_SIZE(options), key, value); +} + +IRC_DL_EXPORT const char * +example_dl_plugin_get_option(const char *key) +{ + return get(options, IRC_UTIL_SIZE(options), key); +} + +IRC_DL_EXPORT const char ** +example_dl_plugin_get_options(void) +{ + return options_list; +} + +IRC_DL_EXPORT void +example_dl_plugin_set_template(const char *key, const char *value) +{ + set(templates, IRC_UTIL_SIZE(templates), key, value); +} + +IRC_DL_EXPORT const char * +example_dl_plugin_get_template(const char *key) +{ + return get(templates, IRC_UTIL_SIZE(templates), key); +} + +IRC_DL_EXPORT const char ** +example_dl_plugin_get_templates(void) +{ + return templates_list; +} + +IRC_DL_EXPORT void +example_dl_plugin_set_path(const char *key, const char *value) +{ + set(paths, IRC_UTIL_SIZE(paths), key, value); +} + +IRC_DL_EXPORT const char * +example_dl_plugin_get_path(const char *key) +{ + return get(paths, IRC_UTIL_SIZE(paths), key); +} + +IRC_DL_EXPORT const char ** +example_dl_plugin_get_paths(void) +{ + return paths_list; +} + +IRC_DL_EXPORT void +example_dl_plugin_event(const struct irc_event *ev) +{ + (void)ev; +} + +IRC_DL_EXPORT void +example_dl_plugin_load(void) +{ +} + +IRC_DL_EXPORT void +example_dl_plugin_reload(void) +{ +} + +IRC_DL_EXPORT void +example_dl_plugin_unload(void) +{ +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/data/example-plugin.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/example-plugin.js Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,1 @@ +/* Just a simple file. */ diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/data/root/file-1.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/root/file-1.txt Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,1 @@ +file-1.txt diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/data/root/level-1/level-2/file-2.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/root/level-1/level-2/file-2.txt Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,1 @@ +file-2.txt diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/data/root/lines.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/root/lines.txt Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,3 @@ +a +b +c diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/data/timer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/timer.js Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,13 @@ +count = 0; + +function onLoad() +{ + if (typeof (type) === "undefined") + throw Error("global timer type not defined"); + + t = new Irccd.Timer(type, 50, function () { + count += 1; + }); + + t.start(); +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/example-dl-plugin.c --- a/tests/example-dl-plugin.c Mon Jan 25 22:56:05 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -/* - * example-dl-plugin.c -- simple plugin for unit tests - * - * Copyright (c) 2013-2021 David Demelier - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include -#include - -struct kw { - const char *key; - char value[256]; -}; - -/* - * Options. - */ -static struct kw options[] = { - { "option-1", "value-1" } -}; - -static const char *options_list[] = { - "option-1", - NULL -}; - -/* - * Templates. - */ -static struct kw templates[] = { - { "template-1", "Welcome #{target}" } -}; - -static const char *templates_list[] = { - "template-1", - NULL -}; - -/* - * Paths. - */ -static struct kw paths[] = { - { "path-1", "/usr/local/etc" } -}; - -static const char *paths_list[] = { - "path-1", - NULL -}; - -static void -set(struct kw *table, size_t tablesz, const char *key, const char *value) -{ - for (size_t i = 0; i < tablesz; ++i) { - if (strcmp(table[i].key, key) == 0) { - strlcpy(table[i].value, value, sizeof (table[i].value)); - break; - } - } -} - -static const char * -get(const struct kw *table, size_t tablesz, const char *key) -{ - for (size_t i = 0; i < tablesz; ++i) - if (strcmp(table[i].key, key) == 0) - return table[i].value; - - return NULL; -} - -IRC_DL_EXPORT void -example_dl_plugin_set_option(const char *key, const char *value) -{ - set(options, IRC_UTIL_SIZE(options), key, value); -} - -IRC_DL_EXPORT const char * -example_dl_plugin_get_option(const char *key) -{ - return get(options, IRC_UTIL_SIZE(options), key); -} - -IRC_DL_EXPORT const char ** -example_dl_plugin_get_options(void) -{ - return options_list; -} - -IRC_DL_EXPORT void -example_dl_plugin_set_template(const char *key, const char *value) -{ - set(templates, IRC_UTIL_SIZE(templates), key, value); -} - -IRC_DL_EXPORT const char * -example_dl_plugin_get_template(const char *key) -{ - return get(templates, IRC_UTIL_SIZE(templates), key); -} - -IRC_DL_EXPORT const char ** -example_dl_plugin_get_templates(void) -{ - return templates_list; -} - -IRC_DL_EXPORT void -example_dl_plugin_set_path(const char *key, const char *value) -{ - set(paths, IRC_UTIL_SIZE(paths), key, value); -} - -IRC_DL_EXPORT const char * -example_dl_plugin_get_path(const char *key) -{ - return get(paths, IRC_UTIL_SIZE(paths), key); -} - -IRC_DL_EXPORT const char ** -example_dl_plugin_get_paths(void) -{ - return paths_list; -} - -IRC_DL_EXPORT void -example_dl_plugin_event(const struct irc_event *ev) -{ - (void)ev; -} - -IRC_DL_EXPORT void -example_dl_plugin_load(void) -{ -} - -IRC_DL_EXPORT void -example_dl_plugin_reload(void) -{ -} - -IRC_DL_EXPORT void -example_dl_plugin_unload(void) -{ -} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-chrono.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-chrono.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,112 @@ +/* + * test-jsapi-chrono.c -- test Irccd.Chrono API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#define GREATEST_USE_ABBREVS 0 +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +GREATEST_TEST +basics_simple(void) +{ + if (duk_peval_string(data->ctx, "timer = new Irccd.Chrono();") != 0) + GREATEST_FAIL(); + + sleep(1); + + if (duk_peval_string(data->ctx, "result = timer.elapsed;") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "result"); + + GREATEST_ASSERT_IN_RANGE(1000U, duk_get_uint(data->ctx, -1), 100); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_reset(void) +{ + /* + * Create a timer and wait for it to accumulate some time. Then use + * start to reset its value and wait for 1s. The elapsed time must not + * be greater than 1s. + */ + if (duk_peval_string(data->ctx, "timer = new Irccd.Chrono()") != 0) + GREATEST_FAIL(); + + sleep(1); + + if (duk_peval_string(data->ctx, "timer.reset();") != 0) + GREATEST_FAIL(); + + sleep(1); + + if (duk_peval_string(data->ctx, "result = timer.elapsed") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "result"); + + GREATEST_ASSERT_IN_RANGE(1000U, duk_get_uint(data->ctx, -1), 100); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_simple); + GREATEST_RUN_TEST(basics_reset); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-directory.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-directory.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,257 @@ +/* + * test-jsapi-directory.c -- test Irccd.Directory API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#define GREATEST_USE_ABBREVS 0 +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; + + duk_push_string(data->ctx, SOURCE); + duk_put_global_string(data->ctx, "SOURCE"); + + duk_push_string(data->ctx, BINARY); + duk_put_global_string(data->ctx, "BINARY"); +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +GREATEST_TEST +object_constructor(void) +{ + const char *script = + "d = new Irccd.Directory(SOURCE + '/data/root');" + "p = d.path;" + "l = d.entries.length;"; + + if (duk_peval_string(data->ctx, script) != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "l"); + GREATEST_ASSERT_EQ(3U, duk_get_uint(data->ctx, -1)); + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT(duk_is_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_find(void) +{ + const char *script = "d = new Irccd.Directory(SOURCE + '/data/root');"; + + if (duk_peval_string(data->ctx, script) != 0) + GREATEST_FAIL(); + + /* Find "lines.txt" not recursively. */ + if (duk_peval_string(data->ctx, "p = d.find('lines.txt');") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT_STR_EQ(SOURCE "/data/root/lines.txt", duk_get_string(data->ctx, -1)); + + /* Find "unknown.txt" not recursively (not found). */ + if (duk_peval_string(data->ctx, "p = d.find('unknown.txt');") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT(duk_is_null(data->ctx, -1)); + + /* Find "file-2.txt" not recursively (exists but in sub directory). */ + if (duk_peval_string(data->ctx, "p = d.find('file-2.txt');") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT(duk_is_null(data->ctx, -1)); + + /* Find "file-2.txt" recursively. */ + if (duk_peval_string(data->ctx, "p = d.find('file-2.txt', true);") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT_STR_EQ(SOURCE "/data/root/level-1/level-2/file-2.txt", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_remove(void) +{ + struct stat st; + + /* First create an empty directory. */ + mkdir(BINARY "/empty", 0700); + + if (duk_peval_string(data->ctx, "d = new Irccd.Directory(BINARY + '/empty')") != 0) + GREATEST_FAIL(); + + /* Not recursive. */ + if (duk_peval_string(data->ctx, "d.remove()") != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT_EQ(-1, stat(BINARY "/empty", &st)); + + mkdir(BINARY "/notempty", 0700); + mkdir(BINARY "/notempty/empty", 0700); + + if (duk_peval_string(data->ctx, "d = new Irccd.Directory(BINARY + '/notempty')") != 0) + GREATEST_FAIL(); + + /* Not recursive. */ + if (duk_peval_string(data->ctx, "d.remove(true)") != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT_EQ(-1, stat(BINARY "/notempty", &st)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_object) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(object_constructor); + GREATEST_RUN_TEST(object_find); + GREATEST_RUN_TEST(object_remove); +} + +GREATEST_TEST +free_find(void) +{ + /* Find "lines.txt" not recursively. */ + if (duk_peval_string(data->ctx, "p = Irccd.Directory.find(SOURCE + '/data/root', 'lines.txt');") != 0) { + puts(duk_to_string(data->ctx, -1)); + GREATEST_FAIL(); + } + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT_STR_EQ(SOURCE "/data/root/lines.txt", duk_get_string(data->ctx, -1)); + + /* Find "unknown.txt" not recursively (not found). */ + if (duk_peval_string(data->ctx, "p = Irccd.Directory.find(SOURCE + '/data/root', 'unknown.txt');") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT(duk_is_null(data->ctx, -1)); + + /* Find "file-2.txt" not recursively (exists but in sub directory). */ + if (duk_peval_string(data->ctx, "p = Irccd.Directory.find(SOURCE + '/data/root', 'file-2.txt');") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT(duk_is_null(data->ctx, -1)); + + /* Find "file-2.txt" recursively. */ + if (duk_peval_string(data->ctx, "p = Irccd.Directory.find(SOURCE + '/data/root', 'file-2.txt', true);") != 0) + GREATEST_FAIL(); + + duk_get_global_string(data->ctx, "p"); + GREATEST_ASSERT_STR_EQ(SOURCE "/data/root/level-1/level-2/file-2.txt", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +free_remove(void) +{ + struct stat st; + + /* First create an empty directory. */ + mkdir(BINARY "/empty", 0700); + + /* Not recursive. */ + if (duk_peval_string(data->ctx, "Irccd.Directory.remove(BINARY + '/empty')") != 0) { + puts(duk_to_string(data->ctx, -1)); + GREATEST_FAIL(); + } + + GREATEST_ASSERT_EQ(-1, stat(BINARY "/empty", &st)); + + mkdir(BINARY "/notempty", 0700); + mkdir(BINARY "/notempty/empty", 0700); + + /* Not recursive. */ + if (duk_peval_string(data->ctx, "Irccd.Directory.remove(BINARY + '/notempty', true)") != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT_EQ(-1, stat(BINARY "/notempty", &st)); + + GREATEST_PASS(); +} + +GREATEST_TEST +free_mkdir(void) +{ + struct stat st; + + remove(BINARY "/1/2"); + remove(BINARY "/1"); + + if (duk_peval_string(data->ctx, "Irccd.Directory.mkdir(BINARY + '/1/2')") != 0) { + puts(duk_to_string(data->ctx, -1)); + GREATEST_FAIL(); + } + + GREATEST_ASSERT_EQ(0, stat(BINARY "/1/2", &st)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_free) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(free_find); + GREATEST_RUN_TEST(free_remove); + GREATEST_RUN_TEST(free_mkdir); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_object); + GREATEST_RUN_SUITE(suite_free); + GREATEST_MAIN_END(); + + return 0; +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-file.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-file.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,452 @@ +/* + * test-jsapi-file.c -- test Irccd.File API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#define GREATEST_USE_ABBREVS 0 +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; + + duk_push_string(data->ctx, SOURCE); + duk_put_global_string(data->ctx, "SOURCE"); + + duk_push_string(data->ctx, BINARY); + duk_put_global_string(data->ctx, "BINARY"); +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +GREATEST_TEST +free_basename(void) +{ + if (duk_peval_string(data->ctx, "result = Irccd.File.basename('/usr/local/etc/irccd.conf');")) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("irccd.conf", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +free_dirname(void) +{ + if (duk_peval_string(data->ctx, "result = Irccd.File.dirname('/usr/local/etc/irccd.conf');")) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("/usr/local/etc", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +free_exists(void) +{ + if (duk_peval_string(data->ctx, "result = Irccd.File.exists(SOURCE + '/data/root/file-1.txt')")) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +free_exists2(void) +{ + if (duk_peval_string(data->ctx, "result = Irccd.File.exists('file_which_does_not_exist.txt')")) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(!duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +free_remove(void) +{ + FILE *fp; + struct stat st; + + if (!(fp = fopen(BINARY "/test.bin", "w"))) + GREATEST_FAIL(); + + fclose(fp); + + if (duk_peval_string(data->ctx, "Irccd.File.remove(BINARY + '/test.bin')") != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(stat(BINARY "/test.bin", &st) < 0); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_free) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(free_basename); + GREATEST_RUN_TEST(free_dirname); + GREATEST_RUN_TEST(free_exists); + GREATEST_RUN_TEST(free_exists2); + GREATEST_RUN_TEST(free_remove); +} + +GREATEST_TEST +object_basename(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "result = f.basename();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("file-1.txt", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_basename_closed(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "f.close();" + "result = f.basename();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("file-1.txt", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_dirname(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "result = f.dirname();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ(SOURCE "/data/root", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_dirname_closed(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "f.close();" + "result = f.dirname();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ(SOURCE "/data/root", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_lines(void) +{ + const int ret = duk_peval_string(data->ctx, + "result = new Irccd.File(SOURCE + '/data/root/lines.txt', 'r').lines();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_EQ(3, duk_get_length(data->ctx, -1)); + GREATEST_ASSERT(duk_get_prop_index(data->ctx, -1, 0)); + GREATEST_ASSERT_STR_EQ("a", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_prop_index(data->ctx, -2, 1)); + GREATEST_ASSERT_STR_EQ("b", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_prop_index(data->ctx, -3, 2)); + GREATEST_ASSERT_STR_EQ("c", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_lines_closed(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {" + " f = new Irccd.File(SOURCE + '/data/root/lines.txt', 'r');" + " f.close();" + " f.lines();" + "} catch (e) {" + " name = e.name;" + "}" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("SystemError", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_seek1(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "f.seek(Irccd.File.SeekSet, 6);" + "result = f.read(1);" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ(".", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_seek2(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "f.seek(Irccd.File.SeekSet, 2);" + "f.seek(Irccd.File.SeekCur, 4);" + "result = f.read(1);" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ(".", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_seek3(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "f.seek(Irccd.File.SeekEnd, -2);" + "result = f.read(1);" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("t", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_seek_closed(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {" + " f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + " f.close();" + " f.seek(Irccd.File.SeekEnd, -2);" + "} catch (e) {" + " name = e.name" + "}" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("SystemError", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_read(void) +{ + const int ret = duk_peval_string(data->ctx, + "f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + "result = f.read();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("file-1.txt\n", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_read_closed(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {" + " f = new Irccd.File(SOURCE + '/data/root/file-1.txt', 'r');" + " f.close();" + " f.read();" + "} catch (e) {" + " name = e.name;" + "}" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("SystemError", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_readline(void) +{ + const int ret = duk_peval_string(data->ctx, + "result = [];" + "f = new Irccd.File(SOURCE + '/data/root/lines.txt', 'r');" + "for (var s; s = f.readline(); ) {" + " result.push(s);" + "}" + ); + + if (ret != 0) { + puts(duk_to_string(data->ctx, -1)); + GREATEST_FAIL(); + } + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_EQ(3, duk_get_length(data->ctx, -1)); + GREATEST_ASSERT(duk_get_prop_index(data->ctx, -1, 0)); + GREATEST_ASSERT_STR_EQ("a", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_prop_index(data->ctx, -2, 1)); + GREATEST_ASSERT_STR_EQ("b", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_prop_index(data->ctx, -3, 2)); + GREATEST_ASSERT_STR_EQ("c", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +object_readline_closed(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {" + " result = [];" + " f = new Irccd.File(SOURCE + '/data/root/lines.txt', 'r');" + " f.close();" + " for (var s; s = f.readline(); ) {" + " result.push(s);" + " }" + "} catch (e) {" + " name = e.name;" + "}\n" + + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_EQ(0, duk_get_length(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("SystemError", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_object) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(object_basename); + GREATEST_RUN_TEST(object_basename_closed); + GREATEST_RUN_TEST(object_dirname); + GREATEST_RUN_TEST(object_dirname_closed); + GREATEST_RUN_TEST(object_lines); + GREATEST_RUN_TEST(object_lines_closed); + GREATEST_RUN_TEST(object_seek1); + GREATEST_RUN_TEST(object_seek2); + GREATEST_RUN_TEST(object_seek3); + GREATEST_RUN_TEST(object_seek_closed); + GREATEST_RUN_TEST(object_read); + GREATEST_RUN_TEST(object_read_closed); + GREATEST_RUN_TEST(object_readline); + GREATEST_RUN_TEST(object_readline_closed); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_free); + GREATEST_RUN_SUITE(suite_object); + GREATEST_MAIN_END(); + + return 0; +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-irccd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-irccd.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,167 @@ +/* + * test-jsapi-irccd.c -- test Irccd API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#define GREATEST_USE_ABBREVS 0 +#include + +#include + +#include +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +static int +throw(duk_context *ctx) +{ + errno = EINVAL; + irc_jsapi_system_raise(ctx); + + return 0; +} + +GREATEST_TEST +basics_version(void) +{ + const int ret = duk_peval_string(data->ctx, + "major = Irccd.version.major;" + "minor = Irccd.version.minor;" + "patch = Irccd.version.patch;" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "major")); + GREATEST_ASSERT_EQ(IRCCD_VERSION_MAJOR, duk_get_int(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "minor")); + GREATEST_ASSERT_EQ(IRCCD_VERSION_MINOR, duk_get_int(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "patch")); + GREATEST_ASSERT_EQ(IRCCD_VERSION_PATCH, duk_get_int(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_system_error_from_js(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {" + " throw new Irccd.SystemError(1, 'test');" + "} catch (e) {" + " errno = e.errno;" + " name = e.name;" + " message = e.message;" + " v1 = (e instanceof Error);" + " v2 = (e instanceof Irccd.SystemError);" + "}" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "errno")); + GREATEST_ASSERT_EQ(1, duk_get_int(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("SystemError", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "message")); + GREATEST_ASSERT_STR_EQ("test", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "v1")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "v2")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_system_error_from_c(void) +{ + duk_push_c_function(data->ctx, throw, 0); + duk_put_global_string(data->ctx, "f"); + + const int ret = duk_peval_string(data->ctx, + "try {" + " f();" + "} catch (e) {" + " errno = e.errno;" + " name = e.name;" + " v1 = (e instanceof Error);" + " v2 = (e instanceof Irccd.SystemError);" + "}" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "errno")); + GREATEST_ASSERT_EQ(EINVAL, duk_get_int(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("SystemError", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "v1")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "v2")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_version); + GREATEST_RUN_TEST(basics_system_error_from_js); + GREATEST_RUN_TEST(basics_system_error_from_c); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-system.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-system.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,121 @@ +/* + * test-jsapi-system.c -- test Irccd.System API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define GREATEST_USE_ABBREVS 0 +#include + +// TODO: irccd/ +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +GREATEST_TEST +basics_popen(void) +{ + int ret = duk_peval_string(data->ctx, + "f = Irccd.System.popen(\"" IRCCD_EXECUTABLE " version\", \"r\");" + "r = f.readline();" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "r")); + GREATEST_ASSERT_STR_EQ(IRCCD_VERSION, duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_sleep(void) +{ + time_t start, now; + + start = time(NULL); + + if (duk_peval_string(data->ctx, "Irccd.System.sleep(2)") != 0) + GREATEST_FAIL(); + + now = time(NULL); + + GREATEST_ASSERT_IN_RANGE(2000LL, difftime(now, start) * 1000LL, 100LL); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_usleep(void) +{ + time_t start, now; + + start = time(NULL); + + if (duk_peval_string(data->ctx, "Irccd.System.usleep(2000000)") != 0) + GREATEST_FAIL(); + + now = time(NULL); + + GREATEST_ASSERT_IN_RANGE(2000LL, difftime(now, start) * 1000LL, 100LL); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_popen); + GREATEST_RUN_TEST(basics_sleep); + GREATEST_RUN_TEST(basics_usleep); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-timer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-timer.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,104 @@ +/* + * test-jsapi-timer.c -- test Irccd.System API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* TODO: We need proper bot function to dispatch */ + +#if 0 + +#define GREATEST_USE_ABBREVS 0 +#include + +// TODO: irccd/ +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/timer.js"); + data = plugin->data; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +static void +set_type(const char *name) +{ + duk_get_global_string(data->ctx, "Irccd"); + duk_get_prop_string(data->ctx, -1, "Timer"); + duk_get_prop_string(data->ctx, -1, name.c_str()); + duk_put_global_string(data->ctx, "type"); + duk_pop_n(data->ctx, 2); + + plugin_->open(); + plugin_->handle_load(bot_); +} + +BOOST_AUTO_TEST_CASE(single) +{ + boost::timer::cpu_timer timer; + + set_type("Single"); + + while (timer.elapsed().wall / 1000000LL < 3000) { + ctx_.reset(); + ctx_.poll(); + } + + BOOST_TEST(duk_get_global_string(data->ctx, "count")); + BOOST_TEST(duk_get_int(data->ctx, -1) == 1); +} + +BOOST_AUTO_TEST_CASE(repeat) +{ + boost::timer::cpu_timer timer; + + set_type("Repeat"); + + while (timer.elapsed().wall / 1000000LL < 3000) { + ctx_.reset(); + ctx_.poll(); + } + + BOOST_TEST(duk_get_global_string(data->ctx, "count")); + BOOST_TEST(duk_get_int(data->ctx, -1) >= 5); +} + +#endif + +int +main(void) +{ + +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-unicode.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-unicode.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,116 @@ +/* + * test-jsapi-unicode.c -- test Irccd.Unicode API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * /!\ Be sure that this file is kept saved in UTF-8 /!\ + */ + +#define GREATEST_USE_ABBREVS 0 +#include + +// TODO: irccd/ +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +GREATEST_TEST +basics_is_letter(void) +{ + duk_peval_string_noresult(data->ctx, "result = Irccd.Unicode.isLetter(String('é').charCodeAt(0));"); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + + duk_peval_string_noresult(data->ctx, "result = Irccd.Unicode.isLetter(String('€').charCodeAt(0));"); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(!duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_is_lower(void) +{ + duk_peval_string_noresult(data->ctx, "result = Irccd.Unicode.isLower(String('é').charCodeAt(0));"); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + + duk_peval_string_noresult(data->ctx, "result = Irccd.Unicode.isLower(String('É').charCodeAt(0));"); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(!duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_is_upper(void) +{ + duk_peval_string_noresult(data->ctx, "result = Irccd.Unicode.isUpper(String('É').charCodeAt(0));"); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(duk_get_boolean(data->ctx, -1)); + + duk_peval_string_noresult(data->ctx, "result = Irccd.Unicode.isUpper(String('é').charCodeAt(0));"); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT(!duk_get_boolean(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_is_letter); + GREATEST_RUN_TEST(basics_is_lower); + GREATEST_RUN_TEST(basics_is_upper); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +} diff -r 63208f5bb0f6 -r 371e1cc2c697 tests/test-jsapi-util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-jsapi-util.c Thu Jan 28 14:20:58 2021 +0100 @@ -0,0 +1,355 @@ +/* + * test-jsapi-util.c -- test Irccd.Util API + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define GREATEST_USE_ABBREVS 0 +#include + +#include +#include + +static struct irc_plugin *plugin; +static struct irc_js_plugin_data *data; + +static void +setup(void *udata) +{ + (void)udata; + + plugin = irc_js_plugin_open(SOURCE "/data/example-plugin.js"); + data = plugin->data; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + + plugin = NULL; + data = NULL; +} + +GREATEST_TEST +basics_splituser(void) +{ + if (duk_peval_string(data->ctx, "result = Irccd.Util.splituser(\"user!~user@hyper/super/host\");") != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("user", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_splithost(void) +{ + if (duk_peval_string(data->ctx, "result = Irccd.Util.splithost(\"user!~user@hyper/super/host\");") != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("hyper/super/host", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_splituser); + GREATEST_RUN_TEST(basics_splithost); +} + +GREATEST_TEST +format_simple(void) +{ + const int ret = duk_peval_string(data->ctx, + "result = Irccd.Util.format(\"#{target}\", { target: \"markand\" })" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "result")); + GREATEST_ASSERT_STR_EQ("markand", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_format) +{ + GREATEST_RUN_TEST(format_simple); +} + +GREATEST_TEST +cut_string_simple(void) +{ + const int ret = duk_peval_string(data->ctx, + "lines = Irccd.Util.cut('hello world');\n" + "line0 = lines[0];\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line0")); + GREATEST_ASSERT_STR_EQ("hello world", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_string_double(void) +{ + const int ret = duk_peval_string(data->ctx, + "lines = Irccd.Util.cut('hello world', 5);\n" + "line0 = lines[0];\n" + "line1 = lines[1];\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line0")); + GREATEST_ASSERT_STR_EQ("hello", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line1")); + GREATEST_ASSERT_STR_EQ("world", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_string_dirty(void) +{ + const int ret = duk_peval_string(data->ctx, + "lines = Irccd.Util.cut(' hello world ', 5);\n" + "line0 = lines[0];\n" + "line1 = lines[1];\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line0")); + GREATEST_ASSERT_STR_EQ("hello", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line1")); + GREATEST_ASSERT_STR_EQ("world", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_string_too_much_lines(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {" + " lines = Irccd.Util.cut('abc def ghi jkl', 3, 3);" + "} catch (e) {\n" + " name = e.name;\n" + " message = e.message;\n" + "}\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("RangeError", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "message")); + GREATEST_ASSERT_STR_EQ("number of lines exceeds maxl (3)", duk_get_string(data->ctx, -1)); + GREATEST_PASS(); +} + +GREATEST_TEST +cut_string_token_too_big(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {\n" + " lines = Irccd.Util.cut('hello world', 3);\n" + "} catch (e) {\n" + " name = e.name;\n" + " message = e.message;\n" + "}\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("RangeError", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "message")); + GREATEST_ASSERT_STR_EQ("token 'hello' could not fit in maxc limit (3)", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_string_negative_maxc(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {\n" + " lines = Irccd.Util.cut('hello world', -3);\n" + "} catch (e) {\n" + " name = e.name;\n" + " message = e.message;\n" + "}\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("RangeError", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "message")); + GREATEST_ASSERT_STR_EQ("argument 1 (maxc) must be positive", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_string_negative_maxl(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {\n" + " lines = Irccd.Util.cut('hello world', undefined, -1);\n" + "} catch (e) {\n" + " name = e.name;\n" + " message = e.message;\n" + "}\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("RangeError", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "message")); + GREATEST_ASSERT_STR_EQ("argument 2 (maxl) must be positive", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_array_simple(void) +{ + const int ret = duk_peval_string(data->ctx, + "lines = Irccd.Util.cut([ 'hello', 'world' ]);\n" + "line0 = lines[0];\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line0")); + GREATEST_ASSERT_STR_EQ("hello world", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_array_double(void) +{ + const int ret = duk_peval_string(data->ctx, + "lines = Irccd.Util.cut([ 'hello', 'world' ], 5);\n" + "line0 = lines[0];\n" + "line1 = lines[1];\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line0")); + GREATEST_ASSERT_STR_EQ("hello", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line1")); + GREATEST_ASSERT_STR_EQ("world", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_array_dirty(void) +{ + const int ret = duk_peval_string(data->ctx, + "lines = Irccd.Util.cut([ ' ', ' hello ', ' world ', ' '], 5);\n" + "line0 = lines[0];\n" + "line1 = lines[1];\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line0")); + GREATEST_ASSERT_STR_EQ("hello", duk_get_string(data->ctx, -1)); + GREATEST_ASSERT(duk_get_global_string(data->ctx, "line1")); + GREATEST_ASSERT_STR_EQ("world", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +cut_invalid_data(void) +{ + const int ret = duk_peval_string(data->ctx, + "try {\n" + " lines = Irccd.Util.cut(123);\n" + "} catch (e) {\n" + " name = e.name;\n" + " message = e.message;\n" + "}\n" + ); + + if (ret != 0) + GREATEST_FAIL(); + + GREATEST_ASSERT(duk_get_global_string(data->ctx, "name")); + GREATEST_ASSERT_STR_EQ("TypeError", duk_get_string(data->ctx, -1)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_cut) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(cut_string_simple); + GREATEST_RUN_TEST(cut_string_double); + GREATEST_RUN_TEST(cut_string_dirty); + GREATEST_RUN_TEST(cut_string_too_much_lines); + GREATEST_RUN_TEST(cut_string_token_too_big); + GREATEST_RUN_TEST(cut_string_negative_maxc); + GREATEST_RUN_TEST(cut_string_negative_maxl); + GREATEST_RUN_TEST(cut_array_simple); + GREATEST_RUN_TEST(cut_array_double); + GREATEST_RUN_TEST(cut_array_dirty); + GREATEST_RUN_TEST(cut_invalid_data); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_RUN_SUITE(suite_cut); + GREATEST_MAIN_END(); + + return 0; +}