diff --git a/src/cli/modules.c b/src/cli/modules.c index 5f5290a2..c0cedfdb 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -7,7 +7,14 @@ #include "scheduler.wren.inc" #include "timer.wren.inc" -extern void fileStartSize(WrenVM* vm); +extern void fileAllocate(WrenVM* vm); +extern void fileFinalize(WrenVM* vm); +extern void fileOpen(WrenVM* vm); +extern void fileSizePath(WrenVM* vm); +extern void fileClose(WrenVM* vm); +extern void fileDescriptor(WrenVM* vm); +extern void fileReadBytes(WrenVM* vm); +extern void fileSize(WrenVM* vm); extern void schedulerCaptureMethods(WrenVM* vm); extern void timerStartTimer(WrenVM* vm); @@ -19,7 +26,7 @@ extern void timerStartTimer(WrenVM* vm); // If you add a new method to the longest class below, make sure to bump this. // Note that it also includes an extra slot for the sentinel value indicating // the end of the list. -#define MAX_METHODS_PER_CLASS 2 +#define MAX_METHODS_PER_CLASS 9 // The maximum number of foreign classes a single built-in module defines. // @@ -75,7 +82,14 @@ static ModuleRegistry modules[] = { MODULE(io) CLASS(File) - STATIC_METHOD("startSize_(_,_)", fileStartSize) + STATIC_METHOD("", fileAllocate) + STATIC_METHOD("", fileFinalize) + STATIC_METHOD("open_(_,_)", fileOpen) + STATIC_METHOD("sizePath_(_,_)", fileSizePath) + METHOD("close_(_)", fileClose) + METHOD("descriptor", fileDescriptor) + METHOD("readBytes_(_,_)", fileReadBytes) + METHOD("size_(_)", fileSize) END_CLASS END_MODULE diff --git a/src/cli/vm.c b/src/cli/vm.c index df191dfd..6752d390 100644 --- a/src/cli/vm.c +++ b/src/cli/vm.c @@ -18,6 +18,9 @@ static uv_loop_t* loop; static char const* rootDirectory = NULL; +// The exit code to use unless some other error overrides it. +int defaultExitCode = 0; + // Reads the contents of the file at [path] and returns it as a heap allocated // string. // @@ -220,6 +223,8 @@ void runFile(const char* path) // Exit with an error code if the script failed. if (result == WREN_RESULT_COMPILE_ERROR) exit(65); // EX_DATAERR. if (result == WREN_RESULT_RUNTIME_ERROR) exit(70); // EX_SOFTWARE. + + if (defaultExitCode != 0) exit(defaultExitCode); } int runRepl() @@ -262,6 +267,11 @@ uv_loop_t* getLoop() return loop; } +void setExitCode(int exitCode) +{ + defaultExitCode = exitCode; +} + void setTestCallbacks(WrenBindForeignMethodFn bindMethod, WrenBindForeignClassFn bindClass, WrenForeignMethodFn afterLoad) diff --git a/src/cli/vm.h b/src/cli/vm.h index e29ef8f5..6018dcee 100644 --- a/src/cli/vm.h +++ b/src/cli/vm.h @@ -18,6 +18,9 @@ WrenVM* getVM(); // Gets the event loop the VM is using. uv_loop_t* getLoop(); +// Set the exit code the CLI should exit with when done. +void setExitCode(int exitCode); + // Adds additional callbacks to use when binding foreign members from Wren. // // Used by the API test executable to let it wire up its own foreign functions. diff --git a/src/module/io.c b/src/module/io.c index 134baf05..88c9613a 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -9,32 +9,171 @@ #include -// Called by libuv when the stat call for size completes. -static void sizeCallback(uv_fs_t* request) +void fileAllocate(WrenVM* vm) +{ + // Store the file descriptor in the foreign data, so that we can get to it + // in the finalizer. + int* fd = (int*)wrenAllocateForeign(vm, sizeof(int)); + *fd = (int)wrenGetArgumentDouble(vm, 1); +} + +void fileFinalize(WrenVM* vm) +{ + int fd = *(int*)wrenGetArgumentForeign(vm, 0); + + // Already closed. + if (fd == -1) return; + + uv_fs_t request; + uv_fs_close(getLoop(), &request, fd, NULL); + uv_fs_req_cleanup(&request); +} + +// If [request] failed with an error, sends the runtime error to the VM and +// frees the request. +// +// Returns true if an error was reported. +static bool handleRequestError(uv_fs_t* request) +{ + if (request->result >= 0) return false; + + WrenValue* fiber = (WrenValue*)request->data; + schedulerResumeError(fiber, uv_strerror((int)request->result)); + uv_fs_req_cleanup(request); + free(request); + return true; +} + +// Allocates a new request that resumes [fiber] when it completes. +uv_fs_t* createRequest(WrenValue* fiber) +{ + uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t)); + request->data = fiber; + return request; +} + +// Releases resources used by [request]. +// +// Returns the fiber that should be resumed after [request] completes. +WrenValue* freeRequest(uv_fs_t* request) { WrenValue* fiber = (WrenValue*)request->data; - if (request->result != 0) - { - schedulerResumeString(fiber, uv_strerror((int)request->result)); - uv_fs_req_cleanup(request); - return; - } + uv_fs_req_cleanup(request); + free(request); + + return fiber; +} + +static void openCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + double fd = (double)request->result; + WrenValue* fiber = freeRequest(request); + + schedulerResumeDouble(fiber, fd); +} + +void fileOpen(WrenVM* vm) +{ + const char* path = wrenGetArgumentString(vm, 1); + uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); + + // TODO: Allow controlling flags and modes. + uv_fs_open(getLoop(), request, path, O_RDONLY, S_IRUSR, openCallback); +} + +// Called by libuv when the stat call for size completes. +static void sizeCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; double size = (double)request->statbuf.st_size; - uv_fs_req_cleanup(request); + WrenValue* fiber = freeRequest(request); schedulerResumeDouble(fiber, size); } -void fileStartSize(WrenVM* vm) +void fileSizePath(WrenVM* vm) { const char* path = wrenGetArgumentString(vm, 1); - WrenValue* fiber = wrenGetArgumentValue(vm, 2); - - // Store the fiber to resume when the request completes. - uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t)); - request->data = fiber; - + uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); uv_fs_stat(getLoop(), request, path, sizeCallback); } + +static void closeCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + WrenValue* fiber = freeRequest(request); + schedulerResume(fiber); +} + +void fileClose(WrenVM* vm) +{ + int* foreign = (int*)wrenGetArgumentForeign(vm, 0); + int fd = *foreign; + + // If it's already closed, we're done. + if (fd == -1) + { + wrenReturnBool(vm, true); + return; + } + + // Mark it closed immediately. + *foreign = -1; + + uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1)); + uv_fs_close(getLoop(), request, fd, closeCallback); + wrenReturnBool(vm, false); +} + +void fileDescriptor(WrenVM* vm) +{ + int* foreign = (int*)wrenGetArgumentForeign(vm, 0); + int fd = *foreign; + wrenReturnDouble(vm, fd); +} + +static void readBytesCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + uv_buf_t buffer = request->bufs[0]; + WrenValue* fiber = freeRequest(request); + + // TODO: Having to copy the bytes here is a drag. It would be good if Wren's + // embedding API supported a way to *give* it bytes that were previously + // allocated using Wren's own allocator. + schedulerResumeBytes(fiber, buffer.base, buffer.len); + + // TODO: Likewise, freeing this after we resume is lame. + free(buffer.base); +} + +void fileReadBytes(WrenVM* vm) +{ + uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 2)); + + int fd = *(int*)wrenGetArgumentForeign(vm, 0); + // TODO: Assert fd != -1. + + uv_buf_t buffer; + buffer.len = (size_t)wrenGetArgumentDouble(vm, 1); + buffer.base = (char*)malloc(buffer.len); + + // TODO: Allow passing in offset. + uv_fs_read(getLoop(), request, fd, &buffer, 1, 0, readBytesCallback); +} + +void fileSize(WrenVM* vm) +{ + uv_fs_t* request = createRequest(wrenGetArgumentValue(vm, 1)); + + int fd = *(int*)wrenGetArgumentForeign(vm, 0); + // TODO: Assert fd != -1. + + uv_fs_fstat(getLoop(), request, fd, sizeCallback); +} diff --git a/src/module/io.wren b/src/module/io.wren index a44c2d25..6320ce7b 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -1,15 +1,69 @@ import "scheduler" for Scheduler -class File { - static size(path) { +foreign class File { + static open(path) { if (!(path is String)) Fiber.abort("Path must be a string.") - startSize_(path, Fiber.current) - var result = Scheduler.runNextScheduled_() - if (result is String) Fiber.abort(result) + open_(path, Fiber.current) + var fd = Scheduler.runNextScheduled_() + return new_(fd) + } + static open(path, fn) { + var file = open(path) + var fiber = Fiber.new { fn.call(file) } + + // Poor man's finally. Can we make this more elegant? + var result = fiber.try() + file.close() + + // TODO: Want something like rethrow since now the callstack ends here. :( + if (fiber.error != null) Fiber.abort(fiber.error) return result } - foreign static startSize_(path, fiber) + static read(path) { + return File.open(path) {|file| file.readBytes(file.size) } + } + + static size(path) { + if (!(path is String)) Fiber.abort("Path must be a string.") + + sizePath_(path, Fiber.current) + return Scheduler.runNextScheduled_() + } + + construct new_(fd) {} + + close() { + if (close_(Fiber.current)) return + Scheduler.runNextScheduled_() + } + + isOpen { descriptor != -1 } + + size { + if (!isOpen) Fiber.abort("File is not open.") + + size_(Fiber.current) + return Scheduler.runNextScheduled_() + } + + readBytes(count) { + if (!isOpen) Fiber.abort("File is not open.") + if (!(count is Num)) Fiber.abort("Count must be an integer.") + if (!count.isInteger) Fiber.abort("Count must be an integer.") + if (count < 0) Fiber.abort("Count cannot be negative.") + + readBytes_(count, Fiber.current) + return Scheduler.runNextScheduled_() + } + + foreign static open_(path, fiber) + foreign static sizePath_(path, fiber) + + foreign close_(fiber) + foreign descriptor + foreign readBytes_(count, fiber) + foreign size_(fiber) } diff --git a/src/module/io.wren.inc b/src/module/io.wren.inc index 91f91f2f..4cbfa968 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -2,16 +2,70 @@ static const char* ioModuleSource = "import \"scheduler\" for Scheduler\n" "\n" -"class File {\n" -" static size(path) {\n" +"foreign class File {\n" +" static open(path) {\n" " if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" "\n" -" startSize_(path, Fiber.current)\n" -" var result = Scheduler.runNextScheduled_()\n" -" if (result is String) Fiber.abort(result)\n" +" open_(path, Fiber.current)\n" +" var fd = Scheduler.runNextScheduled_()\n" +" return new_(fd)\n" +" }\n" "\n" +" static open(path, fn) {\n" +" var file = open(path)\n" +" var fiber = Fiber.new { fn.call(file) }\n" +"\n" +" // Poor man's finally. Can we make this more elegant?\n" +" var result = fiber.try()\n" +" file.close()\n" +"\n" +" // TODO: Want something like rethrow since now the callstack ends here. :(\n" +" if (fiber.error != null) Fiber.abort(fiber.error)\n" " return result\n" " }\n" "\n" -" foreign static startSize_(path, fiber)\n" +" static read(path) {\n" +" return File.open(path) {|file| file.readBytes(file.size) }\n" +" }\n" +"\n" +" static size(path) {\n" +" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" +"\n" +" sizePath_(path, Fiber.current)\n" +" return Scheduler.runNextScheduled_()\n" +" }\n" +"\n" +" construct new_(fd) {}\n" +"\n" +" close() {\n" +" if (close_(Fiber.current)) return\n" +" Scheduler.runNextScheduled_()\n" +" }\n" +"\n" +" isOpen { descriptor != -1 }\n" +"\n" +" size {\n" +" if (!isOpen) Fiber.abort(\"File is not open.\")\n" +"\n" +" size_(Fiber.current)\n" +" return Scheduler.runNextScheduled_()\n" +" }\n" +"\n" +" readBytes(count) {\n" +" if (!isOpen) Fiber.abort(\"File is not open.\")\n" +" if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\n" +" if (!count.isInteger) Fiber.abort(\"Count must be an integer.\")\n" +" if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n" +"\n" +" readBytes_(count, Fiber.current)\n" +" return Scheduler.runNextScheduled_()\n" +" }\n" +"\n" +" foreign static open_(path, fiber)\n" +" foreign static sizePath_(path, fiber)\n" +"\n" +" foreign close_(fiber)\n" +" foreign descriptor\n" +" foreign readBytes_(count, fiber)\n" +" foreign size_(fiber)\n" "}\n"; diff --git a/src/module/scheduler.c b/src/module/scheduler.c index 3e911e9d..7060fbb3 100644 --- a/src/module/scheduler.c +++ b/src/module/scheduler.c @@ -12,33 +12,63 @@ // one. static WrenValue* resume; static WrenValue* resumeWithArg; +static WrenValue* resumeError; void schedulerCaptureMethods(WrenVM* vm) { resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)"); resumeWithArg = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_,_)"); + resumeError = wrenGetMethod(vm, "scheduler", "Scheduler", "resumeError_(_,_)"); +} + +static void callResume(WrenValue* resumeMethod, WrenValue* fiber, + const char* argTypes, ...) +{ + va_list args; + va_start(args, argTypes); + WrenInterpretResult result = wrenCallVarArgs(getVM(), resumeMethod, NULL, + argTypes, args); + va_end(args); + + wrenReleaseValue(getVM(), fiber); + + // If a runtime error occurs in response to an async operation and nothing + // catches the error in the fiber, then exit the CLI. + if (result == WREN_RESULT_RUNTIME_ERROR) + { + uv_stop(getLoop()); + setExitCode(70); // EX_SOFTWARE. + } } void schedulerResume(WrenValue* fiber) { - wrenCall(getVM(), resume, NULL, "v", fiber); - wrenReleaseValue(getVM(), fiber); + callResume(resume, fiber, "v", fiber); +} + +void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length) +{ + callResume(resumeWithArg, fiber, "va", fiber, bytes, length); } void schedulerResumeDouble(WrenValue* fiber, double value) { - wrenCall(getVM(), resumeWithArg, NULL, "vd", fiber, value); - wrenReleaseValue(getVM(), fiber); + callResume(resumeWithArg, fiber, "vd", fiber, value); } void schedulerResumeString(WrenValue* fiber, const char* text) { - wrenCall(getVM(), resumeWithArg, NULL, "vs", fiber, text); - wrenReleaseValue(getVM(), fiber); + callResume(resumeWithArg, fiber, "vs", fiber, text); +} + +void schedulerResumeError(WrenValue* fiber, const char* error) +{ + callResume(resumeError, fiber, "vs", fiber, error); } void schedulerReleaseMethods() { if (resume != NULL) wrenReleaseValue(getVM(), resume); if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg); + if (resumeError != NULL) wrenReleaseValue(getVM(), resumeError); } diff --git a/src/module/scheduler.h b/src/module/scheduler.h index 0276e9a6..3bec2be8 100644 --- a/src/module/scheduler.h +++ b/src/module/scheduler.h @@ -4,8 +4,10 @@ #include "wren.h" void schedulerResume(WrenValue* fiber); +void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length); void schedulerResumeDouble(WrenValue* fiber, double value); void schedulerResumeString(WrenValue* fiber, const char* text); +void schedulerResumeError(WrenValue* fiber, const char* error); void schedulerReleaseMethods(); diff --git a/src/module/scheduler.wren b/src/module/scheduler.wren index 9c0459a7..b7b4c764 100644 --- a/src/module/scheduler.wren +++ b/src/module/scheduler.wren @@ -11,6 +11,7 @@ class Scheduler { // Called by native code. static resume_(fiber) { fiber.transfer() } static resume_(fiber, arg) { fiber.transfer(arg) } + static resumeError_(fiber, error) { fiber.transferError(error) } static runNextScheduled_() { if (__scheduled == null || __scheduled.isEmpty) { diff --git a/src/module/scheduler.wren.inc b/src/module/scheduler.wren.inc index e3eed1b8..98de7569 100644 --- a/src/module/scheduler.wren.inc +++ b/src/module/scheduler.wren.inc @@ -13,6 +13,7 @@ static const char* schedulerModuleSource = " // Called by native code.\n" " static resume_(fiber) { fiber.transfer() }\n" " static resume_(fiber, arg) { fiber.transfer(arg) }\n" +" static resumeError_(fiber, error) { fiber.transferError(error) }\n" "\n" " static runNextScheduled_() {\n" " if (__scheduled == null || __scheduled.isEmpty) {\n" diff --git a/test/io/file/close.wren b/test/io/file/close.wren new file mode 100644 index 00000000..1cfeca54 --- /dev/null +++ b/test/io/file/close.wren @@ -0,0 +1,19 @@ +import "io" for File +import "scheduler" for Scheduler + +// See also: is_open.wren. + +var file = File.open("test/io/file/close.wren") + +System.print(file.close()) // expect: null + +// Can call multiple times. +file.close() + +// If already closed, returns synchronously. +Scheduler.add { + System.print("does not print") +} + +file.close() +System.print("sync") // expect: sync diff --git a/test/io/file/descriptor.wren b/test/io/file/descriptor.wren new file mode 100644 index 00000000..41ad2b15 --- /dev/null +++ b/test/io/file/descriptor.wren @@ -0,0 +1,11 @@ +import "io" for File + +var file = File.open("test/io/file/descriptor.wren") + +// We can't test for a specific value since it's up to the OS, but it should be +// a positive number. +System.print(file.descriptor is Num) // expect: true +System.print(file.descriptor > 0) // expect: true + +file.close() +System.print(file.descriptor) // expect: -1 diff --git a/test/io/file/file.txt b/test/io/file/file.txt new file mode 100644 index 00000000..e068544d --- /dev/null +++ b/test/io/file/file.txt @@ -0,0 +1 @@ +this is a text file \ No newline at end of file diff --git a/test/io/file/is_open.wren b/test/io/file/is_open.wren new file mode 100644 index 00000000..7e3a0568 --- /dev/null +++ b/test/io/file/is_open.wren @@ -0,0 +1,6 @@ +import "io" for File + +var file = File.open("test/io/file/is_open.wren") +System.print(file.isOpen) // expect: true +file.close() +System.print(file.isOpen) // expect: false diff --git a/test/io/file/open.wren b/test/io/file/open.wren new file mode 100644 index 00000000..2a24f243 --- /dev/null +++ b/test/io/file/open.wren @@ -0,0 +1,5 @@ +import "io" for File + +var file = File.open("test/io/file/open.wren") +System.print(file is File) // expect: true +System.print(file.isOpen) // expect: true diff --git a/test/io/file/open_block.wren b/test/io/file/open_block.wren new file mode 100644 index 00000000..e03bede8 --- /dev/null +++ b/test/io/file/open_block.wren @@ -0,0 +1,16 @@ +import "io" for File + +var stash +File.open("test/io/file/open_block.wren") {|file| + System.print(file is File) // expect: true + System.print(file.isOpen) // expect: true + stash = file +} + +// Closes after block. +System.print(stash.isOpen) // expect: false + +// Returns null. +System.print(File.open("test/io/file/open_block.wren") {|file|}) // expect: null + +// TODO: Test a fiber aborting inside the block. diff --git a/test/io/file/open_block_nonexistent.wren b/test/io/file/open_block_nonexistent.wren new file mode 100644 index 00000000..85463109 --- /dev/null +++ b/test/io/file/open_block_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.open("nonexistent") {|file|} // expect runtime error: no such file or directory diff --git a/test/io/file/open_block_wrong_block_type.wren b/test/io/file/open_block_wrong_block_type.wren new file mode 100644 index 00000000..f92c0fee --- /dev/null +++ b/test/io/file/open_block_wrong_block_type.wren @@ -0,0 +1,4 @@ +import "io" for File + +File.open("test/io/file/open_block_wrong_block_type.wren", + "no callable") // expect runtime error: String does not implement 'call(_)'. diff --git a/test/io/file/open_block_wrong_path_type.wren b/test/io/file/open_block_wrong_path_type.wren new file mode 100644 index 00000000..c6fb257c --- /dev/null +++ b/test/io/file/open_block_wrong_path_type.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.open(123) {|file|} // expect runtime error: Path must be a string. diff --git a/test/io/file/open_nonexistent.wren b/test/io/file/open_nonexistent.wren new file mode 100644 index 00000000..dfeb2e3b --- /dev/null +++ b/test/io/file/open_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.open("nonexistent") // expect runtime error: no such file or directory diff --git a/test/io/file/open_wrong_arg_type.wren b/test/io/file/open_wrong_arg_type.wren new file mode 100644 index 00000000..cccc9217 --- /dev/null +++ b/test/io/file/open_wrong_arg_type.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.open(123) // expect runtime error: Path must be a string. diff --git a/test/io/file/read.wren b/test/io/file/read.wren new file mode 100644 index 00000000..05fa5a71 --- /dev/null +++ b/test/io/file/read.wren @@ -0,0 +1,8 @@ +import "io" for File + +var text = File.read("test/io/file/file.txt") +System.print(text) // expect: this is a text file +System.print(text.count) // expect: 19 + +// TODO: A file containing line endings. +// TODO: A file containing null bytes. diff --git a/test/io/file/read_bytes.wren b/test/io/file/read_bytes.wren new file mode 100644 index 00000000..44297cb4 --- /dev/null +++ b/test/io/file/read_bytes.wren @@ -0,0 +1,13 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") + +System.print(file.readBytes(3)) // expect: thi + +// Always reads from the beginning. +System.print(file.readBytes(7)) // expect: this is + +// Allows zero. +System.print(file.readBytes(0).bytes.count) // expect: 0 + +file.close() diff --git a/test/io/file/read_bytes_after_close.wren b/test/io/file/read_bytes_after_close.wren new file mode 100644 index 00000000..a9b026b9 --- /dev/null +++ b/test/io/file/read_bytes_after_close.wren @@ -0,0 +1,6 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.close() + +file.readBytes(3) // expect runtime error: File is not open. diff --git a/test/io/file/read_bytes_count_not_num.wren b/test/io/file/read_bytes_count_not_num.wren new file mode 100644 index 00000000..6f25a7c6 --- /dev/null +++ b/test/io/file/read_bytes_count_not_num.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes("not num") // expect runtime error: Count must be an integer. diff --git a/test/io/file/read_bytes_negative.wren b/test/io/file/read_bytes_negative.wren new file mode 100644 index 00000000..5eec2307 --- /dev/null +++ b/test/io/file/read_bytes_negative.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(-1) // expect runtime error: Count cannot be negative. diff --git a/test/io/file/read_bytes_not_integer.wren b/test/io/file/read_bytes_not_integer.wren new file mode 100644 index 00000000..e02fbd8d --- /dev/null +++ b/test/io/file/read_bytes_not_integer.wren @@ -0,0 +1,4 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.readBytes(1.2) // expect runtime error: Count must be an integer. diff --git a/test/io/file/read_nonexistent.wren b/test/io/file/read_nonexistent.wren new file mode 100644 index 00000000..567b9eba --- /dev/null +++ b/test/io/file/read_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.read("nonexistent") // expect runtime error: no such file or directory diff --git a/test/io/file/read_wrong_arg_type.wren b/test/io/file/read_wrong_arg_type.wren new file mode 100644 index 00000000..228746dc --- /dev/null +++ b/test/io/file/read_wrong_arg_type.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.read(123) // expect runtime error: Path must be a string. diff --git a/test/io/file/size.wren b/test/io/file/size.wren index b1a1c547..84e72eab 100644 --- a/test/io/file/size.wren +++ b/test/io/file/size.wren @@ -1,7 +1,7 @@ import "io" for File import "scheduler" for Scheduler -System.print(File.size("test/io/file/size.wren")) // expect: 401 +System.print(File.size("test/io/file/size.wren")) // expect: 270 // Runs asynchronously. Scheduler.add { @@ -10,9 +10,4 @@ Scheduler.add { System.print(File.size("test/io/file/size.wren")) // expect: async -// expect: 401 - -var error = Fiber.new { - System.print(File.size("nonexistent")) -}.try() -System.print(error) // expect: no such file or directory +// expect: 270 diff --git a/test/io/file/size_after_close.wren b/test/io/file/size_after_close.wren new file mode 100644 index 00000000..7e545fa4 --- /dev/null +++ b/test/io/file/size_after_close.wren @@ -0,0 +1,6 @@ +import "io" for File + +var file = File.open("test/io/file/file.txt") +file.close() + +file.size // expect runtime error: File is not open. diff --git a/test/io/file/size_nonexistent.wren b/test/io/file/size_nonexistent.wren new file mode 100644 index 00000000..950065a5 --- /dev/null +++ b/test/io/file/size_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.size("nonexistent") // expect runtime error: no such file or directory