Update.
Some checks failed
WrenCI / linux (push) Failing after 34s
WrenCI / mac (push) Has been cancelled
WrenCI / windows (push) Has been cancelled

This commit is contained in:
retoor 2026-01-26 12:29:25 +01:00
parent 264712dcbc
commit 5edaf69915
24 changed files with 392 additions and 74 deletions

View File

@ -1,6 +1,6 @@
# retoor <retoor@molodetz.nl>
.PHONY: build tests clean debug sync-sidebar buildmanual wasm wasm-clean install-emscripten
.PHONY: build tests clean debug sync-sidebar buildmanual wasm wasm-clean install-emscripten coverage valgrind clean-coverage
build:
cd projects/make && $(MAKE) -f wren_cli.make -j $$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
@ -26,6 +26,14 @@ install:
clean:
cd projects/make && $(MAKE) -f wren_cli.make clean
publish:
git add src
git add example
git add manual_src
git add test
git commit -a -m "Update."
git push
wasm-clean:
cd projects/make.wasm && $(MAKE) -f wren_wasm.make clean
@ -50,3 +58,22 @@ wasm:
buildmanual:
python3 util/build_manual.py
coverage:
cd projects/make && $(MAKE) -f wren_cli.make config=coverage_64bit clean
cd projects/make && $(MAKE) -f wren_cli.make config=coverage_64bit -j $$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
python3 util/test.py --suffix=_cov
@echo "Generating coverage report..."
lcov --capture --directory projects/make/obj/64bit/Coverage --output-file coverage.info --ignore-errors mismatch,inconsistent
lcov --remove coverage.info '/usr/*' '*/deps/*' --output-file coverage.info --ignore-errors unused
genhtml coverage.info --output-directory coverage_report
@echo "Coverage report: coverage_report/index.html"
valgrind:
cd projects/make && $(MAKE) -f wren_cli.make config=debug_64bit -j $$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
python3 util/test.py --suffix=_d --valgrind
clean-coverage:
rm -rf coverage.info coverage_report
find projects/make -name "*.gcda" -delete
find projects/make -name "*.gcno" -delete

View File

@ -87,6 +87,15 @@ ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -g -std=c99
ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -g
ALL_LDFLAGS += $(LDFLAGS)
else ifeq ($(config),coverage_64bit)
TARGETDIR = ../../bin
TARGET = $(TARGETDIR)/wren_cli_cov
OBJDIR = obj/64bit/Coverage
DEFINES += -DDEBUG -DWREN_PROFILE_ENABLED -D_GNU_SOURCE
ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -g --coverage -O0 -std=c99
ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -m64 -g --coverage -O0
ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -m64 --coverage
else
$(error "invalid configuration $(config)")
endif

View File

@ -50,6 +50,7 @@ extern void bytesXorMask(WrenVM* vm);
extern void bytesConcat(WrenVM* vm);
extern void bytesSlice(WrenVM* vm);
extern void bytesLength(WrenVM* vm);
extern void bytesIndexOf(WrenVM* vm);
extern void cryptoRandomBytes(WrenVM* vm);
extern void cryptoMd5(WrenVM* vm);
extern void cryptoSha1(WrenVM* vm);
@ -158,6 +159,8 @@ extern void serverAllocate(WrenVM* vm);
extern void serverFinalize(void* data);
extern void serverBind(WrenVM* vm);
extern void serverAccept(WrenVM* vm);
extern void serverAcceptBatch(WrenVM* vm);
extern void serverPendingCount(WrenVM* vm);
extern void serverClose(WrenVM* vm);
extern void signalTrap(WrenVM* vm);
extern void signalIgnore(WrenVM* vm);
@ -389,6 +392,8 @@ static ModuleRegistry modules[] =
STATIC_METHOD("concat(_,_)", bytesConcat)
STATIC_METHOD("slice(_,_,_)", bytesSlice)
STATIC_METHOD("length(_)", bytesLength)
STATIC_METHOD("indexOf(_,_)", bytesIndexOf)
STATIC_METHOD("indexOf(_,_,_)", bytesIndexOf)
END_CLASS
END_MODULE
MODULE(crypto)
@ -563,6 +568,8 @@ static ModuleRegistry modules[] =
FINALIZE(serverFinalize)
METHOD("bind_(_,_)", serverBind)
METHOD("accept_(_)", serverAccept)
METHOD("acceptBatch_(_)", serverAcceptBatch)
METHOD("pendingCount_", serverPendingCount)
METHOD("close_()", serverClose)
END_CLASS
END_MODULE

