source: tags/ms_r18q1/WINDOW/AW_inotify.cxx

Last change on this file was 16873, checked in by westram, 6 years ago
  • fix typos ("neither")
File size: 23.6 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : AW_inotify.cxx                                    //
4//   Purpose   : watch file/directory changes                      //
5//                                                                 //
6//   Coded by Ralf Westram (coder@reallysoft.de) in October 2017   //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "aw_inotify.hxx"
12
13#include "aw_root.hxx"
14#include <arbdbt.h>
15#include <arb_file.h>
16
17#include <unistd.h>
18
19#include <string>
20#include <list>
21#include <set>
22
23using namespace std;
24
25// probably needs to be defined system dependent (inotify seems to be linux-specific)
26#define USE_INOTIFY
27
28#if !defined(USE_INOTIFY)
29# define USE_STATPOLL // @@@ test with poll
30#endif
31
32#if defined(USE_STATPOLL)
33#include <sys/stat.h>
34#endif
35
36#if defined(USE_INOTIFY)
37#include <sys/inotify.h>
38#endif
39
40#if defined(DEBUG)
41// #define TRACE_INOTIFY
42// #define TRACE_INOTIFY_BASIC
43#endif
44
45#if defined(TRACE_INOTIFY)
46#define IF_TRACE_INOTIFY(x) x
47#else // !TRACE_INOTIFY
48#define IF_TRACE_INOTIFY(x)
49#endif
50
51
52typedef set<FileChangedCallback> CallbackList;
53class TrackedFiles;
54
55class TrackedFile {
56    string       file;
57    CallbackList callbacks;
58
59#if defined(USE_INOTIFY)
60    int watch_descriptor;
61#else // USE_STATPOLL
62
63    mutable int lastModtime; // last known modification time of 'file'
64    int getModtime() const {
65        struct stat st;
66        if (stat(file.c_str(), &st) == 0) return st.st_mtime;
67        return 0;
68    }
69    bool was_changed() const {
70        int  currModtime = getModtime();
71        bool changed     = currModtime>lastModtime;
72        lastModtime      = currModtime;
73        return changed;
74    }
75#endif
76
77public:
78    TrackedFile(const string& filename) :
79        file(filename)
80    {
81#if defined(USE_INOTIFY)
82        watch_descriptor = -1;
83#else // USE_STATPOLL
84        lastModtime      = 0;
85#endif
86    }
87#if defined(USE_INOTIFY)
88    ~TrackedFile() {
89        aw_assert(watch_descriptor == -1); // watch has to be removed before destruction
90    }
91#endif
92
93    const string& get_name() const { return file; }
94
95    void add_callback   (const FileChangedCallback& cb) { callbacks.insert(cb); }
96    void remove_callback(const FileChangedCallback& cb) { callbacks.erase(cb); }
97
98    bool empty() const { return callbacks.empty(); }
99
100    void callAll(ChangeReason reason) const {
101        bool deleted_still_exists = reason == CR_DELETED && GB_is_regularfile(file.c_str());
102        if (!deleted_still_exists) {
103            CallbackList copy = callbacks;
104            for (CallbackList::const_iterator cb = copy.begin(); cb != copy.end(); ++cb) {
105                if (callbacks.find(*cb) != callbacks.end()) {
106                    (*cb)(file.c_str(), reason);
107                }
108            }
109        }
110    }
111
112#if defined(USE_INOTIFY)
113    int get_watch_descriptor() const {
114        return watch_descriptor;
115    }
116    GB_ERROR add_watch(int inotifier) {
117        aw_assert(watch_descriptor == -1);
118        watch_descriptor = inotify_add_watch(inotifier, file.c_str(), IN_DELETE_SELF|IN_MOVE_SELF|IN_CLOSE_WRITE|IN_DELETE);
119        return watch_descriptor<0 ? GB_IO_error("watching", file.c_str()) : NULp;
120    }
121    GB_ERROR remove_watch(int inotifier) {
122        GB_ERROR err = NULp;
123        if (watch_descriptor != -1) {
124            if (inotify_rm_watch(inotifier, watch_descriptor)<0) {
125                err = GB_IO_error("un-watching", file.c_str());
126            }
127            watch_descriptor = -1;
128        }
129        return err;
130    }
131    void mark_as_disabled() {
132        watch_descriptor = -1;
133    }
134    void track_creation(TrackedFiles *tracked);
135
136#else // USE_STATPOLL
137    void callback_if_changed() const { if (was_changed()) callAll(); }
138#endif
139};
140
141class TrackedFiles : virtual Noncopyable {
142    typedef list<TrackedFile> FileList;
143
144    FileList files;
145
146#if defined(USE_INOTIFY)
147    int inotifier;
148
149    RefPtr<const char> error;
150
151    SmartCharPtr ievent_buffer;
152    int          oversize;
153
154    bool reactivate_stale;
155
156    size_t get_ievent_buffer_size() {
157        return sizeof(inotify_event)+oversize;
158    }
159    void realloc_buffer(int new_oversize) {
160        aw_assert(new_oversize>oversize);
161        oversize      = new_oversize;
162        ievent_buffer = ARB_alloc<char>(get_ievent_buffer_size());
163    }
164
165    inotify_event *get_ievent_buffer() {
166        if (oversize<0) realloc_buffer(9*sizeof(inotify_event)); // reserve memory for 10 events
167        return reinterpret_cast<inotify_event*>(&*ievent_buffer);
168    }
169    void increase_buffer_oversize() {
170        aw_assert(oversize>0);
171        realloc_buffer(oversize*3/2);
172    }
173
174
175#endif
176
177    FileList::iterator find(const string& file) {
178        for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
179            if (file == f->get_name()) return f;
180        }
181        return files.end();
182    }
183#if defined(USE_INOTIFY)
184    FileList::iterator find_watch_descriptor(int wd) {
185        for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
186            if (wd == f->get_watch_descriptor()) {
187                return f;
188            }
189        }
190        return files.end();
191    }
192
193    void check_for_created_files() {
194        aw_assert(reactivate_stale);
195        for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
196            if (f->get_watch_descriptor() == -1) {
197                GB_ERROR err = f->add_watch(inotifier);
198                if (!err) { // successfully reactivated
199                    f->callAll(CR_CREATED);
200                }
201#if defined(TRACE_INOTIFY)
202                fprintf(stderr, "check_for_created_files: add_watch for '%s': %s wd=%i\n", f->get_name().c_str(), err, f->get_watch_descriptor());
203#endif
204            }
205        }
206        // @@@ if no "stale" watch descriptor left => uninstall all watches of parent_modified_cb
207    }
208#endif
209
210public:
211#if defined(USE_INOTIFY)
212    TrackedFiles() :
213        error(NULp),
214        oversize(-1),
215        reactivate_stale(false)
216    {
217        inotifier = inotify_init();
218        if (inotifier<0) {
219            error = GB_IO_error("creating", "<inotify-instance>");
220        }
221    }
222    ~TrackedFiles() {
223        for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
224            if (f->get_watch_descriptor() >= 0) {
225                GB_ERROR  ferr  = f->remove_watch(inotifier);
226                if (ferr) fprintf(stderr, "Error in ~TrackedFiles: %s\n", ferr);
227            }
228            aw_assert(f->get_watch_descriptor() == -1);
229        }
230        close(inotifier);
231    }
232#endif
233
234    GB_ERROR get_error() {
235#if defined(USE_INOTIFY)
236        GB_ERROR err = error;
237        error        = NULp;
238        return err;
239#else // USE_STATPOLL
240        return NULp;
241#endif
242    }
243
244    bool empty() const {
245        return files.empty();
246    }
247    void insert(string file, const FileChangedCallback& fccb) {
248        // @@@ use canonical path of file!
249
250#if defined(TRACE_INOTIFY_BASIC)
251        fprintf(stderr, "[inotifier] + %s\n", file.c_str());
252#endif
253
254#if defined(USE_INOTIFY)
255        aw_assert(!error);
256#endif
257        FileList::iterator found = find(file);
258        if (found == files.end()) {
259            files.push_back(TrackedFile(file)); // new tracked file
260            found = find(file);
261#if defined(USE_INOTIFY)
262            GB_ERROR werr = found->add_watch(inotifier);
263            if (werr) found->track_creation(this);
264#if defined(TRACE_INOTIFY)
265            fprintf(stderr, "insert: add_watch for '%s': %s wd=%i\n", found->get_name().c_str(), werr, found->get_watch_descriptor());
266#endif
267#endif
268        }
269        found->add_callback(fccb);
270    }
271    void erase(string file, const FileChangedCallback& fccb) {
272        // @@@ use canonical path of file!
273
274#if defined(TRACE_INOTIFY_BASIC)
275        fprintf(stderr, "[inotifier] - %s\n", file.c_str());
276#endif
277
278#if defined(USE_INOTIFY)
279        aw_assert(!error);
280#endif
281        FileList::iterator found = find(file);
282        if (found != files.end()) {
283            found->remove_callback(fccb);
284            if (found->empty()) {
285#if defined(USE_INOTIFY)
286                if (found->get_watch_descriptor() != -1) {
287                    error = found->remove_watch(inotifier);
288                }
289#endif
290                files.erase(found);
291            }
292        }
293    }
294
295    void check_changes();
296    void request_reactivate_stale() { reactivate_stale = true; }
297};
298
299static void parent_modified_cb(const char *IF_TRACE_INOTIFY(parent_dir), ChangeReason IF_TRACE_INOTIFY(reason), TrackedFiles *tracked) {
300#if defined(TRACE_INOTIFY)
301    fprintf(stderr, "parent_modified_cb(dir='%s', reason=%i) called\n", parent_dir, int(reason));
302#endif
303    tracked->request_reactivate_stale();
304}
305
306void TrackedFile::track_creation(TrackedFiles *tracked) {
307    aw_assert(get_watch_descriptor() == -1); // otherwise it exists
308
309    char *parent_dir;
310    GB_split_full_path(file.c_str(), &parent_dir, NULp, NULp, NULp);
311
312    aw_assert(parent_dir);
313    if (parent_dir) {
314        AW_add_inotification(parent_dir, makeFileChangedCallback(parent_modified_cb, tracked));
315        free(parent_dir);
316    }
317}
318
319void TrackedFiles::check_changes() {
320    // called manually from unittest below
321    // called via timer callback otherwise
322
323#if defined(USE_INOTIFY)
324    aw_assert(!error);
325
326    timespec timeout = { 0, 0 }; // don't wait
327    fd_set readfds;
328    FD_ZERO(&readfds);
329    FD_SET(inotifier, &readfds);
330
331    int sel;
332    while ((sel = pselect(inotifier+1, &readfds, NULp, NULp, &timeout, NULp))) { // only poll if events are waiting
333        aw_assert(sel>0);
334
335        inotify_event *ievent = get_ievent_buffer();
336        ssize_t        got    = read(inotifier, ievent, get_ievent_buffer_size());
337
338        if (got >= ssize_t(sizeof(inotify_event))) {
339            while (got>0) {
340                FileList::iterator found = find_watch_descriptor(ievent->wd);
341                if (found != files.end()) {
342                    bool watch_removed = (ievent->mask & IN_IGNORED);
343#if defined(TRACE_INOTIFY)
344                    const char *name = (ievent->len>1) ? ievent->name : "";
345
346                    fprintf(stderr,
347                            "got inotify event for '%s' (wd=%i, mask=%X, watch_removed=%i) '%s'\n",
348                            found->get_name().c_str(), ievent->wd, ievent->mask, int(watch_removed), name);
349#endif
350                    if (watch_removed) {
351                        found->mark_as_disabled();
352                        found->track_creation(this);
353                    }
354                    else if (ievent->mask & IN_DELETE_SELF) {
355                        found->callAll(CR_DELETED);
356                    }
357                    else if (ievent->mask & IN_MOVE_SELF) {
358                        found->callAll(CR_DELETED);
359                        found->remove_watch(inotifier); // invalidate watch descriptor of found
360                        found->track_creation(this);    // ensure re-creation gets detected
361                        reactivate_stale = true;        // causes check whether new target has a "stale" watch
362                    }
363                    else if (ievent->mask & (IN_DELETE|IN_CLOSE_WRITE)) { // item deleted from directory or file/dir modified
364                        found->callAll(CR_MODIFIED);
365                    }
366                    else {
367                        aw_assert(0); // unhandled event from inotify
368                    }
369                }
370                else {
371#if defined(TRACE_INOTIFY)
372                    fprintf(stderr, "ignoring event for unknown watch_descriptor=%i\n", ievent->wd);
373#endif
374                }
375
376                int event_len  = sizeof(inotify_event)+ievent->len;
377                got           -= event_len;
378                ievent         = reinterpret_cast<inotify_event*>(reinterpret_cast<char*>(ievent)+event_len);
379            }
380        }
381        else {
382            bool     buffer_too_small = false;
383            GB_ERROR read_err         = NULp;
384
385            if (got == -1) {
386                if (errno == EINVAL) buffer_too_small = true;
387                else read_err = GB_IO_error("reading", "<inotify-descriptor>");
388            }
389            else if (got == 0) buffer_too_small = true;
390            else {
391                aw_assert(0);
392            }
393
394            if (read_err) {
395                fprintf(stderr, "inotifier broken: %s\n", read_err);
396                aw_assert(0);
397                break; // while loop
398            }
399
400            if (!buffer_too_small) {
401                aw_assert(buffer_too_small);
402                GBK_terminate("inotify event queue broken? (neither buffer_too_small nor read_err)");
403            }
404            increase_buffer_oversize();
405        }
406
407        aw_assert(FD_ISSET(inotifier, &readfds));
408    }
409
410    if (reactivate_stale) {
411        check_for_created_files();
412        reactivate_stale = false; // do once if any dir-content changes
413    }
414
415#else // USE_STATPOLL
416    for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
417        f->callback_if_changed();
418    }
419#endif
420}
421
422// --------------------------------------------------------------------------------
423
424static SmartPtr<TrackedFiles> allTrackers(new TrackedFiles);
425
426static bool    maintain_timer_callback = true;
427const unsigned AW_INOTIFY_TIMER        = 700; // ms
428
429static unsigned timed_inotifications_check_cb() {
430    allTrackers->check_changes();
431    if (allTrackers->empty()) {
432#if defined(TRACE_INOTIFY_BASIC)
433        fputs("[inotifier] - timer callback\n", stderr);
434#endif
435        return 0;
436    }
437    return AW_INOTIFY_TIMER;
438}
439
440static void show_tracked_error() {
441    GB_ERROR err = allTrackers->get_error();
442    if (err) {
443        fprintf(stderr, "Error in TrackedFiles: %s\n", err);
444    }
445}
446
447void AW_add_inotification(const char *file, const FileChangedCallback& fccb) {
448    /*! callback after a file changes
449     * @param file the file to watch
450     * @param fccb callback to call
451     */
452    show_tracked_error();
453    if (allTrackers->empty() & maintain_timer_callback) {
454#if defined(TRACE_INOTIFY_BASIC)
455        fputs("[inotifier] + timer callback\n", stderr);
456#endif
457        AW_root::SINGLETON->add_timed_callback(AW_INOTIFY_TIMER, makeTimedCallback(timed_inotifications_check_cb));
458    }
459    allTrackers->insert(file, fccb);
460    show_tracked_error();
461}
462
463void AW_remove_inotification(const char *file, const FileChangedCallback& fccb) {
464    allTrackers->erase(file, fccb);
465    show_tracked_error();
466}
467
468// --------------------------------------------------------------------------------
469
470#ifdef UNIT_TESTS
471#ifndef TEST_UNIT_H
472#include <test_unit.h>
473#endif
474#include <arb_cs.h>
475#include <arb_str.h>
476
477const int FLAGS = 4;
478static int change_flag[FLAGS][CHANGE_REASONS];
479
480static void trace_file_changed_cb(const char *IF_TRACE_INOTIFY(file), ChangeReason reason, int flag) {
481    aw_assert(flag<FLAGS);
482    change_flag[flag][reason]++;
483
484#if defined(TRACE_INOTIFY)
485    fprintf(stderr, "trace_file_changed_cb: flag=%i reason=%i file='%s'\n", flag, int(reason), file);
486#endif
487}
488
489// buf layout is "0123-0123-0123" (digits here are flag indices)
490#define BUFIDX(f,r) ((r)*(FLAGS+1)+(f))
491
492static const char *update_change_counts() {
493    for (int f = 0; f<FLAGS; ++f) {
494        for (int r = 0; r<CHANGE_REASONS; ++r) {
495            change_flag[f][r] = 0;
496        }
497    }
498
499    allTrackers->check_changes();
500
501    static char buf[BUFIDX(0,CHANGE_REASONS)];
502    for (int r = 0; r<CHANGE_REASONS; ++r) {
503        for (int f = 0; f<FLAGS; ++f) {
504            buf[BUFIDX(f,r)] = '0'+change_flag[f][r];
505        }
506        buf[BUFIDX(FLAGS,r)] = '-';
507    }
508    buf[BUFIDX(0,CHANGE_REASONS)-1] = 0;
509    return buf;
510}
511
512static arb_test::match_expectation change_counts_are(const char *expected_counts) {
513    using namespace   arb_test;
514    expectation_group expected;
515
516    const char *detected_counts = update_change_counts();
517
518    expected.add(that(detected_counts).is_equal_to(expected_counts));
519
520    return all().ofgroup(expected);
521}
522
523#define TEST_EXPECT_CHANGE_COUNTS(expected) TEST_EXPECTATION(change_counts_are(expected))
524
525static void update_file(const char *filename) {
526    FILE *out = fopen(filename, "wt");
527    aw_assert(out);
528    fputs("hi", out);
529    fclose(out);
530}
531
532inline void touch_files(const char *f1, const char *f2 = NULp) {
533#if defined(USE_STATPOLL)
534    static time_t last = 0;
535    {
536        time_t now;
537        do time(&now); while (now == last); // wait for new second (to ensure timestamps differ)
538    }
539#endif
540
541    if (f1) update_file(f1);
542    if (f2) update_file(f2);
543
544#if defined(USE_STATPOLL)
545    time(&last);
546#endif
547}
548
549inline void erase_files(const char *f1, const char *f2 = NULp) {
550    if (f1) TEST_EXPECT_MORE_EQUAL(GB_unlink(f1), 0);
551    if (f2) TEST_EXPECT_MORE_EQUAL(GB_unlink(f2), 0);
552}
553
554inline void move_file(const char *src, const char *dst) {
555    TEST_EXPECT_NO_ERROR(GB_move_file(src, dst));
556}
557inline void double_move_file(const char *src, const char *tmp, const char *dst) {
558    move_file(src, tmp);
559    move_file(tmp, dst);
560}
561
562#define INOTIFY_TESTDIR "inotify"
563
564// #define RETRY_INOTIFICATIONS_TEST
565
566void TEST_inotifications() {
567    // for some unknown reason this test randomly fails on one build host
568    //
569    // Update: the previous changes (log:trunk@16775:16780) do not fix the problem :(
570    // * if the test fails initially, repeated calls always fail!
571    // * but initial failure only happens with 5-10% probability (of calls of test-executable)
572    //
573    static bool inotify_tests_failing_randomly = ARB_stricmp(arb_gethostname(), "build-jessie") == 0;
574    if (inotify_tests_failing_randomly) {
575#if defined(RETRY_INOTIFICATIONS_TEST)
576        TEST_ALLOW_RETRY(5); // tell test-suite to try this test up to 5 times
577#else
578        MISSING_TEST("TEST_inotifications fails randomly on build-jessie -> skipped!");
579        return; // disable test on this host
580#endif
581    }
582
583    const char *inotify_testdir = INOTIFY_TESTDIR;
584
585    const char *testfile1 = INOTIFY_TESTDIR "/inotify1.testfile";
586    const char *testfile2 = INOTIFY_TESTDIR "/inotify2.testfile";
587    const char *nofile    = INOTIFY_TESTDIR "/no.testfile";
588
589    TEST_EXPECT_NO_ERROR(GB_create_directory(INOTIFY_TESTDIR));
590
591#if defined(RETRY_INOTIFICATIONS_TEST)
592    static bool retrying = false;
593    if (retrying) {
594        // remove any leftovers from last try
595        erase_files(testfile1,testfile2);
596        erase_files(nofile);
597        TEST_EXPECT_ZERO(rmdir(inotify_testdir));
598        allTrackers = new TrackedFiles;
599        TEST_EXPECT_NO_ERROR(GB_create_directory(INOTIFY_TESTDIR));
600    }
601    retrying = true;
602#endif
603
604    FileChangedCallback fccb1 = makeFileChangedCallback(trace_file_changed_cb, 0);
605    FileChangedCallback fccb2 = makeFileChangedCallback(trace_file_changed_cb, 1);
606    FileChangedCallback fccb3 = makeFileChangedCallback(trace_file_changed_cb, 2);
607    FileChangedCallback dccb4 = makeFileChangedCallback(trace_file_changed_cb, 3); // tracks directory
608
609    maintain_timer_callback = false;
610    erase_files(nofile); // make sure file does not exist
611
612    // event order  (= order of ChangeReason) is "MODIFIED-DELETED-UNKNOWN"
613    TEST_EXPECT_CHANGE_COUNTS("0000-0000-0000");
614
615    AW_add_inotification(inotify_testdir, dccb4);
616
617    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0000"); // two cbs for two changes in directory
618
619    AW_add_inotification(testfile1, fccb1);
620
621    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1002-0000-0000"); // two cbs for dir-changes; one cb for testfile1
622
623    AW_add_inotification(testfile2, fccb2);
624
625    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1102-0000-0000");
626
627    erase_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("0001-1000-0000"); // dir changed + testfile1 deleted
628    touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("0001-0000-1000"); // dir changed + testfile1 (re-)created
629    touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("1001-0000-0000"); // dir changed + testfile1 modified
630
631    erase_files(testfile2); TEST_EXPECT_CHANGE_COUNTS("0001-0100-0000"); // dir changed + testfile2 deleted
632
633    AW_add_inotification(nofile, fccb3); // add a tracker on non-existing file
634
635    // touch non-yet-existing 'nofile' -> should detect creation
636    touch_files(nofile);                      TEST_EXPECT_CHANGE_COUNTS("0001-0000-0010"); // detects creation of non-existing file
637    erase_files(nofile); touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0010"); // does detect quick delete+recreate as "create" (delete gets supressed)
638    erase_files(nofile);                      TEST_EXPECT_CHANGE_COUNTS("0001-0010-0000"); // detects erase
639    touch_files(nofile); erase_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0000"); // does NOT detect quick create+delete (just 2 directory changes)
640
641    // test moving files
642    move_file(testfile1, nofile);                   TEST_EXPECT_CHANGE_COUNTS("0000-1000-0010"); // detects delete 'testfile1' and create 'nofile'
643    touch_files(nofile);                            TEST_EXPECT_CHANGE_COUNTS("0011-0000-0000"); // detects modify 'nofile'
644    touch_files(testfile1);                         TEST_EXPECT_CHANGE_COUNTS("0001-0000-1000"); // detects create 'testfile1'
645    move_file(nofile, testfile1);                   TEST_EXPECT_CHANGE_COUNTS("0000-0010-1000"); // detects delete 'nofile' and create 'testfile1' (delete of overwritten 'testfile1' gets supressed)
646    double_move_file(testfile1, nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS("0000-0000-1000"); // detects creation of 'testfile1' ('nofile' does not trigger)
647    double_move_file(testfile1, nofile, testfile2); TEST_EXPECT_CHANGE_COUNTS("0000-1000-0100"); // delete 'testfile1' + create 'testfile2' ('nofile' does not trigger)
648    touch_files(testfile1, nofile);                 TEST_EXPECT_CHANGE_COUNTS("0002-0000-1010"); // create 'testfile1'+'nofile'
649    double_move_file(testfile2, nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS("0000-0110-1000"); // delete 'testfile2' and 'nofile' + create 'testfile1'
650
651    AW_add_inotification(testfile1, fccb3);
652    AW_add_inotification(testfile2, fccb3);
653
654    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1012-0000-0110"); // dir changed + testfile1 modified + testfile2 created
655
656    touch_files(testfile1);            TEST_EXPECT_CHANGE_COUNTS("1011-0000-0000"); // 2 cbs for file + 1 cb for dir triggered by one touch
657    touch_files(testfile2);            TEST_EXPECT_CHANGE_COUNTS("0111-0000-0000"); // same for other testfile
658
659    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1122-0000-0000"); // 6 callbacks triggered by 2 touches
660    AW_add_inotification(testfile2, fccb3); // (try to) add a tracker twice
661    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1122-0000-0000"); // still 6 callbacks triggered by 2 touches
662
663    AW_remove_inotification(testfile1, fccb1);
664
665    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0122-0000-0000");
666
667    // changing cd is ok for triggers @@@ might fail for delete+re-create
668    touch_files(testfile1, testfile2);
669    {
670        TEST_EXPECT_ZERO(chdir(inotify_testdir));
671        TEST_EXPECT_CHANGE_COUNTS("0122-0000-0000");
672        TEST_EXPECT_ZERO(chdir(".."));
673    }
674
675    AW_remove_inotification(testfile2, fccb2);
676
677    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0022-0000-0000");
678
679    AW_remove_inotification(testfile1, fccb3);
680
681    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0012-0000-0000");
682
683    AW_remove_inotification(testfile2, fccb3);
684
685    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0000");
686
687    AW_remove_inotification(inotify_testdir, dccb4);
688
689    touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0000-0000-0000");
690
691    TEST_EXPECT_ZERO(GB_unlink(testfile1));
692    TEST_EXPECT_ZERO(GB_unlink(testfile2));
693    TEST_EXPECT_ZERO(rmdir(inotify_testdir));
694
695    allTrackers = new TrackedFiles; // test cleanup (normally occurs at program exit)
696}
697
698#endif // UNIT_TESTS
699
700// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.