From c56d6e8fc137fc5bd1a324e823084c1a6f228adc Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 18 Aug 2024 09:45:21 -0700 Subject: [PATCH] Add suite for testing filewatch. Currently expects windows events. --- meson.build | 1 + src/core/filewatch.c | 48 +++++++---------- test/helper.janet | 17 ++++++ test/suite-bundle.janet | 14 +---- test/suite-filewatch.janet | 108 +++++++++++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 test/suite-filewatch.janet diff --git a/meson.build b/meson.build index fd76ed05a..039fe489e 100644 --- a/meson.build +++ b/meson.build @@ -259,6 +259,7 @@ test_files = [ 'test/suite-debug.janet', 'test/suite-ev.janet', 'test/suite-ffi.janet', + 'test/suite-filewatch.janet', 'test/suite-inttypes.janet', 'test/suite-io.janet', 'test/suite-marsh.janet', diff --git a/src/core/filewatch.c b/src/core/filewatch.c index deceb0eaa..79f02ef0f 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -47,7 +47,7 @@ typedef struct { #ifndef JANET_WINDOWS JanetStream *stream; #endif - JanetTable watch_descriptors; + JanetTable* watch_descriptors; JanetChannel *channel; uint32_t default_flags; int is_watching; @@ -115,7 +115,7 @@ static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uin if (fd == -1) { janet_panicv(janet_ev_lasterr()); } - janet_table_init_raw(&watcher->watch_descriptors, 0); + watcher->watch_descriptors = janet_table(0); watcher->channel = channel; watcher->default_flags = default_flags; watcher->is_watching = 0; @@ -133,13 +133,13 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t } Janet name = janet_cstringv(path); Janet wd = janet_wrap_integer(result); - janet_table_put(&watcher->watch_descriptors, name, wd); - janet_table_put(&watcher->watch_descriptors, wd, name); + janet_table_put(watcher->watch_descriptors, name, wd); + janet_table_put(watcher->watch_descriptors, wd, name); } static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { if (watcher->stream == NULL) janet_panic("watcher closed"); - Janet check = janet_table_get(&watcher->watch_descriptors, janet_cstringv(path)); + Janet check = janet_table_get(watcher->watch_descriptors, janet_cstringv(path)); janet_assert(janet_checktype(check, JANET_NUMBER), "bad watch descriptor"); int watch_handle = janet_unwrap_integer(check); int result; @@ -225,7 +225,7 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { if (!janet_watch_filter(watcher, name, inevent.wd)) continue; /* Got an event */ - Janet path = janet_table_get(&watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); + Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); JanetKV *event = janet_struct_begin(6); janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd)); janet_struct_put(event, janet_ckeywordv("wd-path"), path); @@ -299,7 +299,7 @@ static uint32_t decode_watch_flags(const Janet *options, int32_t n) { } static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { - janet_table_init_raw(&watcher->watch_descriptors, 0); + watcher->watch_descriptors = janet_table(0); watcher->channel = channel; watcher->default_flags = default_flags; watcher->is_watching = 0; @@ -361,7 +361,7 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { janet_mark(janet_wrap_string(ow->dir_path)); break; case JANET_ASYNC_EVENT_CLOSE: - janet_table_remove(&ow->watcher->watch_descriptors, janet_wrap_string(ow->dir_path)); + janet_table_remove(ow->watcher->watch_descriptors, janet_wrap_string(ow->dir_path)); break; case JANET_ASYNC_EVENT_ERR: case JANET_ASYNC_EVENT_FAILED: @@ -444,7 +444,7 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t ow->watcher = watcher; ow->overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */ Janet streamv = janet_wrap_pointer(ow); - janet_table_put(&watcher->watch_descriptors, pathv, streamv); + janet_table_put(watcher->watch_descriptors, pathv, streamv); if (watcher->is_watching) { start_listening_ow(ow); } @@ -452,11 +452,11 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { Janet pathv = janet_cstringv(path); - Janet streamv = janet_table_get(&watcher->watch_descriptors, pathv); + Janet streamv = janet_table_get(watcher->watch_descriptors, pathv); if (janet_checktype(streamv, JANET_NIL)) { janet_panicf("path %v is not being watched", pathv); } - janet_table_remove(&watcher->watch_descriptors, pathv); + janet_table_remove(watcher->watch_descriptors, pathv); OverlappedWatch *ow = janet_unwrap_pointer(streamv); janet_stream_close(ow->stream); } @@ -464,8 +464,8 @@ static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { static void janet_watcher_listen(JanetWatcher *watcher) { if (watcher->is_watching) janet_panic("already watching"); watcher->is_watching = 1; - for (int32_t i = 0; i < watcher->watch_descriptors.capacity; i++) { - const JanetKV *kv = watcher->watch_descriptors.data + i; + for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { + const JanetKV *kv = watcher->watch_descriptors->data + i; if (!janet_checktype(kv->value, JANET_POINTER)) continue; OverlappedWatch *ow = janet_unwrap_pointer(kv->value); start_listening_ow(ow); @@ -475,13 +475,13 @@ static void janet_watcher_listen(JanetWatcher *watcher) { static void janet_watcher_unlisten(JanetWatcher *watcher) { if (!watcher->is_watching) return; watcher->is_watching = 0; - for (int32_t i = 0; i < watcher->watch_descriptors.capacity; i++) { - const JanetKV *kv = watcher->watch_descriptors.data + i; + for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { + const JanetKV *kv = watcher->watch_descriptors->data + i; if (!janet_checktype(kv->value, JANET_POINTER)) continue; OverlappedWatch *ow = janet_unwrap_pointer(kv->value); janet_stream_close(ow->stream); } - janet_table_clear(&watcher->watch_descriptors); + janet_table_clear(watcher->watch_descriptors); } #else @@ -533,8 +533,8 @@ static int janet_filewatch_mark(void *p, size_t s) { (void) s; if (watcher->channel == NULL) return 0; /* Incomplete initialization */ #ifdef JANET_WINDOWS - for (int32_t i = 0; i < watcher->watch_descriptors.capacity; i++) { - const JanetKV *kv = watcher->watch_descriptors.data + i; + for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { + const JanetKV *kv = watcher->watch_descriptors->data + i; if (!janet_checktype(kv->value, JANET_POINTER)) continue; OverlappedWatch *ow = janet_unwrap_pointer(kv->value); janet_mark(janet_wrap_fiber(ow->fiber)); @@ -545,21 +545,13 @@ static int janet_filewatch_mark(void *p, size_t s) { janet_mark(janet_wrap_abstract(watcher->stream)); #endif janet_mark(janet_wrap_abstract(watcher->channel)); - janet_mark(janet_wrap_table(&watcher->watch_descriptors)); - return 0; -} - -static int janet_filewatch_gc(void *p, size_t s) { - JanetWatcher *watcher = (JanetWatcher *) p; - if (watcher->channel == NULL) return 0; /* Incomplete initialization */ - (void) s; - janet_table_deinit(&watcher->watch_descriptors); + janet_mark(janet_wrap_table(watcher->watch_descriptors)); return 0; } static const JanetAbstractType janet_filewatch_at = { "filewatch/watcher", - janet_filewatch_gc, + NULL, janet_filewatch_mark, JANET_ATEND_GCMARK }; diff --git a/test/helper.janet b/test/helper.janet index aed838977..d6d9a0930 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -64,3 +64,20 @@ (eprinf "Finished suite %s in %.3f seconds - " suite-name delta) (eprint num-tests-passed " of " num-tests-run " tests passed.") (if (not= num-tests-passed num-tests-run) (os/exit 1))) + +(defn rmrf + "rm -rf in janet" + [x] + (case (os/lstat x :mode) + nil nil + :directory (do + (each y (os/dir x) + (rmrf (string x "/" y))) + (os/rmdir x)) + (os/rm x)) + nil) + +(defn randdir + "Get a random directory name" + [] + (string "tmp_dir_" (slice (string (math/random) ".tmp") 2))) diff --git a/test/suite-bundle.janet b/test/suite-bundle.janet index 3b9d1b962..a0f4c031e 100644 --- a/test/suite-bundle.janet +++ b/test/suite-bundle.janet @@ -30,25 +30,13 @@ [path] (string/replace-all "\\" "/" (os/realpath path))) -(defn- rmrf - "rm -rf in janet" - [x] - (case (os/lstat x :mode) - nil nil - :directory (do - (each y (os/dir x) - (rmrf (string x "/" y))) - (os/rmdir x)) - (os/rm x)) - nil) - # Test mkdir -> rmdir (assert (os/mkdir "tempdir123")) (rmrf "tempdir123") # Setup a temporary syspath for manipultation (math/seedrandom (os/cryptorand 16)) -(def syspath (string (math/random) "_jpm_tree.tmp")) +(def syspath (randdir)) (rmrf syspath) (assert (os/mkdir syspath)) (put root-env *syspath* (bundle-rpath syspath)) diff --git a/test/suite-filewatch.janet b/test/suite-filewatch.janet new file mode 100644 index 000000000..2586a839a --- /dev/null +++ b/test/suite-filewatch.janet @@ -0,0 +1,108 @@ +# Copyright (c) 2024 Calvin Rose +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +(import ./helper :prefix "" :exit true) +(start-suite) + +(assert true) + +(def chan (ev/chan 1000)) + +# Test GC +(assert-no-error "filewatch/new" (filewatch/new chan)) +(gccollect) + +(defn- expect + [key value] + (ev/with-deadline + 1 + (def event (ev/take chan)) + (when is-verbose (pp event)) + (assert event "check event") + (assert (= value (get event key)) (string/format "got %p, exepcted %p" (get event key) value)))) + +(defn- expect-empty + [] + (assert (zero? (ev/count chan)) "channel check empty") + (ev/sleep 0) # turn the event loop + (assert (zero? (ev/count chan)) "channel check empty")) + +(defn spit-file + [dir name] + (def path (string dir "/" name)) + (spit path "test text")) + +# Create a file watcher on two test directories +(def fw (filewatch/new chan)) +(def td1 (randdir)) +(def td2 (randdir)) +(os/mkdir td1) +(os/mkdir td2) +(filewatch/add fw td1 :last-write :last-access :file-name :dir-name :size :attributes :recursive) +(filewatch/add fw td2 :last-write :last-access :file-name :dir-name :size :attributes) +(assert-no-error "filewatch/listen no error" (filewatch/listen fw)) + +# Simulate some file event +(spit-file td1 "file1.txt") +(expect :action :added) +(expect :action :modified) +(expect-empty) +(gccollect) +(spit-file td1 "file1.txt") +(expect :action :modified) +(expect :action :modified) +(expect-empty) +(gccollect) + +# Check td2 +(spit-file td2 "file2.txt") +(expect :action :added) +(expect :action :modified) +(expect-empty) + +# Remove a file, then wait for remove event +(rmrf (string td1 "/file1.txt")) +(expect :action :removed) +(expect-empty) + +# Unlisten to some events +(filewatch/remove fw td2) + +# Check that we don't get anymore events from test directory 2 +(spit-file td2 "file2.txt") +(expect-empty) + +# Repeat and things should still work with test directory 1 +(spit-file td1 "file1.txt") +(expect :action :added) +(expect :action :modified) +(expect-empty) +(gccollect) +(spit-file td1 "file1.txt") +(expect :action :modified) +(expect :action :modified) +(expect-empty) +(gccollect) + +(assert-no-error "filewatch/unlisten no error" (filewatch/unlisten fw)) +(assert-no-error "cleanup 1" (rmrf td1)) +(assert-no-error "cleanup 2" (rmrf td2)) + +(end-suite)