View File

@ -178,3 +178,27 @@ void bytesLength(WrenVM* vm) {
wrenGetSlotBytes(vm, 1, &length);
wrenSetSlotDouble(vm, 0, (double)length);
}
void bytesIndexOf(WrenVM* vm) {
int haystackLen = 0;
const char* haystack = wrenGetSlotBytes(vm, 1, &haystackLen);
int needleLen = 0;
const char* needle = wrenGetSlotBytes(vm, 2, &needleLen);
int start = 0;
if (wrenGetSlotCount(vm) > 3 && wrenGetSlotType(vm, 3) == WREN_TYPE_NUM) {
start = (int)wrenGetSlotDouble(vm, 3);
}
if (needleLen == 0 || haystackLen == 0 || start < 0 || start >= haystackLen) {
wrenSetSlotDouble(vm, 0, -1);
return;
}
for (int i = start; i <= haystackLen - needleLen; i++) {
if (memcmp(haystack + i, needle, needleLen) == 0) {
wrenSetSlotDouble(vm, 0, (double)i);
return;
}
}
wrenSetSlotDouble(vm, 0, -1);
}

View File

@ -11,5 +11,6 @@ void bytesXorMask(WrenVM* vm);
void bytesConcat(WrenVM* vm);
void bytesSlice(WrenVM* vm);
void bytesLength(WrenVM* vm);
void bytesIndexOf(WrenVM* vm);
#endif

View File

@ -7,4 +7,6 @@ class Bytes {
foreign static concat(a, b)
foreign static slice(data, start, end)
foreign static length(data)
foreign static indexOf(haystack, needle)
foreign static indexOf(haystack, needle, start)
}

View File

@ -11,4 +11,6 @@ static const char* bytesModuleSource =
" foreign static concat(a, b)\n"
" foreign static slice(data, start, end)\n"
" foreign static length(data)\n"
" foreign static indexOf(haystack, needle)\n"
" foreign static indexOf(haystack, needle, start)\n"
"}\n";

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "uv.h"
@ -736,15 +737,36 @@ static void stdinFileTimerCallback(uv_timer_t* handle)
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
}
char buffer[4096];
ssize_t numRead = read(stdinDescriptor, buffer, sizeof(buffer));
wrenEnsureSlots(vm, 2);
wrenSetSlotHandle(vm, 0, stdinClass);
wrenSetSlotNull(vm, 1);
WrenInterpretResult result = wrenCall(vm, stdinOnData);
if (result == WREN_RESULT_RUNTIME_ERROR)
if (numRead > 0)
{
uv_stop(getLoop());
setExitCode(70);
wrenSetSlotBytes(vm, 1, buffer, (size_t)numRead);
WrenInterpretResult result = wrenCall(vm, stdinOnData);
if (result == WREN_RESULT_RUNTIME_ERROR)
{
uv_stop(getLoop());
setExitCode(70);
return;
}
uv_timer_start(&stdinFileTimer, stdinFileTimerCallback, 0, 0);
}
else
{
wrenSetSlotNull(vm, 1);
WrenInterpretResult result = wrenCall(vm, stdinOnData);
if (result == WREN_RESULT_RUNTIME_ERROR)
{
uv_stop(getLoop());
setExitCode(70);
}
}
}

View File

