├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── bam.lua ├── include └── fswatcher │ └── fswatcher.h ├── src ├── fswatcher.cpp ├── fswatcher_linux.cpp ├── fswatcher_osx.cpp └── fswatcher_windows.cpp ├── test ├── fswatcher_tests.cpp └── greatest.h └── tester └── fswatch_tester.cpp /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - gcc 4 | - clang 5 | 6 | install: 7 | - git clone https://github.com/matricks/bam.git 8 | - cd bam 9 | - ./make_unix.sh 10 | - cd .. 11 | 12 | script: 13 | - bam/bam compiler=$CC config=debug -r sc test 14 | - bam/bam compiler=$CC config=release -r sc test 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | A small drop-in library for watching the filesystem for changes. 2 | 3 | version 0.1, february, 2015 4 | 5 | Copyright (C) 2015- Fredrik Kihlander 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | Fredrik Kihlander 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/wc-duck/fswatcher.svg?branch=master)](https://travis-ci.org/wc-duck/fswatcher) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/yso5i3uaai2ibmi4?svg=true)](https://ci.appveyor.com/project/wc-duck/fswatcher) 3 | 4 | What is it? 5 | ----------- 6 | fswatcher is exactly what it sounds like, a platform independet wrapping of the different filesystem watcher systems on different platforms. 7 | 8 | Note that it is currently under development and there are bugs and blemishes all over the place. Bugfixes/optimizations/implementations for other platforms is always appreciated! 9 | 10 | Currently supported platforms: 11 | * Linux via inotify 12 | * Windows via ReadDirectoryChangesW 13 | 14 | OSX port is worked on. 15 | 16 | Why writing this? 17 | ----------------- 18 | 19 | Simply put to scratch my own itch with me not finding a library to do this written in a way that I like and with not that much code. 20 | The goal is not to make the most fully functional filesystem watcher ever, but rather a library that cover most peoples needs in a stable way. 21 | 22 | Rules of engagement 23 | ------------------- 24 | 25 | The user always knows better how his/her system works, we can't decide on how the user allocates memory in their app or how the work with threads. So all memory allocations that is made by the library should be well defined how they work and be done by a user-provided allocator if provided. 26 | Also the library is poll-based ( blocking or non-blocking ) so the user can decide for them self if placing it in its own thread or not is the way to go. 27 | 28 | Path and unicode 29 | ---------------- 30 | 31 | Different os:es = different encodings for paths =/ 32 | The way fswatcher deals with this is to always input and output utf8 encoded strings. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: 2 | - x86 3 | 4 | image: 5 | - Visual Studio 2019 6 | 7 | install: 8 | - git clone https://github.com/matricks/bam.git 9 | - cd bam 10 | - make_win64_msvc.bat 11 | - cd .. 12 | - bam\bam.exe config=debug -r sc test 13 | - bam\bam.exe config=release -r sc test 14 | 15 | build: OFF 16 | -------------------------------------------------------------------------------- /bam.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | --]] 26 | 27 | BUILD_PATH = "local" 28 | 29 | function get_config() 30 | local config = ScriptArgs["config"] 31 | if config == nil then 32 | return "debug" 33 | end 34 | return config 35 | end 36 | 37 | function get_platform() 38 | local platform = ScriptArgs["platform"] 39 | if platform == nil then 40 | if family == "windows" then 41 | platform = "winx64" 42 | else 43 | platform = "linux_x86_64" 44 | end 45 | end 46 | return platform 47 | end 48 | 49 | function get_base_settings() 50 | local settings = {} 51 | 52 | settings._is_settingsobject = true 53 | settings.invoke_count = 0 54 | settings.debug = 0 55 | settings.optimize = 0 56 | SetCommonSettings(settings) 57 | 58 | -- add all tools 59 | for _, tool in pairs(_bam_tools) do 60 | tool(settings) 61 | end 62 | 63 | settings.cc.includes:Add( "include" ) 64 | 65 | return settings 66 | end 67 | 68 | function set_compiler( settings, config ) 69 | if family == "windows" then 70 | compiler = "msvc" 71 | else 72 | compiler = ScriptArgs["compiler"] 73 | if compiler == nil then 74 | compiler = "gcc" 75 | end 76 | end 77 | 78 | InitCommonCCompiler(settings) 79 | if compiler == "msvc" then 80 | SetDriversCL( settings ) 81 | if config == "release" then 82 | settings.cc.flags:Add( "/Ox" ) 83 | settings.cc.flags:Add( "/TP" ) -- forcing c++ compile on windows =/ 84 | end 85 | elseif compiler == "gcc" then 86 | SetDriversGCC( settings ) 87 | settings.cc.flags:Add( "-Wconversion", "-Wextra", "-Wall", "-Werror", "-Wstrict-aliasing=2" ) 88 | if config == "release" then 89 | settings.cc.flags:Add( "-O2" ) 90 | end 91 | elseif compiler == "clang" then 92 | SetDriversClang( settings ) 93 | settings.cc.flags:Add( "-Wconversion", "-Wextra", "-Wall", "-Werror", "-Wstrict-aliasing=2" ) 94 | if config == "release" then 95 | settings.cc.flags:Add( "-O2" ) 96 | end 97 | end 98 | end 99 | 100 | config = get_config() 101 | platform = get_platform() 102 | settings = get_base_settings() 103 | set_compiler( settings, config ) 104 | TableLock( settings ) 105 | 106 | local output_path = PathJoin( BUILD_PATH, PathJoin( platform, config ) ) 107 | local output_func = function(settings, path) return PathJoin(output_path, PathFilename(PathBase(path)) .. settings.config_ext) end 108 | settings.cc.Output = output_func 109 | settings.lib.Output = output_func 110 | settings.link.Output = output_func 111 | 112 | local objs = Compile( settings, 'src/fswatcher.cpp' ) 113 | local lib = StaticLibrary( settings, 'fswatcher', objs ) 114 | 115 | local tester = Link( settings, 'fswatch_tester', Compile( settings, 'tester/fswatch_tester.cpp' ), lib ) 116 | 117 | local test_objs = Compile( settings, 'test/fswatcher_tests.cpp' ) 118 | local tests = Link( settings, 'fswatcher_tests', test_objs, lib ) 119 | 120 | test_args = " -v" 121 | if ScriptArgs["test"] then test_args = test_args .. " -t " .. ScriptArgs["test"] end 122 | if ScriptArgs["suite"] then test_args = test_args .. " -s " .. ScriptArgs["suite"] end 123 | 124 | if family == "windows" then 125 | AddJob( "test", "unittest", string.gsub( tests, "/", "\\" ) .. test_args, tests, tests ) 126 | else 127 | AddJob( "test", "unittest", tests .. test_args, tests, tests ) 128 | AddJob( "valgrind", "valgrind", "valgrind -v --leak-check=full --track-origins=yes " .. tests .. test_args, tests, tests ) 129 | end 130 | 131 | PseudoTarget( "all", tests, tester ) 132 | DefaultTarget( "all" ) 133 | 134 | -------------------------------------------------------------------------------- /include/fswatcher/fswatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #ifndef FSWATCHER_H_INCLUDED 28 | #define FSWATCHER_H_INCLUDED 29 | 30 | #include // for size_t 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif // __cplusplus 35 | 36 | /** 37 | * A note on paths. All paths in and out of the library is utf8 encoded. 38 | */ 39 | 40 | /** 41 | * 42 | */ 43 | enum fswatcher_create_flags 44 | { 45 | FSWATCHER_CREATE_BLOCKING = (1 << 1), ///< calls to fswatcher_poll should block until 1 or more events arrive. 46 | FSWATCHER_CREATE_RECURSIVE = (1 << 2), ///< the directory watch should recursively add all sub-directories to watch. 47 | FSWATCHER_CREATE_DEFAULT = FSWATCHER_CREATE_RECURSIVE 48 | }; 49 | 50 | /** 51 | * 52 | */ 53 | enum fswatcher_event_type 54 | { 55 | FSWATCHER_EVENT_CREATE = (1 << 1), ///< file in "src" was just created. 56 | FSWATCHER_EVENT_REMOVE = (1 << 2), ///< file in "src" was just removed. 57 | FSWATCHER_EVENT_MODIFY = (1 << 3), ///< file in "src" was just modified. 58 | FSWATCHER_EVENT_MOVE = (1 << 4), ///< file was moved from "src" to "dst", if "src" or "dst" is 0x0 it indicates that the path was outside the current watch. 59 | 60 | FSWATCHER_EVENT_ALL = FSWATCHER_EVENT_CREATE | 61 | FSWATCHER_EVENT_REMOVE | 62 | FSWATCHER_EVENT_MODIFY | 63 | FSWATCHER_EVENT_MOVE , 64 | 65 | FSWATCHER_EVENT_BUFFER_OVERFLOW ///< doc me 66 | }; 67 | 68 | /** 69 | * 70 | */ 71 | typedef struct fswatcher* fswatcher_t; 72 | 73 | /** 74 | * Allocator interface used by fswatcher, read function-documentation for allocation pattern descriptions. 75 | * 76 | * @example: 77 | * 78 | * struct my_stack_allocator 79 | * { 80 | * fswatcher_allocator alloc; 81 | * 82 | * size_t allocated; 83 | * char buffer[ 4096 ]; 84 | * }; 85 | * 86 | * void* my_stack_realloc( fswatcher_allocator* allocator, void* ptr, size_t old_size, size_t new_size ) 87 | * { 88 | * my_stack_allocator* salloc = (my_stack_allocator*)allocator; 89 | * if( salloc->allocated + new_size > 4096 ) 90 | * return 0x0; 91 | * return salloc->buffer[ salloc->allocated += new_size ]; 92 | * } 93 | * 94 | * void run_poll( fswatcher_t w, fswatcher_event_handler* h ) 95 | * { 96 | * my_stack_allocator salloc = { { my_stack_realloc, 0x0 }, 0 }, 97 | * fswatcher_poll( w, h, &salloc.alloc ); 98 | * } 99 | */ 100 | struct fswatcher_allocator 101 | { 102 | /** 103 | * realloc function, should work in the same way as realloc(). 104 | * 105 | * @param allocator struct holding the function pointer. 106 | * @param ptr to memory to realloc or 0x0 if not allocated. 107 | * @param old_size size of allocation if ptr != 0x0. 108 | * @param new_size requested allocation size. 109 | * 110 | * @return pointer to newly allocated memory or 0x0 on failure. 111 | */ 112 | void* ( *realloc )( fswatcher_allocator* allocator, void* ptr, size_t old_size, size_t new_size ); 113 | 114 | /** 115 | * free function, should be used to free memory allocated with realloc(). 116 | * This function can be set to 0x0 if no free call is needed. 117 | * 118 | * @param allocator struct holding the function pointer. 119 | * @param ptr pointer to memory to free. 120 | */ 121 | void ( *free )( fswatcher_allocator* allocator, void* ptr ); 122 | }; 123 | 124 | /** 125 | * Struct used together with fswatcher_poll() to fetch events from fswatcher. 126 | * 127 | * @example 128 | * 129 | * struct my_fsevent_handler 130 | * { 131 | * fswatcher_event_handler eh; 132 | * 133 | * // ... some userdata here ... 134 | * }; 135 | * 136 | * static bool my_fsevent_func( fswatcher_event_handler* handler, fswatcher_event_type evtype, const char* src, const char* dst ) 137 | * { 138 | * my_fsevent_handler* h = (my_fsevent_handler*)handler; 139 | * 140 | * // ... do stuff in handler ... 141 | * } 142 | * 143 | * void poll_it( fswatcher_t w ) 144 | * { 145 | * my_fsevent_handler h = { my_fsevent_func }; 146 | * fswatcher_poll( w, &h.eh, 0x0 ); 147 | * } 148 | */ 149 | struct fswatcher_event_handler 150 | { 151 | /** 152 | * Callback used per event that is queued on the fswatcher. 153 | * 154 | * @param callback called per event. 155 | * @param evtype type of event received. 156 | * @param src path to source file, can be 0x0 if event has no source. ( this can happen for example on a move if file was moved from non-watched folder to watched folder. ) 157 | * @param dst path to destination file, can be 0x0 if event has no destination. ( this can happen for example on a move if file was moved from watched folder to non-watched folder. ) 158 | * 159 | * @return false if poll should end. 160 | */ 161 | bool ( *callback )( fswatcher_event_handler* handler, fswatcher_event_type evtype, const char* src, const char* dst ); 162 | }; 163 | 164 | /** 165 | * Create a new fswatcher watching a specific directory and potential sub-directories. 166 | * 167 | * @note The allocator passed to this function will all need to persist during the life-time of the fswatcher and might need reallocation. 168 | * 169 | * @param flags ... 170 | * @param types events to watch and get events for. 171 | * @param watch_dir directory to watch. 172 | * @param allocator to use for this fswatcher or 0x0 to use malloc/free 173 | */ 174 | fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ); 175 | 176 | /** 177 | * Destroy fswatcher_t and free all its used resources. 178 | * 179 | * @param watcher to destroy. 180 | */ 181 | void fswatcher_destroy( fswatcher_t watcher ); 182 | 183 | /** 184 | * Poll an fswatcher for new events, this call is blocking if FSWATCHER_CREATE_BLOCKING was passed to fswatcher_create(). 185 | * 186 | * @note This function might allocate data in 2 instances, first if an event was detected that a directory was created, 187 | * in that case the allocator passed to fswatcher_poll() will be used. 188 | * The more usual case is the src/dst parameters to the callback function. These allocations will only be alive during 189 | * the callback and can thusly be allocated with a stack-allocator or other temporary allocator. 190 | * 191 | * @param watcher to poll. 192 | * @param handler to poll events with. 193 | * @param allocator used to allocate temporary data during poll or 0x0 to use malloc/free. 194 | */ 195 | void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ); 196 | 197 | #ifdef __cplusplus 198 | } 199 | #endif // __cplusplus 200 | 201 | #endif // FSWATCHER_H_INCLUDED 202 | -------------------------------------------------------------------------------- /src/fswatcher.cpp: -------------------------------------------------------------------------------- 1 | #if defined( __linux__ ) 2 | # include "fswatcher_linux.cpp" 3 | #elif defined( _WIN32 ) 4 | # include "fswatcher_windows.cpp" 5 | #elif defined( __OSX__ ) 6 | # include "fswatcher_osx.cpp" 7 | #else 8 | # error "Unsupported platform" 9 | #endif 10 | -------------------------------------------------------------------------------- /src/fswatcher_linux.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include // malloc 32 | #include // read 33 | #include // printf 34 | #include 35 | #include 36 | 37 | // write something about how we suppose that the kernels will keep on working as they do now: 38 | // In the current kernel inotify implementation move events are always emitted as contiguous pairs with IN_MOVED_FROM immediately followed by IN_MOVED_TO 39 | 40 | struct fswatcher_item 41 | { 42 | int wd; 43 | const char* path; 44 | }; 45 | 46 | struct fswatcher 47 | { 48 | fswatcher_allocator* allocator; 49 | int notifierfd; 50 | 51 | uint32_t watch_flags; 52 | 53 | size_t watches_cnt; 54 | size_t watches_cap; 55 | fswatcher_item* watches; 56 | }; 57 | 58 | static void* fswatcher_default_realloc( fswatcher_allocator*, void* ptr, size_t, size_t new_size ) 59 | { 60 | return realloc( ptr, new_size ); 61 | } 62 | 63 | static void fswatcher_default_free( fswatcher_allocator*, void* ptr ) 64 | { 65 | free( ptr ); 66 | } 67 | 68 | static fswatcher_allocator g_fswatcher_default_alloc = { fswatcher_default_realloc, fswatcher_default_free }; 69 | 70 | static void* fswatcher_realloc( fswatcher_allocator* allocator, void* ptr, size_t old_size, size_t new_size ) 71 | { 72 | return allocator->realloc( allocator, ptr, old_size, new_size ); 73 | } 74 | 75 | static void fswatcher_free( fswatcher_allocator* allocator, void* ptr ) 76 | { 77 | if( allocator->free ) 78 | allocator->free( allocator, ptr ); 79 | } 80 | 81 | static char* fswatcher_strdup( fswatcher_allocator* allocator, const char* str ) 82 | { 83 | size_t len = strlen( str ) + 1; 84 | char* res = (char*)fswatcher_realloc( allocator, 0x0, 0, len ); 85 | strcpy( res, str ); 86 | return res; 87 | } 88 | 89 | static const char* fswatcher_find_wd_path( fswatcher_t w, int wd ) 90 | { 91 | for( size_t i = 0; i < w->watches_cnt; ++ i ) 92 | if( wd == w->watches[i].wd ) 93 | return w->watches[i].path; 94 | return 0x0; 95 | } 96 | 97 | static void fswatcher_add( fswatcher_t w, char* path ) 98 | { 99 | int wd = inotify_add_watch( w->notifierfd, path, w->watch_flags ); 100 | if( wd < 0 ) 101 | { 102 | fprintf(stderr, "failed to add a watch for %s ", path); 103 | perror(""); 104 | return; 105 | } 106 | if( w->watches_cnt >= w->watches_cap ) 107 | { 108 | w->watches = (fswatcher_item*)fswatcher_realloc( w->allocator, w->watches, sizeof(fswatcher_item) * w->watches_cap, sizeof(fswatcher_item) * w->watches_cap * 2 ); 109 | w->watches_cap *= 2; 110 | } 111 | 112 | w->watches[ w->watches_cnt ].wd = wd; 113 | w->watches[ w->watches_cnt ].path = fswatcher_strdup( w->allocator, path ); 114 | ++w->watches_cnt; 115 | } 116 | 117 | static void fswatcher_remove( fswatcher_t w, int wd ) 118 | { 119 | for( size_t i = 0; i < w->watches_cnt; ++ i ) 120 | { 121 | if( wd != w->watches[i].wd ) 122 | continue; 123 | 124 | fswatcher_free( w->allocator, (void*)w->watches[i].path ); 125 | w->watches[i].wd = 0; 126 | w->watches[i].path = 0x0; 127 | 128 | size_t swap_index = w->watches_cnt - 1; 129 | if( i != swap_index ) 130 | memcpy( w->watches + i, w->watches + swap_index, sizeof( fswatcher_item ) ); 131 | --w->watches_cnt; 132 | return; 133 | } 134 | } 135 | 136 | static void fswatcher_recursive_add( fswatcher_t w, char* path_buffer, size_t path_len, size_t path_max ) 137 | { 138 | fswatcher_add( w, path_buffer ); 139 | DIR* dirp = opendir( path_buffer ); 140 | dirent* ent; 141 | while( ( ent = readdir( dirp ) ) != 0x0 ) 142 | { 143 | if( ( ent->d_type != DT_DIR && ent->d_type != DT_LNK && ent->d_type != DT_UNKNOWN ) ) 144 | continue; 145 | 146 | if( strcmp( ent->d_name, "." ) == 0 || strcmp( ent->d_name, ".." ) == 0 ) 147 | continue; 148 | 149 | size_t d_name_size = strlen( ent->d_name ); 150 | if( path_len + d_name_size + 2 >= path_max ) 151 | return; // TODO: handle! 152 | 153 | strcpy( path_buffer + path_len, ent->d_name ); 154 | path_buffer[ path_len + d_name_size ] = '/'; 155 | path_buffer[ path_len + d_name_size + 1 ] = '\0'; 156 | 157 | if( ent->d_type == DT_LNK || ent->d_type == DT_UNKNOWN ) 158 | { 159 | struct stat statbuf; 160 | if( stat( path_buffer, &statbuf ) == -1 ) 161 | continue; 162 | 163 | if( !S_ISDIR( statbuf.st_mode ) ) 164 | continue; 165 | } 166 | 167 | fswatcher_recursive_add( w, path_buffer, path_len + d_name_size + 1, path_max ); 168 | } 169 | path_buffer[path_len] = '\0'; 170 | 171 | closedir( dirp ); 172 | } 173 | 174 | fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ) 175 | { 176 | if( allocator == 0x0 ) 177 | allocator = &g_fswatcher_default_alloc; 178 | 179 | fswatcher* w = (fswatcher*)fswatcher_realloc( allocator, 0x0, 0, sizeof( fswatcher ) ); 180 | memset( w, 0x0, sizeof( fswatcher ) ); 181 | w->allocator = allocator; 182 | w->watch_flags = 0; 183 | 184 | if( types & FSWATCHER_EVENT_CREATE ) w->watch_flags |= IN_CREATE; 185 | if( types & FSWATCHER_EVENT_REMOVE ) w->watch_flags |= IN_DELETE; 186 | if( types & FSWATCHER_EVENT_MOVE ) w->watch_flags |= IN_MOVE; 187 | if( types & FSWATCHER_EVENT_MODIFY ) w->watch_flags |= IN_MODIFY; 188 | w->watch_flags |= IN_DELETE_SELF; 189 | 190 | int inotify_flags = 0; 191 | if( ( flags & FSWATCHER_CREATE_BLOCKING ) == 0 ) 192 | inotify_flags |= IN_NONBLOCK; 193 | 194 | w->notifierfd = inotify_init1( inotify_flags ); 195 | if( w->notifierfd < -1 ) 196 | { 197 | fswatcher_free( allocator, w ); 198 | return 0x0; 199 | } 200 | 201 | w->watches_cap = 16; // 256; 202 | w->watches = (fswatcher_item*)fswatcher_realloc( w->allocator, 0x0, 0, sizeof(fswatcher_item) * w->watches_cap ); 203 | 204 | char path_buffer[4096]; 205 | strncpy( path_buffer, watch_dir, sizeof( path_buffer ) ); 206 | // TODO: make sure path fit ... 207 | 208 | size_t path_len = strlen( path_buffer ); 209 | if( path_buffer[path_len-1] != '/' ) 210 | { 211 | path_buffer[path_len] = '/'; 212 | path_buffer[path_len+1] = '\0'; 213 | ++path_len; 214 | } 215 | 216 | fswatcher_recursive_add( w, path_buffer, path_len, sizeof( path_buffer ) ); 217 | return w; 218 | } 219 | 220 | void fswatcher_destroy( fswatcher_t watcher ) 221 | { 222 | close( watcher->notifierfd ); 223 | for( size_t i = 0; i < watcher->watches_cnt; ++i ) 224 | fswatcher_free( watcher->allocator, (void*)watcher->watches[i].path ); 225 | fswatcher_free( watcher->allocator, watcher->watches ); 226 | fswatcher_free( watcher->allocator, watcher ); 227 | } 228 | 229 | static char* fswatcher_build_full_path( fswatcher_t watcher, fswatcher_allocator* allocator, int wd, const char* name, uint32_t name_len ) 230 | { 231 | const char* dirpath = fswatcher_find_wd_path( watcher, wd ); 232 | size_t dirlen = strlen( dirpath ); 233 | size_t length = dirlen + 1 + name_len; 234 | char* res = (char*)fswatcher_realloc( allocator, 0x0, 0, length ); 235 | if( res ) 236 | { 237 | memcpy( res, dirpath, dirlen ); 238 | memcpy( res + dirlen, name, name_len ); 239 | res[length-1] = 0; 240 | } 241 | return res; 242 | } 243 | 244 | #define FS_MAKE_CALLBACK( type, src, dst ) handler->callback( handler, (type), (src), (dst) ); 245 | 246 | static void fswatcher_make_callback_with_src_path( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_event_type type, inotify_event* ev ) 247 | { 248 | char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev->wd, ev->name, ev->len ); 249 | FS_MAKE_CALLBACK( type, src, 0x0 ); 250 | fswatcher_free( watcher->allocator, src ); 251 | } 252 | 253 | static void fswatcher_make_callback_with_dst_path( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_event_type type, inotify_event* ev ) 254 | { 255 | char* dst = fswatcher_build_full_path( watcher, watcher->allocator, ev->wd, ev->name, ev->len ); 256 | FS_MAKE_CALLBACK( type, 0x0, dst ); 257 | fswatcher_free( watcher->allocator, dst ); 258 | } 259 | 260 | void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ) 261 | { 262 | if( allocator == 0x0 ) 263 | allocator = &g_fswatcher_default_alloc; 264 | 265 | char* move_src = 0x0; 266 | uint32_t move_cookie = 0; 267 | 268 | while( true ) 269 | { 270 | char read_buffer[4096]; 271 | ssize_t read_bytes = read( watcher->notifierfd, read_buffer, sizeof( read_buffer ) ); 272 | if( read_bytes <= 0 ) 273 | break; 274 | 275 | for( char* bufp = read_buffer; bufp < read_buffer + read_bytes; ) 276 | { 277 | inotify_event* ev = (inotify_event*)bufp; 278 | bool is_dir = ( ev->mask & IN_ISDIR ); 279 | bool is_create = ( ev->mask & IN_CREATE ); 280 | bool is_remove = ( ev->mask & IN_DELETE ); 281 | bool is_modify = ( ev->mask & IN_MODIFY ); 282 | bool is_move_from = ( ev->mask & IN_MOVED_FROM ); 283 | bool is_move_to = ( ev->mask & IN_MOVED_TO ); 284 | bool is_del_self = ( ev->mask & IN_DELETE_SELF ); 285 | 286 | if( is_dir ) 287 | { 288 | if( is_create ) 289 | { 290 | char* src = fswatcher_build_full_path( watcher, allocator, ev->wd, ev->name, ev->len ); 291 | fswatcher_add( watcher, src ); 292 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_CREATE, src, 0x0 ); 293 | fswatcher_free( allocator, src ); 294 | } 295 | else if( is_remove ) 296 | fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_REMOVE, ev ); 297 | else if( is_del_self ) 298 | fswatcher_remove( watcher, ev->wd ); 299 | } 300 | else if( ev->mask & IN_Q_OVERFLOW ) 301 | { 302 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_BUFFER_OVERFLOW, 0x0, 0x0 ); 303 | } 304 | else 305 | { 306 | if( is_create ) 307 | fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_CREATE, ev ); 308 | else if( is_remove ) 309 | fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_REMOVE, ev ); 310 | else if( is_modify ) 311 | fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_MODIFY, ev ); 312 | else if( is_move_from ) 313 | { 314 | if( move_src != 0x0 ) 315 | { 316 | // ... this is a new pair of a move, so the last one was move "outside" the current watch ... 317 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, 0x0 ); 318 | fswatcher_free( allocator, move_src ); 319 | } 320 | 321 | // ... this is the first potential pair of a move ... 322 | move_src = fswatcher_build_full_path( watcher, allocator, ev->wd, ev->name, ev->len ); 323 | move_cookie = ev->cookie; 324 | } 325 | else if( is_move_to ) 326 | { 327 | if( move_src && move_cookie == ev->cookie ) 328 | { 329 | // ... this is the dst for a move ... 330 | char* dst = fswatcher_build_full_path( watcher, allocator, ev->wd, ev->name, ev->len ); 331 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, dst ); 332 | fswatcher_free( watcher->allocator, dst ); 333 | fswatcher_free( watcher->allocator, move_src ); 334 | move_src = 0x0; 335 | move_cookie = 0; 336 | } 337 | else if( move_src != 0x0 ) 338 | { 339 | // ... this is a "move to outside of watch" ... 340 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, 0x0 ); 341 | fswatcher_free( watcher->allocator, move_src ); 342 | move_src = 0x0; 343 | move_cookie = 0; 344 | 345 | // ...followed by a "move from outside to watch ... 346 | fswatcher_make_callback_with_dst_path( watcher, handler, FSWATCHER_EVENT_MOVE, ev ); 347 | } 348 | else 349 | { 350 | // ... this is a "move from outside to watch" ... 351 | fswatcher_make_callback_with_dst_path( watcher, handler, FSWATCHER_EVENT_MOVE, ev ); 352 | } 353 | } 354 | } 355 | 356 | bufp += sizeof(inotify_event) + ev->len; 357 | } 358 | } 359 | 360 | if( move_src ) 361 | { 362 | // ... we have a "move to outside of watch" that was never closed ... 363 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, 0x0 ); 364 | fswatcher_free( allocator, move_src ); 365 | } 366 | } 367 | 368 | #undef FS_MAKE_CALLBACK 369 | 370 | -------------------------------------------------------------------------------- /src/fswatcher_osx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #include 28 | 29 | fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ) 30 | { 31 | (void)flags; (void)types; (void)watch_dir; (void)allocator; 32 | return 0x0; 33 | } 34 | 35 | void fswatcher_destroy( fswatcher_t watcher ) 36 | { 37 | (void)watcher; 38 | } 39 | 40 | void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ) 41 | { 42 | (void)watcher; (void)handler; (void)allocator; 43 | } 44 | -------------------------------------------------------------------------------- /src/fswatcher_windows.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | #include 27 | 28 | #include // malloc 29 | #include 30 | 31 | #include // remove 32 | 33 | struct fswatcher 34 | { 35 | fswatcher_allocator* allocator; 36 | bool recursive; 37 | bool blocking; 38 | 39 | const char* watch_dir; 40 | size_t watch_dir_len; 41 | 42 | HANDLE directory; 43 | OVERLAPPED overlapped; 44 | 45 | DWORD read_buffer[2048]; // hmmmm. 46 | }; 47 | 48 | static void* fswatcher_default_realloc( fswatcher_allocator*, void* ptr, size_t, size_t new_size ) 49 | { 50 | return realloc( ptr, new_size ); 51 | } 52 | 53 | static void fswatcher_default_free( fswatcher_allocator*, void* ptr ) 54 | { 55 | free( ptr ); 56 | } 57 | 58 | static fswatcher_allocator g_fswatcher_default_alloc = { fswatcher_default_realloc, fswatcher_default_free }; 59 | 60 | static void* fswatcher_realloc( fswatcher_allocator* allocator, void* ptr, size_t old_size, size_t new_size ) 61 | { 62 | return allocator->realloc( allocator, ptr, old_size, new_size ); 63 | } 64 | 65 | static void fswatcher_free( fswatcher_allocator* allocator, void* ptr ) 66 | { 67 | if( allocator->free ) 68 | allocator->free( allocator, ptr ); 69 | } 70 | 71 | #define FS_MAKE_CALLBACK( type, src, dst ) handler->callback( handler, (type), (src), (dst) ); 72 | 73 | static char* fswatcher_build_full_path( fswatcher_t watcher, fswatcher_allocator* allocator, FILE_NOTIFY_INFORMATION* ev ) 74 | { 75 | size_t path_len = (size_t)( ev->FileNameLength / 2 ); 76 | 77 | int utf8_len = WideCharToMultiByte( CP_UTF8, // CodePage 78 | 0, // dwFlags 79 | ev->FileName, // lpWideCharStr 80 | (int)path_len, // cchWideChar 81 | nullptr, // lpMultiByteStr 82 | 0, // cbMultiByte 83 | nullptr, // lpDefaultChar 84 | nullptr ); // lpUsedDefaultChar 85 | size_t res_len = watcher->watch_dir_len + utf8_len; 86 | 87 | char* res = (char*)fswatcher_realloc( allocator, 0x0, 0, res_len + 1 ); 88 | strcpy( res, watcher->watch_dir ); 89 | 90 | char* file_part = res + watcher->watch_dir_len; 91 | 92 | WideCharToMultiByte( CP_UTF8, // CodePage 93 | 0, // dwFlags 94 | ev->FileName, // lpWideCharStr 95 | (int)path_len, // cchWideChar 96 | file_part, // lpMultiByteStr 97 | utf8_len, // cbMultiByte 98 | nullptr, // lpDefaultChar 99 | nullptr ); // lpUsedDefaultChar 100 | res[res_len] = '\0'; 101 | return res; 102 | } 103 | 104 | static void fswatcher_begin_read( fswatcher_t watcher ) 105 | { 106 | ::ZeroMemory( &watcher->overlapped, sizeof( watcher->overlapped ) ); 107 | 108 | BOOL success = ::ReadDirectoryChangesW( watcher->directory, 109 | watcher->read_buffer, 110 | sizeof( watcher->read_buffer ), 111 | watcher->recursive ? TRUE : FALSE, 112 | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, 113 | 0x0, 114 | &watcher->overlapped, 115 | 0x0 ); 116 | if( !success ) 117 | printf("NOOOOOOOOOOOOOO!"); 118 | } 119 | 120 | fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ) 121 | { 122 | if( allocator == 0x0 ) 123 | allocator = &g_fswatcher_default_alloc; 124 | 125 | fswatcher_t w = (fswatcher_t)fswatcher_realloc( allocator, 0x0, 0, sizeof( fswatcher ) ); 126 | w->allocator = allocator; 127 | w->watch_dir_len = strlen( watch_dir ); 128 | w->watch_dir = (char*)fswatcher_realloc( w->allocator, 0x0, 0, w->watch_dir_len + 1 ); 129 | strcpy( (char*)w->watch_dir, watch_dir ); 130 | w->recursive = ( flags & FSWATCHER_CREATE_RECURSIVE ) > 0; 131 | w->blocking = ( flags & FSWATCHER_CREATE_BLOCKING ) > 0; 132 | w->directory = ::CreateFile( watch_dir, 133 | FILE_LIST_DIRECTORY, 134 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 135 | NULL, // security descriptor 136 | OPEN_EXISTING, // how to create 137 | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // file attributes 138 | NULL ); // file with attributes to copy 139 | // handle error ... 140 | 141 | fswatcher_begin_read( w ); 142 | return w; 143 | } 144 | 145 | void fswatcher_destroy( fswatcher_t watcher ) 146 | { 147 | ::CloseHandle( watcher->directory ); 148 | fswatcher_free( watcher->allocator, watcher ); 149 | } 150 | 151 | void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ) 152 | { 153 | DWORD bytes; 154 | BOOL res = ::GetOverlappedResult( watcher->directory, 155 | &watcher->overlapped, 156 | &bytes, 157 | watcher->blocking ); 158 | if( res != TRUE ) 159 | return; 160 | 161 | char* move_src = 0x0; 162 | 163 | FILE_NOTIFY_INFORMATION* ev = (FILE_NOTIFY_INFORMATION*)watcher->read_buffer; 164 | do 165 | { 166 | switch(ev->Action) 167 | { 168 | case FILE_ACTION_ADDED: 169 | { 170 | char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); 171 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_CREATE, src, 0x0 ); 172 | fswatcher_free( watcher->allocator, src ); 173 | } 174 | break; 175 | case FILE_ACTION_REMOVED: 176 | { 177 | char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); 178 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_REMOVE, src, 0x0 ); 179 | fswatcher_free( watcher->allocator, src ); 180 | } 181 | break; 182 | case FILE_ACTION_MODIFIED: 183 | { 184 | char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); 185 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_MODIFY, src, 0x0 ); 186 | fswatcher_free( watcher->allocator, src ); 187 | } 188 | break; 189 | case FILE_ACTION_RENAMED_OLD_NAME: 190 | { 191 | move_src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); 192 | } 193 | break; 194 | case FILE_ACTION_RENAMED_NEW_NAME: 195 | { 196 | char* dst = fswatcher_build_full_path( watcher, watcher->allocator, ev ); 197 | FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, dst ); 198 | fswatcher_free( watcher->allocator, move_src ); 199 | fswatcher_free( watcher->allocator, dst ); 200 | move_src = 0x0; 201 | } 202 | break; 203 | default: 204 | printf("unhandled action %d\n", ev->Action); 205 | } 206 | 207 | if( ev->NextEntryOffset == 0 ) 208 | break; 209 | ev = (FILE_NOTIFY_INFORMATION*)((char*)ev + ev->NextEntryOffset); 210 | } 211 | while( true ); 212 | 213 | if( move_src != 0x0 ) 214 | { 215 | // how to handle this? 216 | fswatcher_free( watcher->allocator, move_src ); 217 | } 218 | 219 | fswatcher_begin_read( watcher ); 220 | } 221 | -------------------------------------------------------------------------------- /test/fswatcher_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #include "greatest.h" 28 | #include 29 | 30 | #include 31 | #include // system 32 | 33 | #include 34 | 35 | #if defined( _WIN32 ) 36 | # include 37 | # define DIR_SEP "\\" 38 | #else 39 | # define DIR_SEP "/" 40 | #endif 41 | 42 | static const char* get_test_dir() 43 | { 44 | static char temp_path[4096] = {0}; 45 | static bool fetched = false; 46 | if( !fetched ) 47 | { 48 | #if defined( _WIN32 ) 49 | GetTempPathA( sizeof( temp_path ), temp_path ); 50 | strcat( temp_path, "\\fswatcher_test\\" ); 51 | #else 52 | strcat( temp_path, P_tmpdir ); 53 | strcat( temp_path, "/fswatcher_test/" ); 54 | #endif 55 | printf( "Running tests in \"%s\"\n", temp_path ); 56 | fetched = true; 57 | } 58 | return temp_path; 59 | } 60 | 61 | static void run_system( const char* cmd, const char* path ) 62 | { 63 | char buffer[4096] = {0}; 64 | strcat( buffer, cmd ); 65 | strcat( buffer, path ); 66 | // printf("system: %s\n", buffer); 67 | if( system( buffer ) < 0 ) 68 | printf("failed to run system( %s )\n", buffer ); 69 | } 70 | 71 | #if defined( _WIN32 ) 72 | static void create_dir ( const char* dirname ) { run_system( "mkdir ", dirname ); } 73 | static void create_file( const char* filename ) { run_system( "type nul>> ", filename ); } 74 | static void remove_dir ( const char* dirname ) { run_system( "rmdir /q /s ", dirname ); } 75 | static void remove_file( const char* filename ) { run_system( "del ", filename ); } 76 | static void move_file( const char* src, const char* dst ) 77 | { 78 | char buffer[4096] = {0}; 79 | strcat( buffer, "move " ); 80 | strcat( buffer, src ); 81 | strcat( buffer, " " ); 82 | strcat( buffer, dst ); 83 | strcat( buffer, " >nul 2>&1" ); 84 | if( system( buffer ) < 0 ) 85 | printf("faied to run system( %s )\n", buffer ); 86 | } 87 | #else 88 | static void create_dir ( const char* dirname ) { run_system( "mkdir -p ", dirname ); } 89 | static void create_file( const char* filename ) { run_system( "touch ", filename ); } 90 | static void remove_dir ( const char* dirname ) { run_system( "rm -rf ", dirname ); } 91 | static void remove_file( const char* filename ) { run_system( "rm ", filename ); } 92 | static void move_file( const char* src, const char* dst ) 93 | { 94 | char buffer[4096] = {0}; 95 | strcat( buffer, "mv " ); 96 | strcat( buffer, src ); 97 | strcat( buffer, " " ); 98 | strcat( buffer, dst ); 99 | if( system( buffer ) < 0 ) 100 | printf("faied to run system( %s )\n", buffer ); 101 | } 102 | 103 | static void create_symlink( const char* dst, const char* name, const char* src ) 104 | { 105 | char buffer[4096] = {0}; 106 | strcat( buffer, "cd " ); 107 | strcat( buffer, dst ); 108 | strcat( buffer, ";ln -s " ); 109 | strcat( buffer, src ); 110 | strcat( buffer, " " ); 111 | strcat( buffer, name ); 112 | // printf( "%s\n", buffer ); 113 | if( system( buffer ) < 0 ) 114 | printf("faied to run system( %s )\n", buffer ); 115 | } 116 | #endif 117 | 118 | static const char* test_dir_path( const char* path, char* buffer ) 119 | { 120 | buffer[0] = '\0'; 121 | strcat( buffer, get_test_dir() ); 122 | strcat( buffer, path ); 123 | return buffer; 124 | } 125 | 126 | static const char* test_dir_path( const char* path ) 127 | { 128 | static char buffer[4096]; 129 | return test_dir_path( path, buffer ); 130 | } 131 | 132 | static void setup_test_dir() 133 | { 134 | // ... remove dir from prev test ... 135 | remove_dir( get_test_dir() ); 136 | 137 | // ... and recreate it ... 138 | create_dir( get_test_dir() ); 139 | } 140 | 141 | struct test_handler 142 | { 143 | fswatcher_event_handler handler; 144 | fswatcher_event_type type; 145 | const char* src; 146 | const char* dst; 147 | }; 148 | 149 | #define HANDLER_RESET( handler ) \ 150 | free( (void*)handler.src ); handler.src = 0x0; \ 151 | free( (void*)handler.dst ); handler.dst = 0x0; \ 152 | handler.type = FSWATCHER_EVENT_ALL 153 | 154 | static bool watch_event_handler( fswatcher_event_handler* handler, fswatcher_event_type evtype, const char* src, const char* dst ) 155 | { 156 | test_handler* h = (test_handler*)handler; 157 | h->type = evtype; 158 | h->src = src ? strdup( src ) : 0; 159 | h->dst = dst ? strdup( dst ) : 0; 160 | return true; 161 | } 162 | 163 | int check_event_handler( fswatcher_event_type evtype, const char* src, const char* dst, test_handler* handler ) 164 | { 165 | if( handler->type != evtype ) 166 | printf("%d != %d, %s %s\n", handler->type, evtype, src, dst); 167 | ASSERT_EQ( handler->type, evtype ); 168 | if( src ) 169 | ASSERT_STR_EQ( src, handler->src ); 170 | else 171 | ASSERT_EQ( src, handler->src ); 172 | if( dst ) 173 | ASSERT_STR_EQ( dst, handler->dst ); 174 | else 175 | ASSERT_EQ( dst, handler->dst ); 176 | return 0; 177 | } 178 | 179 | TEST create_remove_dir() 180 | { 181 | setup_test_dir(); 182 | 183 | fswatcher_t watcher = fswatcher_create( FSWATCHER_CREATE_DEFAULT, FSWATCHER_EVENT_ALL, get_test_dir(), 0x0 ); 184 | ASSERT( 0x0 != watcher ); 185 | test_handler handler = { { watch_event_handler }, FSWATCHER_EVENT_ALL, 0x0, 0x0 }; 186 | 187 | const char* path = test_dir_path( "d1" ); 188 | 189 | create_dir( path ); 190 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 191 | 192 | if( int ret = check_event_handler( FSWATCHER_EVENT_CREATE, path, 0x0, &handler ) ) return ret; 193 | HANDLER_RESET( handler ); 194 | 195 | remove_dir( path ); 196 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 197 | 198 | if( int ret = check_event_handler( FSWATCHER_EVENT_REMOVE, path, 0x0, &handler ) ) return ret; 199 | HANDLER_RESET( handler ); 200 | 201 | create_dir( path ); 202 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 203 | 204 | if( int ret = check_event_handler( FSWATCHER_EVENT_CREATE, path, 0x0, &handler ) ) return ret; 205 | HANDLER_RESET( handler ); 206 | 207 | fswatcher_destroy( watcher ); 208 | return 0; 209 | } 210 | 211 | TEST create_remove_file() 212 | { 213 | setup_test_dir(); 214 | 215 | fswatcher_t watcher = fswatcher_create( FSWATCHER_CREATE_DEFAULT, FSWATCHER_EVENT_ALL, get_test_dir(), 0x0 ); 216 | test_handler handler = { { watch_event_handler }, FSWATCHER_EVENT_ALL, 0x0, 0x0 }; 217 | 218 | const char* path = test_dir_path( "f1" ); 219 | create_file( path ); 220 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 221 | 222 | ASSERT_EQ( 0, check_event_handler( FSWATCHER_EVENT_CREATE, path, 0x0, &handler ) ); 223 | HANDLER_RESET( handler ); 224 | 225 | remove_file( path ); 226 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 227 | 228 | ASSERT_EQ( 0, check_event_handler( FSWATCHER_EVENT_REMOVE, path, 0x0, &handler ) ); 229 | HANDLER_RESET( handler ); 230 | 231 | fswatcher_destroy( watcher ); 232 | return 0; 233 | } 234 | 235 | TEST create_remove_file_in_subdir() 236 | { 237 | #if !defined( _WIN32 ) 238 | // TODO: make this work 239 | // the events are posted but they require some polling and also on windows we get a modify 240 | // event for the containing dir that we do not get on linux, how to handle? ( add event on linux, 241 | // ignore modified for dirs on windows, just let the platforms differ? ) 242 | setup_test_dir(); 243 | create_dir( test_dir_path( "subdir" ) ); 244 | 245 | fswatcher_t watcher = fswatcher_create( FSWATCHER_CREATE_DEFAULT, FSWATCHER_EVENT_ALL, get_test_dir(), 0x0 ); 246 | test_handler handler = { { watch_event_handler }, FSWATCHER_EVENT_ALL, 0x0, 0x0 }; 247 | 248 | const char* path = test_dir_path( "subdir" DIR_SEP "f1" ); 249 | create_file( path ); 250 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 251 | 252 | ASSERT_EQ( 0, check_event_handler( FSWATCHER_EVENT_CREATE, path, 0x0, &handler ) ); 253 | HANDLER_RESET( handler ); 254 | 255 | remove_file( path ); 256 | 257 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 258 | ASSERT_EQ( 0, check_event_handler( FSWATCHER_EVENT_REMOVE, test_dir_path( "subdir" DIR_SEP "f1" ), 0x0, &handler ) ); 259 | HANDLER_RESET( handler ); 260 | 261 | fswatcher_destroy( watcher ); 262 | #endif 263 | return 0; 264 | } 265 | 266 | TEST test_move_file() 267 | { 268 | setup_test_dir(); 269 | char path1[2048]; 270 | char path2[2048]; 271 | test_dir_path( "f1", path1 ); 272 | test_dir_path( "f2", path2 ); 273 | 274 | create_file( path1 ); 275 | 276 | fswatcher_t watcher = fswatcher_create( FSWATCHER_CREATE_DEFAULT, FSWATCHER_EVENT_ALL, get_test_dir(), 0x0 ); 277 | test_handler handler = { { watch_event_handler }, FSWATCHER_EVENT_ALL, 0x0, 0x0 }; 278 | 279 | move_file( path1, path2 ); 280 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 281 | 282 | if( int ret = check_event_handler( FSWATCHER_EVENT_MOVE, path1, path2, &handler ) ) return ret; 283 | HANDLER_RESET( handler ); 284 | 285 | fswatcher_destroy( watcher ); 286 | return 0; 287 | } 288 | 289 | TEST watch_symlinked_dir() 290 | { 291 | #if !defined( _WIN32 ) 292 | // TODO: make this work on windows 293 | setup_test_dir(); 294 | char watch_me_dir_path[2048]; 295 | char symlinked_dir_path[2048]; 296 | char test_file1_path[2048]; 297 | char test_file2_path[2048]; 298 | test_dir_path( "watch_me", watch_me_dir_path ); 299 | test_dir_path( "symlinked", symlinked_dir_path ); 300 | test_dir_path( "watch_me/linked/test.file", test_file1_path ); 301 | test_dir_path( "symlinked/test.file", test_file2_path ); 302 | 303 | create_dir( watch_me_dir_path ); 304 | create_dir( symlinked_dir_path ); 305 | 306 | create_symlink( watch_me_dir_path, "linked", symlinked_dir_path ); 307 | 308 | fswatcher_t watcher = fswatcher_create( FSWATCHER_CREATE_DEFAULT, FSWATCHER_EVENT_ALL, watch_me_dir_path, 0x0 ); 309 | test_handler handler = { { watch_event_handler }, FSWATCHER_EVENT_ALL, 0x0, 0x0 }; 310 | 311 | create_file( test_file2_path ); 312 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 313 | 314 | if( int ret = check_event_handler( FSWATCHER_EVENT_CREATE, test_file1_path, 0x0, &handler ) ) return ret; 315 | HANDLER_RESET( handler ); 316 | 317 | remove_file( test_file2_path ); 318 | fswatcher_poll( watcher, &handler.handler, 0x0 ); 319 | 320 | if( int ret = check_event_handler( FSWATCHER_EVENT_REMOVE, test_file1_path, 0x0, &handler ) ) return ret; 321 | HANDLER_RESET( handler ); 322 | 323 | fswatcher_destroy( watcher ); 324 | #endif 325 | return 0; 326 | } 327 | 328 | GREATEST_SUITE( fswatcher ) 329 | { 330 | RUN_TEST( create_remove_dir ); 331 | RUN_TEST( create_remove_file ); 332 | RUN_TEST( create_remove_file_in_subdir ); 333 | RUN_TEST( test_move_file ); 334 | RUN_TEST( watch_symlinked_dir ); 335 | } 336 | 337 | GREATEST_MAIN_DEFS(); 338 | 339 | int main( int argc, char **argv ) 340 | { 341 | get_test_dir(); 342 | 343 | GREATEST_MAIN_BEGIN(); 344 | RUN_SUITE( fswatcher ); 345 | GREATEST_MAIN_END(); 346 | } 347 | -------------------------------------------------------------------------------- /test/greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #define GREATEST_VERSION_MAJOR 0 21 | #define GREATEST_VERSION_MINOR 9 22 | #define GREATEST_VERSION_PATCH 3 23 | 24 | /* A unit testing system for C, contained in 1 file. 25 | * It doesn't use dynamic allocation or depend on anything 26 | * beyond ANSI C89. */ 27 | 28 | 29 | /********************************************************************* 30 | * Minimal test runner template 31 | *********************************************************************/ 32 | #if 0 33 | 34 | #include "greatest.h" 35 | 36 | TEST foo_should_foo() { 37 | PASS(); 38 | } 39 | 40 | static void setup_cb(void *data) { 41 | printf("setup callback for each test case\n"); 42 | } 43 | 44 | static void teardown_cb(void *data) { 45 | printf("teardown callback for each test case\n"); 46 | } 47 | 48 | SUITE(suite) { 49 | /* Optional setup/teardown callbacks which will be run before/after 50 | * every test case in the suite. 51 | * Cleared when the suite finishes. */ 52 | SET_SETUP(setup_cb, voidp_to_callback_data); 53 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 54 | 55 | RUN_TEST(foo_should_foo); 56 | } 57 | 58 | /* Add all the definitions that need to be in the test runner's main file. */ 59 | GREATEST_MAIN_DEFS(); 60 | 61 | int main(int argc, char **argv) { 62 | GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */ 63 | RUN_SUITE(suite); 64 | GREATEST_MAIN_END(); /* display results */ 65 | } 66 | 67 | #endif 68 | /*********************************************************************/ 69 | 70 | 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | 77 | /*********** 78 | * Options * 79 | ***********/ 80 | 81 | /* Default column width for non-verbose output. */ 82 | #ifndef GREATEST_DEFAULT_WIDTH 83 | #define GREATEST_DEFAULT_WIDTH 72 84 | #endif 85 | 86 | /* FILE *, for test logging. */ 87 | #ifndef GREATEST_STDOUT 88 | #define GREATEST_STDOUT stdout 89 | #endif 90 | 91 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 92 | #ifndef GREATEST_USE_ABBREVS 93 | #define GREATEST_USE_ABBREVS 1 94 | #endif 95 | 96 | 97 | /********* 98 | * Types * 99 | *********/ 100 | 101 | /* Info for the current running suite. */ 102 | typedef struct greatest_suite_info { 103 | unsigned int tests_run; 104 | unsigned int passed; 105 | unsigned int failed; 106 | unsigned int skipped; 107 | 108 | /* timers, pre/post running suite and individual tests */ 109 | clock_t pre_suite; 110 | clock_t post_suite; 111 | clock_t pre_test; 112 | clock_t post_test; 113 | } greatest_suite_info; 114 | 115 | /* Type for a suite function. */ 116 | typedef void (greatest_suite_cb)(void); 117 | 118 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 119 | * and passed the pointer to their additional data. */ 120 | typedef void (greatest_setup_cb)(void *udata); 121 | typedef void (greatest_teardown_cb)(void *udata); 122 | 123 | typedef enum { 124 | GREATEST_FLAG_VERBOSE = 0x01, 125 | GREATEST_FLAG_FIRST_FAIL = 0x02, 126 | GREATEST_FLAG_LIST_ONLY = 0x04 127 | } GREATEST_FLAG; 128 | 129 | typedef struct greatest_run_info { 130 | unsigned int flags; 131 | unsigned int tests_run; /* total test count */ 132 | 133 | /* Overall pass/fail/skip counts. */ 134 | unsigned int passed; 135 | unsigned int failed; 136 | unsigned int skipped; 137 | 138 | /* currently running test suite */ 139 | greatest_suite_info suite; 140 | 141 | /* info to print about the most recent failure */ 142 | const char *fail_file; 143 | unsigned int fail_line; 144 | const char *msg; 145 | 146 | /* current setup/teardown hooks and userdata */ 147 | greatest_setup_cb *setup; 148 | void *setup_udata; 149 | greatest_teardown_cb *teardown; 150 | void *teardown_udata; 151 | 152 | /* formatting info for ".....s...F"-style output */ 153 | unsigned int col; 154 | unsigned int width; 155 | 156 | /* only run a specific suite or test */ 157 | char *suite_filter; 158 | char *test_filter; 159 | 160 | /* overall timers */ 161 | clock_t begin; 162 | clock_t end; 163 | } greatest_run_info; 164 | 165 | /* Global var for the current testing context. 166 | * Initialized by GREATEST_MAIN_DEFS(). */ 167 | extern greatest_run_info greatest_info; 168 | 169 | 170 | /********************** 171 | * Exported functions * 172 | **********************/ 173 | 174 | void greatest_do_pass(const char *name); 175 | void greatest_do_fail(const char *name); 176 | void greatest_do_skip(const char *name); 177 | int greatest_pre_test(const char *name); 178 | void greatest_post_test(const char *name, int res); 179 | void greatest_usage(const char *name); 180 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 181 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 182 | 183 | 184 | /********** 185 | * Macros * 186 | **********/ 187 | 188 | /* Define a suite. */ 189 | #define GREATEST_SUITE(NAME) void NAME(void) 190 | 191 | /* Start defining a test function. 192 | * The arguments are not included, to allow parametric testing. */ 193 | #define GREATEST_TEST static int 194 | 195 | /* Run a suite. */ 196 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 197 | 198 | /* Run a test in the current suite. */ 199 | #define GREATEST_RUN_TEST(TEST) \ 200 | do { \ 201 | if (greatest_pre_test(#TEST) == 1) { \ 202 | int res = TEST(); \ 203 | greatest_post_test(#TEST, res); \ 204 | } else if (GREATEST_LIST_ONLY()) { \ 205 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 206 | } \ 207 | } while (0) 208 | 209 | /* Run a test in the current suite with one void* argument, 210 | * which can be a pointer to a struct with multiple arguments. */ 211 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 212 | do { \ 213 | if (greatest_pre_test(#TEST) == 1) { \ 214 | int res = TEST(ENV); \ 215 | greatest_post_test(#TEST, res); \ 216 | } else if (GREATEST_LIST_ONLY()) { \ 217 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 218 | } \ 219 | } while (0) 220 | 221 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 222 | * without needing to manually manage the argument struct. */ 223 | #if __STDC_VERSION__ >= 19901L 224 | #define GREATEST_RUN_TESTp(TEST, ...) \ 225 | do { \ 226 | if (greatest_pre_test(#TEST) == 1) { \ 227 | int res = TEST(__VA_ARGS__); \ 228 | greatest_post_test(#TEST, res); \ 229 | } else if (GREATEST_LIST_ONLY()) { \ 230 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 231 | } \ 232 | } while (0) 233 | #endif 234 | 235 | 236 | /* Check if the test runner is in verbose mode. */ 237 | #define GREATEST_IS_VERBOSE() (greatest_info.flags & GREATEST_FLAG_VERBOSE) 238 | #define GREATEST_LIST_ONLY() (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 239 | #define GREATEST_FIRST_FAIL() (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 240 | #define GREATEST_FAILURE_ABORT() (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL()) 241 | 242 | /* Message-less forms. */ 243 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 244 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 245 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 246 | #define GREATEST_ASSERT(COND) GREATEST_ASSERTm(#COND, COND) 247 | #define GREATEST_ASSERT_FALSE(COND) GREATEST_ASSERT_FALSEm(#COND, COND) 248 | #define GREATEST_ASSERT_EQ(EXP, GOT) GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 249 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 250 | 251 | /* The following forms take an additional message argument first, 252 | * to be displayed by the test runner. */ 253 | 254 | /* Fail if a condition is not true, with message. */ 255 | #define GREATEST_ASSERTm(MSG, COND) \ 256 | do { \ 257 | greatest_info.msg = MSG; \ 258 | greatest_info.fail_file = __FILE__; \ 259 | greatest_info.fail_line = __LINE__; \ 260 | if (!(COND)) { return -1; } \ 261 | greatest_info.msg = NULL; \ 262 | } while (0) 263 | 264 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 265 | do { \ 266 | greatest_info.msg = MSG; \ 267 | greatest_info.fail_file = __FILE__; \ 268 | greatest_info.fail_line = __LINE__; \ 269 | if ((COND)) { return -1; } \ 270 | greatest_info.msg = NULL; \ 271 | } while (0) 272 | 273 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ 274 | do { \ 275 | greatest_info.msg = MSG; \ 276 | greatest_info.fail_file = __FILE__; \ 277 | greatest_info.fail_line = __LINE__; \ 278 | if ((EXP) != (GOT)) { return -1; } \ 279 | greatest_info.msg = NULL; \ 280 | } while (0) 281 | 282 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 283 | do { \ 284 | const char *exp_s = (EXP); \ 285 | const char *got_s = (GOT); \ 286 | greatest_info.msg = MSG; \ 287 | greatest_info.fail_file = __FILE__; \ 288 | greatest_info.fail_line = __LINE__; \ 289 | if (0 != strcmp(exp_s, got_s)) { \ 290 | fprintf(GREATEST_STDOUT, \ 291 | "Expected:\n####\n%s\n####\n", exp_s); \ 292 | fprintf(GREATEST_STDOUT, \ 293 | "Got:\n####\n%s\n####\n", got_s); \ 294 | return -1; \ 295 | } \ 296 | greatest_info.msg = NULL; \ 297 | } while (0) 298 | 299 | #define GREATEST_PASSm(MSG) \ 300 | do { \ 301 | greatest_info.msg = MSG; \ 302 | return 0; \ 303 | } while (0) 304 | 305 | #define GREATEST_FAILm(MSG) \ 306 | do { \ 307 | greatest_info.fail_file = __FILE__; \ 308 | greatest_info.fail_line = __LINE__; \ 309 | greatest_info.msg = MSG; \ 310 | return -1; \ 311 | } while (0) 312 | 313 | #define GREATEST_SKIPm(MSG) \ 314 | do { \ 315 | greatest_info.msg = MSG; \ 316 | return 1; \ 317 | } while (0) 318 | 319 | #define GREATEST_SET_TIME(NAME) \ 320 | NAME = clock(); \ 321 | if (NAME == (clock_t) -1) { \ 322 | fprintf(GREATEST_STDOUT, \ 323 | "clock error: %s\n", #NAME); \ 324 | exit(EXIT_FAILURE); \ 325 | } 326 | 327 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 328 | fprintf(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 329 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 330 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) \ 331 | 332 | /* Include several function definitions in the main test file. */ 333 | #define GREATEST_MAIN_DEFS() \ 334 | \ 335 | /* Is FILTER a subset of NAME? */ \ 336 | static int greatest_name_match(const char *name, \ 337 | const char *filter) { \ 338 | size_t offset = 0; \ 339 | size_t filter_len = strlen(filter); \ 340 | while (name[offset] != '\0') { \ 341 | if (name[offset] == filter[0]) { \ 342 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 343 | return 1; \ 344 | } \ 345 | } \ 346 | offset++; \ 347 | } \ 348 | \ 349 | return 0; \ 350 | } \ 351 | \ 352 | int greatest_pre_test(const char *name) { \ 353 | if (!GREATEST_LIST_ONLY() \ 354 | && (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \ 355 | && (greatest_info.test_filter == NULL || \ 356 | greatest_name_match(name, greatest_info.test_filter))) { \ 357 | GREATEST_SET_TIME(greatest_info.suite.pre_test); \ 358 | if (greatest_info.setup) { \ 359 | greatest_info.setup(greatest_info.setup_udata); \ 360 | } \ 361 | return 1; /* test should be run */ \ 362 | } else { \ 363 | return 0; /* skipped */ \ 364 | } \ 365 | } \ 366 | \ 367 | void greatest_post_test(const char *name, int res) { \ 368 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 369 | if (greatest_info.teardown) { \ 370 | void *udata = greatest_info.teardown_udata; \ 371 | greatest_info.teardown(udata); \ 372 | } \ 373 | \ 374 | if (res < 0) { \ 375 | greatest_do_fail(name); \ 376 | } else if (res > 0) { \ 377 | greatest_do_skip(name); \ 378 | } else if (res == 0) { \ 379 | greatest_do_pass(name); \ 380 | } \ 381 | greatest_info.suite.tests_run++; \ 382 | greatest_info.col++; \ 383 | if (GREATEST_IS_VERBOSE()) { \ 384 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 385 | greatest_info.suite.post_test); \ 386 | fprintf(GREATEST_STDOUT, "\n"); \ 387 | } else if (greatest_info.col % greatest_info.width == 0) { \ 388 | fprintf(GREATEST_STDOUT, "\n"); \ 389 | greatest_info.col = 0; \ 390 | } \ 391 | if (GREATEST_STDOUT == stdout) fflush(stdout); \ 392 | } \ 393 | \ 394 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 395 | const char *suite_name) { \ 396 | if (greatest_info.suite_filter && \ 397 | !greatest_name_match(suite_name, greatest_info.suite_filter)) { \ 398 | return; \ 399 | } \ 400 | if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) { return; } \ 401 | greatest_info.suite.tests_run = 0; \ 402 | greatest_info.suite.failed = 0; \ 403 | greatest_info.suite.passed = 0; \ 404 | greatest_info.suite.skipped = 0; \ 405 | greatest_info.suite.pre_suite = 0; \ 406 | greatest_info.suite.post_suite = 0; \ 407 | greatest_info.suite.pre_test = 0; \ 408 | greatest_info.suite.post_test = 0; \ 409 | greatest_info.col = 0; \ 410 | fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 411 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 412 | suite_cb(); \ 413 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 414 | if (greatest_info.suite.tests_run > 0) { \ 415 | fprintf(GREATEST_STDOUT, \ 416 | "\n%u tests - %u pass, %u fail, %u skipped", \ 417 | greatest_info.suite.tests_run, \ 418 | greatest_info.suite.passed, \ 419 | greatest_info.suite.failed, \ 420 | greatest_info.suite.skipped); \ 421 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 422 | greatest_info.suite.post_suite); \ 423 | fprintf(GREATEST_STDOUT, "\n"); \ 424 | } \ 425 | greatest_info.setup = NULL; \ 426 | greatest_info.setup_udata = NULL; \ 427 | greatest_info.teardown = NULL; \ 428 | greatest_info.teardown_udata = NULL; \ 429 | greatest_info.passed += greatest_info.suite.passed; \ 430 | greatest_info.failed += greatest_info.suite.failed; \ 431 | greatest_info.skipped += greatest_info.suite.skipped; \ 432 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 433 | } \ 434 | \ 435 | void greatest_do_pass(const char *name) { \ 436 | if (GREATEST_IS_VERBOSE()) { \ 437 | fprintf(GREATEST_STDOUT, "PASS %s: %s", \ 438 | name, greatest_info.msg ? greatest_info.msg : ""); \ 439 | } else { \ 440 | fprintf(GREATEST_STDOUT, "."); \ 441 | } \ 442 | greatest_info.suite.passed++; \ 443 | } \ 444 | \ 445 | void greatest_do_fail(const char *name) { \ 446 | if (GREATEST_IS_VERBOSE()) { \ 447 | fprintf(GREATEST_STDOUT, \ 448 | "FAIL %s: %s (%s:%u)", \ 449 | name, greatest_info.msg ? greatest_info.msg : "", \ 450 | greatest_info.fail_file, greatest_info.fail_line); \ 451 | } else { \ 452 | fprintf(GREATEST_STDOUT, "F"); \ 453 | greatest_info.col++; \ 454 | /* add linebreak if in line of '.'s */ \ 455 | if (greatest_info.col != 0) { \ 456 | fprintf(GREATEST_STDOUT, "\n"); \ 457 | greatest_info.col = 0; \ 458 | } \ 459 | fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 460 | name, \ 461 | greatest_info.msg ? greatest_info.msg : "", \ 462 | greatest_info.fail_file, greatest_info.fail_line); \ 463 | } \ 464 | greatest_info.suite.failed++; \ 465 | } \ 466 | \ 467 | void greatest_do_skip(const char *name) { \ 468 | if (GREATEST_IS_VERBOSE()) { \ 469 | fprintf(GREATEST_STDOUT, "SKIP %s: %s", \ 470 | name, \ 471 | greatest_info.msg ? \ 472 | greatest_info.msg : "" ); \ 473 | } else { \ 474 | fprintf(GREATEST_STDOUT, "s"); \ 475 | } \ 476 | greatest_info.suite.skipped++; \ 477 | } \ 478 | \ 479 | void greatest_usage(const char *name) { \ 480 | fprintf(GREATEST_STDOUT, \ 481 | "Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \ 482 | " -h print this Help\n" \ 483 | " -l List suites and their tests, then exit\n" \ 484 | " -f Stop runner after first failure\n" \ 485 | " -v Verbose output\n" \ 486 | " -s SUITE only run suite named SUITE\n" \ 487 | " -t TEST only run test named TEST\n", \ 488 | name); \ 489 | } \ 490 | \ 491 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 492 | greatest_info.setup = cb; \ 493 | greatest_info.setup_udata = udata; \ 494 | } \ 495 | \ 496 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ 497 | void *udata) { \ 498 | greatest_info.teardown = cb; \ 499 | greatest_info.teardown_udata = udata; \ 500 | } \ 501 | \ 502 | greatest_run_info greatest_info 503 | 504 | /* Handle command-line arguments, etc. */ 505 | #define GREATEST_MAIN_BEGIN() \ 506 | do { \ 507 | int i = 0; \ 508 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 509 | if (greatest_info.width == 0) { \ 510 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 511 | } \ 512 | for (i = 1; i < argc; i++) { \ 513 | if (0 == strcmp("-t", argv[i])) { \ 514 | if (argc <= i + 1) { \ 515 | greatest_usage(argv[0]); \ 516 | exit(EXIT_FAILURE); \ 517 | } \ 518 | greatest_info.test_filter = argv[i+1]; \ 519 | i++; \ 520 | } else if (0 == strcmp("-s", argv[i])) { \ 521 | if (argc <= i + 1) { \ 522 | greatest_usage(argv[0]); \ 523 | exit(EXIT_FAILURE); \ 524 | } \ 525 | greatest_info.suite_filter = argv[i+1]; \ 526 | i++; \ 527 | } else if (0 == strcmp("-f", argv[i])) { \ 528 | greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ 529 | } else if (0 == strcmp("-v", argv[i])) { \ 530 | greatest_info.flags |= GREATEST_FLAG_VERBOSE; \ 531 | } else if (0 == strcmp("-l", argv[i])) { \ 532 | greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \ 533 | } else if (0 == strcmp("-h", argv[i])) { \ 534 | greatest_usage(argv[0]); \ 535 | exit(EXIT_SUCCESS); \ 536 | } else { \ 537 | fprintf(GREATEST_STDOUT, \ 538 | "Unknown argument '%s'\n", argv[i]); \ 539 | greatest_usage(argv[0]); \ 540 | exit(EXIT_FAILURE); \ 541 | } \ 542 | } \ 543 | } while (0); \ 544 | GREATEST_SET_TIME(greatest_info.begin) 545 | 546 | #define GREATEST_MAIN_END() \ 547 | do { \ 548 | if (!GREATEST_LIST_ONLY()) { \ 549 | GREATEST_SET_TIME(greatest_info.end); \ 550 | fprintf(GREATEST_STDOUT, \ 551 | "\nTotal: %u tests", greatest_info.tests_run); \ 552 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 553 | greatest_info.end); \ 554 | fprintf(GREATEST_STDOUT, "\n"); \ 555 | fprintf(GREATEST_STDOUT, \ 556 | "Pass: %u, fail: %u, skip: %u.\n", \ 557 | greatest_info.passed, \ 558 | greatest_info.failed, greatest_info.skipped); \ 559 | } \ 560 | return (greatest_info.failed > 0 \ 561 | ? EXIT_FAILURE : EXIT_SUCCESS); \ 562 | } while (0) 563 | 564 | /* Make abbreviations without the GREATEST_ prefix for the 565 | * most commonly used symbols. */ 566 | #if GREATEST_USE_ABBREVS 567 | #define TEST GREATEST_TEST 568 | #define SUITE GREATEST_SUITE 569 | #define RUN_TEST GREATEST_RUN_TEST 570 | #define RUN_TEST1 GREATEST_RUN_TEST1 571 | #define RUN_SUITE GREATEST_RUN_SUITE 572 | #define ASSERT GREATEST_ASSERT 573 | #define ASSERTm GREATEST_ASSERTm 574 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 575 | #define ASSERT_EQ GREATEST_ASSERT_EQ 576 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 577 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 578 | #define ASSERT_EQm GREATEST_ASSERT_EQm 579 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 580 | #define PASS GREATEST_PASS 581 | #define FAIL GREATEST_FAIL 582 | #define SKIP GREATEST_SKIP 583 | #define PASSm GREATEST_PASSm 584 | #define FAILm GREATEST_FAILm 585 | #define SKIPm GREATEST_SKIPm 586 | #define SET_SETUP GREATEST_SET_SETUP_CB 587 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 588 | 589 | #if __STDC_VERSION__ >= 19901L 590 | #endif /* C99 */ 591 | #define RUN_TESTp GREATEST_RUN_TESTp 592 | #endif /* USE_ABBREVS */ 593 | 594 | #endif 595 | -------------------------------------------------------------------------------- /tester/fswatch_tester.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | A small drop-in library for watching the filesystem for changes. 3 | 4 | version 0.1, february, 2015 5 | 6 | Copyright (C) 2015- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | 31 | static bool watch_event_handler( fswatcher_event_handler* handler, fswatcher_event_type evtype, const char* src, const char* dst ) 32 | { 33 | (void)handler; (void)dst; 34 | 35 | switch( evtype ) 36 | { 37 | case FSWATCHER_EVENT_CREATE: printf("item create %s!\n", src); break; 38 | case FSWATCHER_EVENT_REMOVE: printf("item remove %s!\n", src); break; 39 | case FSWATCHER_EVENT_MODIFY: printf("item modify %s!\n", src); break; 40 | case FSWATCHER_EVENT_MOVE: printf("item moved %s -> %s!\n", src, dst); break; 41 | default: 42 | printf("unhandled event!\n"); 43 | } 44 | return true; 45 | } 46 | 47 | int main( int argc, const char** argv ) 48 | { 49 | (void)argc; (void)argv; 50 | fswatcher_t watcher = fswatcher_create( FSWATCHER_CREATE_DEFAULT, FSWATCHER_EVENT_ALL, argv[1], 0x0 ); 51 | 52 | while( true ) 53 | { 54 | fswatcher_event_handler handler = { watch_event_handler }; 55 | fswatcher_poll( watcher, &handler, 0x0 ); 56 | }; 57 | 58 | fswatcher_destroy( watcher ); 59 | return 0; 60 | } 61 | --------------------------------------------------------------------------------