Start some internal testing frameworking.

This commit is contained in:
Sage Vaillancourt 2022-03-21 12:33:35 -04:00 committed by Sage Vaillancourt
parent 3bf65577c0
commit 097cbf6a5c
13 changed files with 245 additions and 74 deletions

View File

@ -12,7 +12,7 @@ mkfile_dir := $(dir $(mkfile_path))
GCC_COM ?= gcc -g -O0 -Wall -o $(exe) -D WEBSERVER -D STANDALONE -DSCRIPTDIR=\"$(SCRIPTDIR)\"
all:
$(GCC_COM) $(files) $(libs) && echo && ./tests.sh
$(GCC_COM) $(files) $(libs) && echo && ./tests.sh && ./pl --run-tests
notest:
$(GCC_COM) $(file_libs)

View File

@ -52,6 +52,7 @@ struct Environment envForLambda(const Object* params, const Object* arg_forms,
}
struct Environment env = {
.name = "lambdaEnv",
.outer = outer,
.strings = NULL,
.objects = NULL,
@ -240,17 +241,24 @@ void setGlobal(struct Environment* env)
struct helpText {
const char* symbol;
const char* help;
const char*const* tests;
};
int currentHelp = 0;
struct helpText helpTexts[100];
#define pf(_name, _func) buildHelp(_name, &(_func), _func ## Doc)
#define pf(_name, _func) buildFuncSym(_name, &(_func), _func ## Doc, NULL)
#define pfn(_func) buildFuncSym(_func ## Symbol, &(_func), _func ## Doc, NULL)
#define pft(_name, _func) buildFuncSym(_name, &(_func), _func ## Doc, _func ## Tests)
struct symFunc buildHelp(const char* symbol, Object (* func)(Object, Object, struct Environment*), const char* help)
int helpInitialized = 0;
struct symFunc buildFuncSym(const char* symbol, Object (* func)(Object, Object, struct Environment*), const char* help, const char*const* tests)
{
helpTexts[currentHelp].help = help;
helpTexts[currentHelp].symbol = symbol;
currentHelp += 1;
if (!helpInitialized) {
helpTexts[currentHelp].help = help;
helpTexts[currentHelp].symbol = symbol;
helpTexts[currentHelp].tests = tests;
currentHelp += 1;
}
return (struct symFunc) {
.func = func,
@ -269,6 +277,54 @@ const char* getHelp(const char* symbol)
return "Help not found!";
}
fnn(segfault, "seg", "Induces a segfault.")
(Object ignore1, Object ignore2, struct Environment* env)
{
int* p = NULL;
return numberObject(*p);
}
// Returns number of failures
int runTests()
{
printf("Running tests...\n");
int failureCount = 0;
int passCount = 0;
for (int hi = 0; hi < currentHelp; hi++) {
struct helpText h = helpTexts[hi];
if (h.tests) {
int ti = 0;
char result[1024];
while (h.tests[ti]) {
const char* test = h.tests[ti];
const char* expected = h.tests[ti + 1];
struct Environment env = defaultEnv();
Object o = parseEval(test, &env);
stringObj(result, &o);
cleanObject(&o);
if (strcmp(result, expected) != 0) {
failureCount++;
printf("Test failed!\n");
printf("%s\n", test);
printf("Expected '%s' but received '%s'\n", expected, result);
} else {
passCount++;
}
ti += 2;
}
}
}
if (passCount > 0) {
printf("");
}
printf("%d tests passed!\n", passCount);
if (failureCount > 0) {
printf("");
}
printf("%d tests failed!\n", failureCount);
return failureCount;
}
struct Environment defaultEnv()
{
char** strings = calloc(sizeof(char*), MAX_ENV_ELM);
@ -276,6 +332,7 @@ struct Environment defaultEnv()
char size = MAX_ENV_ELM;
struct Environment e = {
.name = "defaultEnv",
.outer = NULL,
.strings = strings,
.objects = objects,
@ -297,8 +354,8 @@ struct Environment defaultEnv()
{"<", &lth},
{"&", &and},
{"|", &or},
pf("cat", catObjects),
pf("fil", filter),
pft("cat", catObjects),
pft("fil", filter),
pf("len", len),
pf("ap", append),
pf("pre", prepend),
@ -312,22 +369,23 @@ struct Environment defaultEnv()
pf("isnum", isNum),
pf("islist", isList),
pf("isstr", isString),
{"iserr", &isErr},
pf("iserr", isErr),
pf("char", charVal),
{"eval", &parseEvalO},
{"poss", &possessive},
pf("eval", parseEvalO),
pf("poss", possessive),
#ifdef WEBSERVER
pf("get", addGetRoute),
pf("post", addPostRoute),
pf("serve", startServer),
#endif
#ifdef STANDALONE
{"prn", &print},
{"pch", &pChar},
{"penv", &printEnvO},
//{"sys", &systemCall},
{"loadfile", &loadFile},
{"inp", &takeInput},
pfn(segfault),
pf("prn", print),
pf("pch", pChar),
pf("penv", printEnvO),
pf("sys", systemCall),
pf("loadfile", loadFile),
pf("inp", takeInput),
pf("?", help)
#endif
};
@ -336,6 +394,7 @@ struct Environment defaultEnv()
addFunc(symFuncs[i].sym, symFuncs[i].func, &e);
}
helpInitialized = 1;
return e;
}

View File

@ -15,6 +15,7 @@ struct Environment {
struct StructDef* structDefs;
int refs;
const char* name;
};
struct Environment* global();
@ -44,4 +45,6 @@ int getStructIndex(struct Environment* env, const char* name);
const char* getHelp(const char* symbol);
int runTests();
#endif

View File

@ -36,12 +36,13 @@ and the interpreter won't even instantly crash over it! It's truly astounding
stuff, when you think about it."
))
(def homepage (fn () (html (
(def homepage (fn (req) (html (
(head (
(link ((rel "stylesheet") (href "styles.css")))
))
(body (
(h1 "This is a sweet PebbLisp blog")
(h1 "This is a sweet PebbLisp site")
(p (cat "" req))
(htmlize p1)
(htmlize p2)))
))))

View File

@ -1,4 +1,5 @@
#include "object.h"
#include "pebblisp.h"
#include "env.h"
#include <stdio.h>
@ -476,11 +477,16 @@ void _printObj(const Object* obj, int newline)
printObj(&obj->lambda->body);
return;
}
char temp[200] = "";
stringObj(temp, obj);
if (newline) {
printf("%s\n", temp);
if (obj->type == TYPE_ERROR) {
if (obj->error && obj->error->plContext) {
printf("%s\n", obj->error->plContext->text);
return;
}
}
} else {
printf("%s", temp);
}
@ -922,6 +928,7 @@ inline Object errorObject(enum errorCode err)
o.error = malloc(sizeof(struct Error));
o.error->code = err;
o.error->context = NULL;
o.error->plContext = NULL;
#endif
return o;

View File

@ -85,11 +85,11 @@ typedef struct Object Object;
struct Lambda;
struct Environment;
struct Slice;
struct Other;
struct Error {
enum errorCode code;
char* context;
struct Slice* plContext;
};
struct Object {

View File

@ -1,3 +1,9 @@
#ifdef STANDALONE
#define _GNU_SOURCE
#include <signal.h>
#include <ucontext.h>
#endif
#include "pebblisp.h"
#include <stdlib.h>
@ -590,6 +596,7 @@ Result parseAtom(struct Slice* s)
}
}
struct Slice* lastOpen = NULL;
Object parseEval(const char* input, struct Environment* env)
{
struct Error err = noError();
@ -623,6 +630,7 @@ Object parseEval(const char* input, struct Environment* env)
struct Slice* tok = tokens;
while (tok[i].text != NULL) {
if (tok[i].text[0] == '(') {
lastOpen = &tok[i];
parens++;
} else if (tok[i].text[0] == ')') {
parens--;
@ -633,6 +641,7 @@ Object parseEval(const char* input, struct Environment* env)
Object parsed = parse(tok).obj;
if (parsed.type == TYPE_ERROR) {
obj = parsed; // TODO Check necessity
obj.error->plContext = lastOpen;
break;
}
if (tok[i].text[0] == ')') {
@ -733,10 +742,48 @@ void loadArgsIntoEnv(int argc, const char* argv[], struct Environment* env)
addToEnv(env, "args", args);
}
int nestedSegfault = 0;
void handler(int nSignum, siginfo_t* si, void* vcontext)
{
if (nestedSegfault) {
printf("Nested segfault!!!\n");
exit(139);
}
nestedSegfault = 1;
printf("Segfaulted!\n");
if (lastOpen) {
printf("line: %d\n%s\n", lastOpen->lineNumber, lastOpen->text);
} else {
printf("Happened before token processing.\n");
}
struct Environment *e = global();
*e = defaultEnv();
ucontext_t* context = vcontext;
context->uc_mcontext.gregs[REG_RIP]++;
exit(139);
}
int main(int argc, const char* argv[])
{
struct Environment env = defaultEnv();
setGlobal(&env);
if (argc == 2 && strcmp(argv[1], "--run-tests") == 0) {
runTests();
return 0;
}
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = handler;
sigaction(SIGSEGV, &action, NULL);
// struct Environment* e = &env;
// e += 10000;
// printEnv(e);
readFile(SCRIPTDIR "/lib.pbl", &env);
if (argc >= 2) {
FILE* file = fopen(argv[1], "r");

View File

@ -6,11 +6,22 @@
#include "env.h"
#include "object.h"
#define F(_name, _docs) static const char * const _name ## Doc = _docs; Object _name
#define fn(_name, _docs, ...) static const char * const _name ## Doc = _docs; \
Object _name
#define fnn(_name, _symbol, _docs)\
static const char * const _name ## Doc = _docs;\
static const char * const _name ## Symbol = _symbol;\
Object _name
#define fnt(_name, _docs, ...) static const char * const _name ## Doc = _docs;\
static const char * const _name ## Tests[] = {__VA_ARGS__, NULL}; \
Object _name
struct Slice {
const char* text;
unsigned char length;
int lineNumber;
};
typedef struct Result {
@ -43,12 +54,6 @@ Object simpleFuncEval(Object func, Object arg1, Object arg2, struct Environment*
#ifdef STANDALONE
Object takeInput(Object i1, Object i2, struct Environment* i3);
Object systemCall(Object call, Object _, struct Environment* i3);
Object loadFile(Object filename, Object _, struct Environment* env);
int _readFile(FILE* input, struct Environment* env);
int readFile(const char* filename, struct Environment* env);

View File

@ -4,7 +4,7 @@
#include "pebblisp.h"
#define BASIC_OP(_name) \
Object _name(Object obj1, Object obj2, struct Environment *env);
Object _name(Object obj1, Object obj2, struct Environment *env)
BASIC_OP(add);
@ -28,85 +28,118 @@ BASIC_OP(or);
#undef BASIC_OP
F(catObjects,
"Concatenate string versions of the given objects.\n"
"(cat \"Stuff: \" (1 2 3)) => \"Stuff: ( 1 2 3 )\""
fnt(catObjects,
"Concatenate string versions of the given objects.\n",
"(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )"
)(Object obj1, Object obj2, struct Environment* env);
F(filter, "(filter (< 50) (25 60 100)) => ( 60 100 )")
(Object condition, Object list, struct Environment* env);
fnt(filter,
"Filter a list based on the given condition.\n",
"(fil (< 50) (25 60 100))", "( 60 100 )"
)(Object condition, Object list, struct Environment* env);
F(append, "(ap (1 2) 3) => ( 1 2 3 )")
fn(append,
"Append the given element",
"(ap (1 2) 3)", "( 1 2 3 )")
(Object list, Object newElement, struct Environment* env);
F(prepend, "(pre (2 3) 1) => ( 1 2 3 )")
fn(prepend, "(pre (2 3) 1) => ( 1 2 3 )")
(Object list, Object newElement, struct Environment* env);
F(len, "(len (2 3)) => 2")
fn(len, "(len (2 3)) => 2")
(Object obj1, Object o_ignore, struct Environment* e_ignore);
F(reduce, "(reduce ((1 2 3) 0) +) => 6")
fn(reduce, "(reduce ((1 2 3) 0) +) => 6")
(Object listInitial, Object func, struct Environment* env);
F(at, "(at 1 (1 2 3)) => 2")
fn(at, "(at 1 (1 2 3)) => 2")
(Object index, Object list, struct Environment* env);
F(rest, "(rest (1 2 3)) => ( 2 3 )")
fn(rest, "(rest (1 2 3)) => ( 2 3 )")
(Object list, Object ignore, struct Environment* env);
F(reverse, "(rev (1 2 3)) => ( 3 2 1 )")
fn(reverse, "(rev (1 2 3)) => ( 3 2 1 )")
(Object _list, Object ignore, struct Environment* ignore2);
F(isNum, "(isnum 1) => T\n(isnum \"Hello\") => F")
fn(isNum, "(isnum 1) => T\n(isnum \"Hello\") => F")
(Object test, Object ignore, struct Environment* ignore2);
F(isList,
fn(isList,
"(islist (1 2 3)) => T\n"
"(islist ()) => T\n"
"(islist \"Stringy\") => F"
)(Object test, Object ignore, struct Environment* ignore2);
F(isString,
fn(isString,
"(isstr \"Heyo\") => T\n"
"(isstr \"\") => T\n"
"(isstr 10) => F"
)(Object test, Object ignore, struct Environment* ignore2);
F(isErr, "")
(Object test, Object ignore, struct Environment* ignore2);
fn(isErr,
"(iserr (eval \"(((\") => T\n"
"(iserr 5) => F"
)(Object test, Object ignore, struct Environment* ignore2);
F(charAt,
fn(charAt,
"(chat \"Hello\" 1) => \"e\"\n"
"(chat \"Hello\" 10) => \"\""
)(Object string, Object at, struct Environment* ignore);
F(charVal,
fn(charVal,
"(char \"h\") => 104\n"
"(char \"hello\") => 104\n"
"(char \"\") => 0"
)(Object test, Object ignore, struct Environment* ignore2);
F(parseEvalO, "(eval \"(1 2 3)\") => (1 2 3)")
fn(parseEvalO, "(eval \"(1 2 3)\") => (1 2 3)")
(Object text, Object ignore, struct Environment* env);
F(possessive,
"(struct Post (title body)) (def p (Post \"TI\" \"BO\")) p's title => TI"
fn(possessive,
"(struct Post (title body))\n"
"(def p (Post \"TI\" \"BO\"))\n"
"p's title => TI"
)(Object structo, Object field, struct Environment* env);
#ifdef STANDALONE
F(print, "Prints the string representation of the given object to stdout.")
fn(print, "Prints the string representation of the given object to stdout.")
(Object p, Object ignore, struct Environment* ignore2);
F(pChar, "Prints the ascii character for the given number value.")
fn(pChar, "Prints the ascii character for the given number value.")
(Object c, Object i1, struct Environment* i2);
F(printEnvO, "Prints out the current scoped environment.")
fn(printEnvO, "Prints out the current scoped environment.")
(Object i1, Object i2, struct Environment* env);
F(help, "(? +) => \"(+ 1 2) => 3\"")
(Object symbol, Object ignore, struct Environment* ignore2);
fn(systemCall,
"Opens a shell and runs the given command, returning the command's exit code.\n"
"(sys \"echo yee\")\n"
"yee\n"
"=> 0"
)(Object process, Object _, struct Environment* env);
#endif
fn(loadFile,
"Loads and parses the given file.\n"
"Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n"
"(loadfile \"printdate.pl\")\n"
"Mon 21 Mar 2022 10:35:03 AM EDT\n"
"=> 0"
)(Object filename, Object _, struct Environment* env);
#endif //PEBBLISP_PLFUNC_H
fn(takeInput,
"Take console input with an optional prompt. For example:\n"
"`(def x (input))` will wait for user input with no prompt.\n"
"`(def x (input \">> \"))` wait for input, but prompt the user with '>> '.\n"
)(Object i1, Object i2, struct Environment* i3);
fn(help,
"Displays help text for the given function.\n"
"Currently requires the function name as a string, but future syntactic sugar may\n"
"loosen this requirement.\n"
"(? \"+\") => \"(+ 1 2) => 3\""
)(Object symbol, Object ignore, struct Environment* ignore2);
#endif // STANDALONE
#endif // PEBBLISP_PLFUNC_H

View File

@ -55,21 +55,26 @@ check() {
return 1
fi
local output
if $VALGRIND; then
echo -ne "\n $1\r "
local output="$($VALCOM ./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)"
output="$($VALCOM ./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)"
else
local output="$(./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)"
output="$(./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)"
fi
if [ "$output" == "$3" ]; then
if [ "$3" == "$regex" ]; then
if [[ "$output" =~ ^$4$ ]]; then
pass "$1"
return
fi
elif [ "$output" == "$3" ]; then
pass "$1"
elif [ "$3" == "$regex" ] && [[ "$output" =~ $4 ]]; then
pass "$1"
else
fail "$1" "$2"
FAIL_OUTPUT="${FAIL_OUTPUT}\n  expected '$3' but received '$output'\n"
return
fi
fail "$1" "$2"
FAIL_OUTPUT="${FAIL_OUTPUT}\n  expected '$3' but received '$output'\n"
}
echo "STARTING TESTS"
@ -206,9 +211,9 @@ title "ShouldError"
check "LenOfNotList" "(len 5)" "NOT_A_LIST"
check "NoMapList" "(map sq)" "( )"
check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE"
check "BadNumber" "(5df)" "BAD_NUMBER"
check "BadHex" "(0x0zf)" "BAD_NUMBER"
check "BadBinary" "(0b01120)" "BAD_NUMBER"
check "BadNumber" "(5df)" regex "BAD_NUMBER.*"
check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*"
check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*"
check "BadParens1" "(hey()" regex "'MISMATCHED_PARENS.*"
check "BadParens2" "(hey)(" regex "'MISMATCHED_PARENS.*"
check "BadParens3" "((hey(" regex "'MISMATCHED_PARENS.*"

View File

@ -66,6 +66,7 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
int i = 0;
int slice = 0;
int lineNumber = 1;
int parens = 0;
while (input[i] != '\0') {
@ -73,6 +74,9 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
// printd("input: '%c'\n", input[i]);
if (isWhitespace(input[i]) || input[i] == ';') {
if (input[i] == '\n') {
lineNumber++;
}
i++;
continue;
}
@ -92,6 +96,7 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
}
slices[slice].text = &input[i];
slices[slice].lineNumber = lineNumber;
if (isSingle(input[i])) {
i++;
@ -105,6 +110,9 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
if (input[i] == '"' && input[i + 1] == '"' && input[i + 2] == '"') {
break;
}
if (input[i] == '\n') {
lineNumber++;
}
l++;
if (input[i] == '\0' || input[i + 1] == '\0' || input[i + 2] == '\0') {
err->context = malloc(sizeof(char) * ERR_LEN + 1);
@ -118,6 +126,9 @@ struct Slice* nf_tokenize(const char* input, struct Error* err)
} else {
// Simple string
while (input[++i] != '"' && input[i] != '\0') {
if (input[i] == '\n') {
lineNumber++;
}
l++;
}
}

View File

@ -80,8 +80,8 @@ answer_to_connection(void* cls, struct MHD_Connection* connection,
Object queryParams = listObject();
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, add_query_param, &queryParams);
Object res = structObject(requestDefinition);
res.structObject->fields[0] = queryParams;
Object res = numberObject(1010);//structObject(requestDefinition);
//res.structObject->fields[0] = queryParams;
Object route = cloneObject(routes[i].routeAction);
Object result = listEvalLambda(&route, &res, routes[i].env);

View File

@ -16,21 +16,21 @@ int addRoute(struct Route route);
int start(int port);
F(startServer,
fn(startServer,
"(serve) => 0\n"
"Starts a simple web server with routes that have been added with (get) and (post).\n"
"Note: This is a non-blocking call. It is recommended to wait for user input before exiting.\n"
" A simple way would be to use (inp) immediately after the (serve) call."
)(Object path, Object textFunc, struct Environment* env);
F(addGetRoute,
fn(addGetRoute,
"Note: Implementation bugs currently prevent using an inline lambda.\n"
" (def homepage (fn () (\"Hello, world!\")));(get \"/\" homepage)\n"
" (def queryPage (fn (req) (req's queryParams)));(get \"/x\" queryPage)\n"
" (serve)\n"
)(Object path, Object textFunc, struct Environment* env);
F(addPostRoute,
fn(addPostRoute,
"Note: Implementation bugs currently prevent using an inline lambda.\n"
" (def homepage (fn () (\"Hello, world!\")));(post \"/\" homepage)\n"
" (serve)\n"