@ -26,11 +26,14 @@ typedef struct SocketListNode {
struct SocketListNode* next;
} SocketListNode;
#define MAX_PENDING_CONNECTIONS 512
typedef struct {
uv_tcp_t* handle;
WrenHandle* acceptFiber; // Fiber waiting for accept()
WrenHandle* acceptFiber;
SocketListNode* pendingHead;
SocketListNode* pendingTail;
int pendingCount;
} ServerData;
static WrenHandle* socketClassHandle = NULL;
@ -278,6 +281,7 @@ void serverAllocate(WrenVM* vm)
data->acceptFiber = NULL;
data->pendingHead = NULL;
data->pendingTail = NULL;
data->pendingCount = 0;
}
void serverFinalize(void* data)
@ -313,27 +317,27 @@ void netShutdown()
static void connectionCallback(uv_stream_t* server, int status)
{
ServerData* data = (ServerData*)server->data;
if (status < 0) {
return;
}
uv_tcp_t* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
uv_tcp_init(getLoop(), client);
if (uv_accept(server, (uv_stream_t*)client) == 0) {
if (data->acceptFiber) {
WrenHandle* fiber = data->acceptFiber;
data->acceptFiber = NULL;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
if (socketClassHandle == NULL) {
fprintf(stderr, "FATAL: socketClassHandle NULL in connectionCallback\n");
exit(1);
}
wrenSetSlotHandle(vm, 1, socketClassHandle);
wrenSetSlotNewForeign(vm, 2, 1, sizeof(SocketData));
SocketData* sockData = (SocketData*)wrenGetSlotForeign(vm, 2);
@ -348,6 +352,10 @@ static void connectionCallback(uv_stream_t* server, int status)
schedulerResume(fiber, true);
schedulerFinishResume();
} else {
if (data->pendingCount >= MAX_PENDING_CONNECTIONS) {
uv_close((uv_handle_t*)client, closeCallback);
return;
}
SocketListNode* node = (SocketListNode*)malloc(sizeof(SocketListNode));
node->handle = client;
node->next = NULL;
@ -358,6 +366,7 @@ static void connectionCallback(uv_stream_t* server, int status)
data->pendingHead = node;
data->pendingTail = node;
}
data->pendingCount++;
}
} else {
uv_close((uv_handle_t*)client, closeCallback);
@ -390,18 +399,19 @@ void serverAccept(WrenVM* vm)
SocketListNode* node = data->pendingHead;
data->pendingHead = node->next;
if (data->pendingHead == NULL) data->pendingTail = NULL;
data->pendingCount--;
uv_tcp_t* client = node->handle;
free(node);
wrenEnsureSlots(vm, 3);
wrenEnsureSlots(vm, 3);
if (socketClassHandle == NULL) {
fprintf(stderr, "FATAL: socketClassHandle NULL in serverAccept\n");
exit(1);
}
wrenSetSlotHandle(vm, 2, socketClassHandle);
wrenSetSlotNewForeign(vm, 0, 2, sizeof(SocketData));
SocketData* sockData = (SocketData*)wrenGetSlotForeign(vm, 0);
sockData->handle = client;
client->data = sockData;
@ -417,6 +427,50 @@ void serverAccept(WrenVM* vm)
}
}
void serverAcceptBatch(WrenVM* vm)
{
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);
int maxCount = (int)wrenGetSlotDouble(vm, 1);
wrenEnsureSlots(vm, 4);
wrenSetSlotNewList(vm, 0);
int count = 0;
while (data->pendingHead && count < maxCount) {
SocketListNode* node = data->pendingHead;
data->pendingHead = node->next;
if (data->pendingHead == NULL) data->pendingTail = NULL;
data->pendingCount--;
if (socketClassHandle == NULL) {
uv_close((uv_handle_t*)node->handle, closeCallback);
free(node);
continue;
}
wrenSetSlotHandle(vm, 2, socketClassHandle);
wrenSetSlotNewForeign(vm, 1, 2, sizeof(SocketData));
SocketData* sockData = (SocketData*)wrenGetSlotForeign(vm, 1);
sockData->handle = node->handle;
node->handle->data = sockData;
sockData->fiber = NULL;
sockData->connectReq = NULL;
sockData->writeReq = NULL;
sockData->readBuffer = NULL;
sockData->readBufferSize = 0;
free(node);
wrenInsertInList(vm, 0, -1, 1);
count++;
}
}
void serverPendingCount(WrenVM* vm)
{
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)data->pendingCount);
}
void serverClose(WrenVM* vm)
{
ServerData* data = (ServerData*)wrenGetSlotForeign(vm, 0);

View File

@ -19,6 +19,8 @@ void serverAllocate(WrenVM* vm);
void serverFinalize(void* data);
void serverBind(WrenVM* vm);
void serverAccept(WrenVM* vm);
void serverAcceptBatch(WrenVM* vm);
void serverPendingCount(WrenVM* vm);
void serverClose(WrenVM* vm);
#endif

12
src/module/net.wren vendored
View File

@ -40,7 +40,7 @@ foreign class Server {
static bind(host, port) {
if (!(host is String)) Fiber.abort("Host must be a string.")
if (!(port is Num)) Fiber.abort("Port must be a number.")
var server = Server.new()
server.bind_(host, port)
return server
@ -51,12 +51,20 @@ foreign class Server {
if (socket != null) return socket
return Scheduler.runNextScheduled_()
}
acceptBatch(maxCount) {
return acceptBatch_(maxCount)
}
pendingCount { pendingCount_ }
close() {
close_()
}
foreign bind_(host, port)
foreign accept_(fiber)
foreign acceptBatch_(maxCount)
foreign pendingCount_
foreign close_()
}

View File

@ -44,7 +44,7 @@ static const char* netModuleSource =
" static bind(host, port) {\n"
" if (!(host is String)) Fiber.abort(\"Host must be a string.\")\n"
" if (!(port is Num)) Fiber.abort(\"Port must be a number.\")\n"
" \n"
"\n"
" var server = Server.new()\n"
" server.bind_(host, port)\n"
" return server\n"
@ -55,12 +55,20 @@ static const char* netModuleSource =
" if (socket != null) return socket\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
" \n"
"\n"
" acceptBatch(maxCount) {\n"
" return acceptBatch_(maxCount)\n"
" }\n"
"\n"
" pendingCount { pendingCount_ }\n"
"\n"
" close() {\n"
" close_()\n"
" }\n"
"\n"
" foreign bind_(host, port)\n"
" foreign accept_(fiber)\n"
" foreign acceptBatch_(maxCount)\n"
" foreign pendingCount_\n"
" foreign close_()\n"
"}\n";

View File

@ -135,7 +135,9 @@ void regexAllocate(WrenVM* vm) {
char* processed = preprocessPattern(pattern, data->dotall);
if (!processed) {
free(data->pattern);
data->pattern = NULL;
free(data->flags);
data->flags = NULL;
wrenSetSlotString(vm, 0, "Failed to preprocess pattern");
wrenAbortFiber(vm, 0);
return;
@ -148,7 +150,9 @@ void regexAllocate(WrenVM* vm) {
char errbuf[256];
regerror(err, &data->compiled, errbuf, sizeof(errbuf));
free(data->pattern);
data->pattern = NULL;
free(data->flags);
data->flags = NULL;
wrenSetSlotString(vm, 0, errbuf);
wrenAbortFiber(vm, 0);
return;

View File

@ -45,7 +45,7 @@ class Scheduler {
}
var fiber = __scheduled[__scheduleIndex]
__scheduleIndex = __scheduleIndex + 1
if (__scheduleIndex > 64 && __scheduleIndex > __scheduled.count / 2) {
if (__scheduleIndex > 32 && __scheduleIndex > __scheduled.count / 4) {
__scheduled = __scheduled[__scheduleIndex..-1]
__scheduleIndex = 0
}

View File

@ -49,7 +49,7 @@ static const char* schedulerModuleSource =
" }\n"
" var fiber = __scheduled[__scheduleIndex]\n"
" __scheduleIndex = __scheduleIndex + 1\n"
" if (__scheduleIndex > 64 && __scheduleIndex > __scheduled.count / 2) {\n"
" if (__scheduleIndex > 32 && __scheduleIndex > __scheduled.count / 4) {\n"
" __scheduled = __scheduled[__scheduleIndex..-1]\n"
" __scheduleIndex = 0\n"
" }\n"

View File

@ -468,6 +468,13 @@ void popenAllocate(WrenVM* vm) {
}
void popenFinalize(void* userData) {
PopenData* data = (PopenData*)userData;
for (int i = 0; i < 3; i++) {
if (data->streams[i].buffer != NULL) {
free(data->streams[i].buffer);
}
}
}
void popenPid(WrenVM* vm) {

102
src/module/web.wren vendored
View File

@ -40,6 +40,7 @@ class Request {
headers { _headers }
body { _body }
params { _params }
params=(value) { _params = value }
socket { _socket }
header(name) {
@ -71,43 +72,92 @@ class Request {
var boundary = extractBoundary_(contentType)
if (boundary == null) return result
var parts = _body.split("--" + boundary)
for (part in parts) {
var trimmed = part.trim()
if (trimmed.count == 0 || trimmed == "--") continue
var delimBytes = ("--" + boundary)
var bodyLen = Bytes.length(_body)
var pos = Bytes.indexOf(_body, delimBytes, 0)
if (pos < 0) return result
var headerEnd = trimmed.indexOf("\r\n\r\n")
while (pos >= 0) {
pos = pos + Bytes.length(delimBytes)
if (pos >= bodyLen) break
var nextByte = Bytes.slice(_body, pos, pos + 2)
if (nextByte == "--") break
if (Bytes.slice(_body, pos, pos + 2) == "\r\n") {
pos = pos + 2
} else if (Bytes.slice(_body, pos, pos + 1) == "\n") {
pos = pos + 1
}
var nextPos = Bytes.indexOf(_body, delimBytes, pos)
if (nextPos < 0) nextPos = bodyLen
var partData = Bytes.slice(_body, pos, nextPos)
var partLen = Bytes.length(partData)
var headerEnd = Bytes.indexOf(partData, "\r\n\r\n", 0)
var delimLen = 4
if (headerEnd < 0) {
headerEnd = trimmed.indexOf("\n\n")
headerEnd = Bytes.indexOf(partData, "\n\n", 0)
delimLen = 2
}
if (headerEnd < 0) continue
var headers = trimmed[0...headerEnd]
var content = trimmed[headerEnd + delimLen..-1]
if (content.endsWith("\r\n")) {
content = content[0...-2]
} else if (content.endsWith("\n")) {
content = content[0...-1]
if (headerEnd < 0) {
pos = nextPos
continue
}
var headerBytes = Bytes.slice(partData, 0, headerEnd)
var headers = ""
for (b in Bytes.toList(headerBytes)) {
headers = headers + String.fromByte(b)
}
var contentStart = headerEnd + delimLen
var contentEnd = partLen
var trailCheck = Bytes.slice(partData, partLen - 2, partLen)
if (trailCheck == "\r\n") {
contentEnd = partLen - 2
} else {
trailCheck = Bytes.slice(partData, partLen - 1, partLen)
if (trailCheck == "\n") {
contentEnd = partLen - 1
}
}
var content = Bytes.slice(partData, contentStart, contentEnd)
var disposition = extractHeader_(headers, "Content-Disposition")
if (disposition == null) continue
if (disposition == null) {
pos = nextPos
continue
}
var name = extractDispositionParam_(disposition, "name")
if (name == null) continue
if (name == null) {
pos = nextPos
continue
}
var filename = extractDispositionParam_(disposition, "filename")
if (filename != null) {
var contentStr = ""
for (b in Bytes.toList(content)) {
contentStr = contentStr + String.fromByte(b)
}
result[name] = {
"filename": filename,
"content": content,
"content": contentStr,
"content_type": extractHeader_(headers, "Content-Type") || "application/octet-stream"
}
} else {
result[name] = content
var contentStr = ""
for (b in Bytes.toList(content)) {
contentStr = contentStr + String.fromByte(b)
}
result[name] = contentStr
}
pos = nextPos
}
return result
}
@ -840,6 +890,12 @@ class Application {
Scheduler.add {
self.handleConnectionSafe_(socket)
}
var batch = server.acceptBatch(15)
for (s in batch) {
Scheduler.add {
self.handleConnectionSafe_(s)
}
}
}
}
@ -864,8 +920,15 @@ class Application {
var headersComplete = false
var headerBytes = 0
var headerStr = null
var maxReadIterations = 1000
var readIterations = 0
while (true) {
readIterations = readIterations + 1
if (readIterations > maxReadIterations) {
socket.close()
return
}
var chunk = socket.read()
if (chunk == null || chunk.bytes.count == 0) {
socket.close()
@ -960,8 +1023,7 @@ class Application {
if (response == null) {
var route = _router.match(method, path)
if (route != null) {
request = Request.new_(method, path, query, headers, body, route["params"], socket)
loadSession_(request)
request.params = route["params"]
for (mw in _middleware) {
var result = mw.call(request)
if (result != null) {

View File

@ -44,6 +44,7 @@ static const char* webModuleSource =
" headers { _headers }\n"
" body { _body }\n"
" params { _params }\n"
" params=(value) { _params = value }\n"
" socket { _socket }\n"
"\n"
" header(name) {\n"
@ -75,43 +76,92 @@ static const char* webModuleSource =
" var boundary = extractBoundary_(contentType)\n"
" if (boundary == null) return result\n"
"\n"
" var parts = _body.split(\"--\" + boundary)\n"
" for (part in parts) {\n"
" var trimmed = part.trim()\n"
" if (trimmed.count == 0 || trimmed == \"--\") continue\n"
" var delimBytes = (\"--\" + boundary)\n"
" var bodyLen = Bytes.length(_body)\n"
" var pos = Bytes.indexOf(_body, delimBytes, 0)\n"
" if (pos < 0) return result\n"
"\n"
" var headerEnd = trimmed.indexOf(\"\\r\\n\\r\\n\")\n"
" while (pos >= 0) {\n"
" pos = pos + Bytes.length(delimBytes)\n"
" if (pos >= bodyLen) break\n"
"\n"
" var nextByte = Bytes.slice(_body, pos, pos + 2)\n"
" if (nextByte == \"--\") break\n"
"\n"
" if (Bytes.slice(_body, pos, pos + 2) == \"\\r\\n\") {\n"
" pos = pos + 2\n"
" } else if (Bytes.slice(_body, pos, pos + 1) == \"\\n\") {\n"
" pos = pos + 1\n"
" }\n"
"\n"
" var nextPos = Bytes.indexOf(_body, delimBytes, pos)\n"
" if (nextPos < 0) nextPos = bodyLen\n"
"\n"
" var partData = Bytes.slice(_body, pos, nextPos)\n"
" var partLen = Bytes.length(partData)\n"
"\n"
" var headerEnd = Bytes.indexOf(partData, \"\\r\\n\\r\\n\", 0)\n"
" var delimLen = 4\n"
" if (headerEnd < 0) {\n"
" headerEnd = trimmed.indexOf(\"\\n\\n\")\n"
" headerEnd = Bytes.indexOf(partData, \"\\n\\n\", 0)\n"
" delimLen = 2\n"
" }\n"
" if (headerEnd < 0) continue\n"
"\n"
" var headers = trimmed[0...headerEnd]\n"
" var content = trimmed[headerEnd + delimLen..-1]\n"
" if (content.endsWith(\"\\r\\n\")) {\n"
" content = content[0...-2]\n"
" } else if (content.endsWith(\"\\n\")) {\n"
" content = content[0...-1]\n"
" if (headerEnd < 0) {\n"
" pos = nextPos\n"
" continue\n"
" }\n"
"\n"
" var headerBytes = Bytes.slice(partData, 0, headerEnd)\n"
" var headers = \"\"\n"
" for (b in Bytes.toList(headerBytes)) {\n"
" headers = headers + String.fromByte(b)\n"
" }\n"
"\n"
" var contentStart = headerEnd + delimLen\n"
" var contentEnd = partLen\n"
" var trailCheck = Bytes.slice(partData, partLen - 2, partLen)\n"
" if (trailCheck == \"\\r\\n\") {\n"
" contentEnd = partLen - 2\n"
" } else {\n"
" trailCheck = Bytes.slice(partData, partLen - 1, partLen)\n"
" if (trailCheck == \"\\n\") {\n"
" contentEnd = partLen - 1\n"
" }\n"
" }\n"
" var content = Bytes.slice(partData, contentStart, contentEnd)\n"
"\n"
" var disposition = extractHeader_(headers, \"Content-Disposition\")\n"
" if (disposition == null) continue\n"
" if (disposition == null) {\n"
" pos = nextPos\n"
" continue\n"
" }\n"
"\n"
" var name = extractDispositionParam_(disposition, \"name\")\n"
" if (name == null) continue\n"
" if (name == null) {\n"
" pos = nextPos\n"
" continue\n"
" }\n"
"\n"
" var filename = extractDispositionParam_(disposition, \"filename\")\n"
" if (filename != null) {\n"
" var contentStr = \"\"\n"
" for (b in Bytes.toList(content)) {\n"
" contentStr = contentStr + String.fromByte(b)\n"
" }\n"
" result[name] = {\n"
" \"filename\": filename,\n"
" \"content\": content,\n"
" \"content\": contentStr,\n"
" \"content_type\": extractHeader_(headers, \"Content-Type\") || \"application/octet-stream\"\n"
" }\n"
" } else {\n"
" result[name] = content\n"
" var contentStr = \"\"\n"
" for (b in Bytes.toList(content)) {\n"
" contentStr = contentStr + String.fromByte(b)\n"
" }\n"
" result[name] = contentStr\n"
" }\n"
"\n"
" pos = nextPos\n"
" }\n"
" return result\n"
" }\n"
@ -844,6 +894,12 @@ static const char* webModuleSource =
" Scheduler.add {\n"
" self.handleConnectionSafe_(socket)\n"
" }\n"
" var batch = server.acceptBatch(15)\n"
" for (s in batch) {\n"
" Scheduler.add {\n"
" self.handleConnectionSafe_(s)\n"
" }\n"
" }\n"
" }\n"
" }\n"
"\n"
@ -868,8 +924,15 @@ static const char* webModuleSource =
" var headersComplete = false\n"
" var headerBytes = 0\n"
" var headerStr = null\n"
" var maxReadIterations = 1000\n"
" var readIterations = 0\n"
"\n"
" while (true) {\n"
" readIterations = readIterations + 1\n"
" if (readIterations > maxReadIterations) {\n"
" socket.close()\n"
" return\n"
" }\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.bytes.count == 0) {\n"
" socket.close()\n"
@ -964,8 +1027,7 @@ static const char* webModuleSource =
" if (response == null) {\n"
" var route = _router.match(method, path)\n"
" if (route != null) {\n"
" request = Request.new_(method, path, query, headers, body, route[\"params\"], socket)\n"
" loadSession_(request)\n"
" request.params = route[\"params\"]\n"
" for (mw in _middleware) {\n"
" var result = mw.call(request)\n"
" if (result != null) {\n"

View File

@ -1,4 +1,5 @@
// skip:
// retoor <retoor@molodetz.nl>
import "io" for Stdin
Stdin.readLine() // stdin: one line

View File

@ -1,4 +1,5 @@
// skip:
// retoor <retoor@molodetz.nl>
import "io" for Stdin
System.write("> ")

View File

@ -1,4 +1,5 @@
// skip:
// retoor <retoor@molodetz.nl>
import "io" for Stdin
Stdin.readLine() // stdin: one line

View File

@ -1,6 +1,5 @@
// retoor <retoor@molodetz.nl>
// skip: double free bug in C implementation when handling invalid regex patterns
import "regex" for Regex, Match
var re = Regex.new("[unclosed")
var re = Regex.new("[unclosed") // expect runtime error: Unmatched [, [^, [:, [., or [=

View File

@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
// skip: multipart parser has pre-existing issues with null bytes and high-byte values
import "web" for Request

View File

@ -27,6 +27,8 @@ parser.add_argument('--suffix', default='')
parser.add_argument('suite', nargs='?')
parser.add_argument('--skip-tests', action='store_true')
parser.add_argument('--skip-examples', action='store_true')
parser.add_argument('--valgrind', action='store_true',
help='Run tests under valgrind')
args = parser.parse_args(sys.argv[1:])
@ -150,10 +152,15 @@ class Test:
return True
def run(self, app, type):
# Invoke wren and run the test.
def run(self, app, type, valgrind=False):
test_arg = self.path
proc = Popen([app, test_arg], stdin=PIPE, stdout=PIPE, stderr=PIPE)
if valgrind:
cmd = ['valgrind', '--leak-check=full', '--error-exitcode=99',
'--suppressions=' + join(dirname(realpath(__file__)), 'valgrind.supp'),
app, test_arg]
else:
cmd = [app, test_arg]
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
# If a test takes longer than five seconds, kill it.
#
@ -173,12 +180,14 @@ class Test:
if timed_out[0]:
self.fail("Timed out.")
else:
self.validate(type == "example", proc.returncode, out, err)
if valgrind and proc.returncode == 99:
self.fail("Valgrind detected memory errors")
self.validate(type == "example", proc.returncode, out, err, valgrind)
finally:
timer.cancel()
def validate(self, is_example, exit_code, out, err):
def validate(self, is_example, exit_code, out, err, valgrind=False):
if self.compile_errors and self.runtime_error_message:
self.fail("Test error: Cannot expect both compile and runtime errors.")
return
@ -189,6 +198,13 @@ class Test:
except:
self.fail('Error decoding output.')
if valgrind:
err_lines = []
for line in err.split('\n'):
if not line.startswith('==') and not line.startswith('--'):
err_lines.append(line)
err = '\n'.join(err_lines)
error_lines = err.split('\n')
# Validate that an expected runtime error occurred.
@ -376,7 +392,7 @@ def run_script(app, path, type):
# It's a skipped or non-test file.
return
test.run(app, type)
test.run(app, type, args.valgrind)
# Display the results.
if len(test.failures) == 0: