source: branches/help/ARBDB/ad_cb.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 65.4 KB
Line 
1// ================================================================= //
2//                                                                   //
3//   File      : ad_cb.cxx                                           //
4//   Purpose   : callbacks on DB entries                             //
5//                                                                   //
6//   Institute of Microbiology (Technical University Munich)         //
7//   http://www.arb-home.de/                                         //
8//                                                                   //
9// ================================================================= //
10
11#include "ad_cb.h"
12#include "ad_hcb.h"
13#include "gb_compress.h"
14#include "gb_ta.h"
15#include "gb_ts.h"
16
17#include <arb_strarray.h>
18#include <arb_strbuf.h>
19
20static gb_triggered_callback *currently_called_back = NULp; // points to callback during callback; NULp otherwise
21static GB_MAIN_TYPE          *inside_callback_main  = NULp; // points to DB root during callback; NULp otherwise
22
23gb_hierarchy_location::gb_hierarchy_location(GBDATA *gb_main, const char *db_path) {
24    invalidate();
25    if (db_path) {
26        GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
27        GB_test_transaction(Main);
28
29        ConstStrArray keys;
30        GBT_split_string(keys, db_path, '/');
31
32#define INVALIDATE_IF(cond) do{ if (cond) { invalidate(); return; } }while(0)
33
34        int  size         = keys.size();
35        bool is_full_path = !keys[0][0];   // db_path starts with '/'
36
37        depth = is_full_path ? INT_MAX : size;
38
39        int implEntry = int(is_full_path); // 1 if is_full_path (the empty entry before leading slash); 0 otherwise
40        if (size>implEntry) {
41            int q = 0;
42            for (int offset = size-1; offset>=implEntry; --offset, ++q) {
43                INVALIDATE_IF(!keys[offset][0]); // empty key
44                quark[q] = gb_find_or_create_quark(Main, keys[offset]);
45                INVALIDATE_IF(quark[q]<1); // unknown/invalid key
46            }
47            quark[size-implEntry] = 0;
48            gb_assert(is_valid());
49        }
50    }
51#undef INVALIDATE_IF
52}
53
54char *gb_hierarchy_location::get_db_path(GBDATA *gb_main) const {
55    GBS_strstruct  out(MAX_HIERARCHY_DEPTH*20);
56    GB_MAIN_TYPE  *Main = GB_MAIN(gb_main);
57
58    int offset = 0;
59    while (quark[offset]) ++offset;
60    if (!is_submatch()) out.put('/');
61    while (offset-->0) {
62        out.cat(quark2key(Main, quark[offset]));
63        out.put('/');
64    }
65    out.cut_tail(1);
66    return out.release();
67}
68
69void gb_pending_callbacks::call_and_forget(GB_CB_TYPE allowedTypes) {
70#if defined(ASSERTION_USED)
71    const gb_triggered_callback *tail = get_tail();
72#endif
73
74    for (itertype cb = callbacks.begin(); cb != callbacks.end(); ++cb) {
75        currently_called_back = &*cb;
76        gb_assert(currently_called_back);
77        currently_called_back->spec(cb->gbd, allowedTypes);
78        currently_called_back = NULp;
79    }
80
81    gb_assert(tail == get_tail());
82
83    callbacks.clear();
84}
85
86void GB_MAIN_TYPE::call_pending_callbacks() {
87    inside_callback_main = this;
88
89    deleteCBs.pending.call_and_forget(GB_CB_DELETE);         // first all delete callbacks:
90    changeCBs.pending.call_and_forget(GB_CB_ALL_BUT_DELETE); // then all change callbacks:
91
92    inside_callback_main = NULp;
93}
94
95inline void GB_MAIN_TYPE::callback_group::forget_hcbs() {
96    delete hierarchy_cbs;
97    hierarchy_cbs = NULp;
98}
99
100void GB_MAIN_TYPE::forget_hierarchy_cbs() {
101    changeCBs.forget_hcbs();
102    deleteCBs.forget_hcbs();
103}
104
105static void dummy_db_cb(GBDATA*, GB_CB_TYPE) { gb_assert(0); } // used as marker for deleted callbacks
106DatabaseCallback TypedDatabaseCallback::MARKED_DELETED = makeDatabaseCallback(dummy_db_cb);
107
108GB_MAIN_TYPE *gb_get_main_during_cb() {
109    /* if inside a callback, return the DB root of the DB element, the callback was called for.
110     * if not inside a callback, return NULp.
111     */
112    return inside_callback_main;
113}
114
115NOT4PERL bool GB_inside_callback(GBDATA *of_gbd, GB_CB_TYPE cbtype) {
116    GB_MAIN_TYPE *Main   = gb_get_main_during_cb();
117    bool          inside = false;
118
119    if (Main) {                 // inside a callback
120        gb_assert(currently_called_back);
121        if (currently_called_back->gbd == of_gbd) {
122            GB_CB_TYPE curr_cbtype;
123            if (Main->has_pending_delete_callback()) { // delete callbacks were not all performed yet
124                                                       // => current callback is a delete callback
125                curr_cbtype = GB_CB_TYPE(currently_called_back->spec.get_type() & GB_CB_DELETE);
126            }
127            else {
128                gb_assert(Main->has_pending_change_callback());
129                curr_cbtype = GB_CB_TYPE(currently_called_back->spec.get_type() & (GB_CB_ALL-GB_CB_DELETE));
130            }
131            gb_assert(curr_cbtype != GB_CB_NONE); // wtf!? are we inside callback or not?
132
133            if ((cbtype&curr_cbtype) != GB_CB_NONE) {
134                inside = true;
135            }
136        }
137    }
138
139    return inside;
140}
141
142GBDATA *GB_get_gb_main_during_cb() {
143    GBDATA       *gb_main = NULp;
144    GB_MAIN_TYPE *Main    = gb_get_main_during_cb();
145
146    if (Main) {                 // inside callback
147        if (!GB_inside_callback(Main->gb_main(), GB_CB_DELETE)) { // main is not deleted
148            gb_main = Main->gb_main();
149        }
150    }
151    return gb_main;
152}
153
154static GB_CSTR gb_read_pntr_ts(GBDATA *gbd, gb_transaction_save *ts) {
155    int         type = GB_TYPE_TS(ts);
156    const char *data = GB_GETDATA_TS(ts);
157    if (data) {
158        if (ts->flags.compressed_data) {    // uncompressed data return pntr to database entry
159            long size = GB_GETSIZE_TS(ts) * gb_convert_type_2_sizeof[type] + gb_convert_type_2_appendix_size[type];
160            data = gb_uncompress_data(gbd, data, size);
161        }
162    }
163    return data;
164}
165
166NOT4PERL const void *GB_read_old_value() {
167    // get last array value in callbacks
168    char *data;
169
170    if (!currently_called_back) {
171        GB_export_error("You cannot call GB_read_old_value outside a ARBDB callback");
172        return NULp;
173    }
174    if (!currently_called_back->old) {
175        GB_export_error("No old value available in GB_read_old_value");
176        return NULp;
177    }
178    data = GB_GETDATA_TS(currently_called_back->old);
179    if (!data) return NULp;
180
181    return gb_read_pntr_ts(currently_called_back->gbd, currently_called_back->old);
182}
183long GB_read_old_size() {
184    // same as GB_read_old_value for size
185    if (!currently_called_back) {
186        GB_export_error("You cannot call GB_read_old_size outside a ARBDB callback");
187        return -1;
188    }
189    if (!currently_called_back->old) {
190        GB_export_error("No old value available in GB_read_old_size");
191        return -1;
192    }
193    return GB_GETSIZE_TS(currently_called_back->old);
194}
195
196inline char *cbtype2readable(GB_CB_TYPE type) {
197    ConstStrArray septype;
198
199#define appendcbtype(cbt) do {                  \
200        if (type&cbt) {                         \
201            type = GB_CB_TYPE(type-cbt);        \
202            septype.put(#cbt);                  \
203        }                                       \
204    } while(0)
205
206    appendcbtype(GB_CB_DELETE);
207    appendcbtype(GB_CB_CHANGED);
208    appendcbtype(GB_CB_SON_CREATED);
209
210    gb_assert(type == GB_CB_NONE);
211
212    return GBT_join_strings(septype, '|');
213}
214
215char *TypedDatabaseCallback::get_info() const {
216    const char *readable_fun    = GBS_funptr2readable((void*)dbcb.callee(), true);
217    char       *readable_cbtype = cbtype2readable((GB_CB_TYPE)dbcb.inspect_CD2());
218    char       *result          = GBS_global_string_copy("func='%s' type=%s clientdata=%p",
219                                                         readable_fun, readable_cbtype, (void*)dbcb.inspect_CD1());
220
221    free(readable_cbtype);
222
223    return result;
224}
225
226char *GB_get_callback_info(GBDATA *gbd) {
227    // returns human-readable information about callbacks of 'gbd' or NULp
228    char *result = NULp;
229    if (gbd->ext) {
230        gb_callback_list *cbl = gbd->get_callbacks();
231        if (cbl) {
232            for (gb_callback_list::itertype cb = cbl->callbacks.begin(); cb != cbl->callbacks.end(); ++cb) {
233                char *cb_info = cb->spec.get_info();
234                if (result) {
235                    char *new_result = GBS_global_string_copy("%s\n%s", result, cb_info);
236                    free(result);
237                    free(cb_info);
238                    result = new_result;
239                }
240                else {
241                    result = cb_info;
242                }
243            }
244        }
245    }
246
247    return result;
248}
249
250#if defined(ASSERTION_USED)
251template<typename CB>
252bool CallbackList<CB>::contains_unremoved_callback(const CB& like) const {
253    for (const_itertype cb = callbacks.begin(); cb != callbacks.end(); ++cb) {
254        if (cb->spec.is_equal_to(like.spec) &&
255            !cb->spec.is_marked_for_removal())
256        {
257            return true;
258        }
259    }
260    return false;
261}
262template<>
263bool CallbackList<gb_hierarchy_callback>::contains_unremoved_callback(const gb_hierarchy_callback& like) const {
264    for (const_itertype cb = callbacks.begin(); cb != callbacks.end(); ++cb) {
265        if (cb->spec.is_equal_to(like.spec)   &&
266            !cb->spec.is_marked_for_removal() &&
267            cb->get_location() == like.get_location()) // if location differs, accept duplicate callback
268        {
269            return true;
270        }
271    }
272    return false;
273}
274#endif
275
276template <typename PRED>
277inline void gb_remove_callbacks_that(GBDATA *gbd, PRED shallRemove) {
278#if defined(ASSERTION_USED)
279    if (GB_inside_callback(gbd, GB_CB_DELETE)) {
280        printf("Warning: gb_remove_callback called inside delete-callback of gbd (gbd may already be freed)\n");
281        gb_assert(0); // fix callback-handling (never modify callbacks from inside delete callbacks)
282        return;
283    }
284#endif // DEBUG
285
286    if (gbd->ext) {
287        gb_callback_list *cbl = gbd->get_callbacks();
288        if (cbl) cbl->remove_callbacks_that(shallRemove);
289    }
290}
291
292struct ShallBeDeleted {
293    bool operator()(const gb_callback& cb) const { return cb.spec.is_marked_for_removal(); }
294};
295void gb_remove_callbacks_marked_for_deletion(GBDATA *gbd) {
296    gb_remove_callbacks_that(gbd, ShallBeDeleted());
297}
298
299struct IsCallback : private TypedDatabaseCallback {
300    IsCallback(GB_CB func_, GB_CB_TYPE type_) : TypedDatabaseCallback(makeDatabaseCallback((GB_CB)func_, (int*)NULp), type_) {}
301    bool operator()(const gb_callback& cb) const { return sig_is_equal_to(cb.spec); }
302};
303struct IsSpecificCallback : private TypedDatabaseCallback {
304    IsSpecificCallback(const TypedDatabaseCallback& cb) : TypedDatabaseCallback(cb) {}
305    bool operator()(const gb_callback& cb) const { return is_equal_to(cb.spec); }
306};
307struct IsSpecificHierarchyCallback : private TypedDatabaseCallback {
308    gb_hierarchy_location loc;
309    IsSpecificHierarchyCallback(const gb_hierarchy_location& loc_, const TypedDatabaseCallback& cb)
310        : TypedDatabaseCallback(cb),
311          loc(loc_)
312    {}
313    bool operator()(const gb_callback& cb) const {
314        const gb_hierarchy_callback& hcb = static_cast<const gb_hierarchy_callback&>(cb);
315        return is_equal_to(cb.spec) && hcb.get_location() == loc;
316    }
317};
318
319inline void add_to_callback_chain(gb_callback_list*& head, const TypedDatabaseCallback& cbs) {
320    if (!head) head = new gb_callback_list;
321    head->add(gb_callback(cbs));
322}
323inline void add_to_callback_chain(gb_hierarchy_callback_list*& head, const TypedDatabaseCallback& cbs, const gb_hierarchy_location& loc) {
324    if (!head) head = new gb_hierarchy_callback_list;
325    head->add(gb_hierarchy_callback(cbs, loc));
326}
327
328inline GB_ERROR gb_add_callback(GBDATA *gbd, const TypedDatabaseCallback& cbs) {
329    /* Adds a callback to a DB entry.
330     *
331     * Be careful when writing GB_CB_DELETE callbacks, there is a severe restriction:
332     *
333     * - the DB element may already be freed. The pointer is still pointing to the original
334     *   location, so you can use it to identify the DB element, but you cannot dereference
335     *   it under all circumstances.
336     *
337     * ARBDB internal delete-callbacks may use gb_get_main_during_cb() to access the DB root.
338     * See also: GB_get_gb_main_during_cb()
339     */
340
341#if defined(DEBUG)
342    if (GB_inside_callback(gbd, GB_CB_DELETE)) {
343        printf("Warning: add_priority_callback called inside delete-callback of gbd (gbd may already be freed)\n");
344#if defined(DEVEL_RALF)
345        gb_assert(0); // fix callback-handling (never modify callbacks from inside delete callbacks)
346#endif // DEVEL_RALF
347    }
348#endif // DEBUG
349
350    GB_test_transaction(gbd); // may return error
351    gbd->create_extended();
352    add_to_callback_chain(gbd->ext->callback, cbs);
353    return NULp;
354}
355
356GB_ERROR GB_add_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
357    return gb_add_callback(gbd, TypedDatabaseCallback(dbcb, type));
358}
359
360void GB_remove_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
361    // remove specific callback; 'type' and 'dbcb' have to match
362    gb_remove_callbacks_that(gbd, IsSpecificCallback(TypedDatabaseCallback(dbcb, type)));
363}
364void GB_remove_all_callbacks_to(GBDATA *gbd, GB_CB_TYPE type, GB_CB func) {
365    // removes all callbacks 'func' bound to 'gbd' with 'type'
366    gb_remove_callbacks_that(gbd, IsCallback(func, type));
367}
368
369inline void GB_MAIN_TYPE::callback_group::add_hcb(const gb_hierarchy_location& loc, const TypedDatabaseCallback& dbcb) {
370    add_to_callback_chain(hierarchy_cbs, dbcb, loc);
371}
372inline void GB_MAIN_TYPE::callback_group::remove_hcb(const gb_hierarchy_location& loc, const TypedDatabaseCallback& dbcb) {
373    if (hierarchy_cbs) {
374        hierarchy_cbs->remove_callbacks_that(IsSpecificHierarchyCallback(loc, dbcb));
375    }
376}
377
378#define CHECK_HIER_CB_CONDITION()                                                                       \
379    if (get_transaction_level()<0) return "no hierarchy callbacks allowed in NO_TRANSACTION_MODE";      \
380    if (!loc.is_valid()) return "invalid hierarchy location"
381
382GB_ERROR GB_MAIN_TYPE::add_hierarchy_cb(const gb_hierarchy_location& loc, const TypedDatabaseCallback& dbcb) {
383    CHECK_HIER_CB_CONDITION();
384
385    GB_CB_TYPE type = dbcb.get_type();
386    if (type & GB_CB_DELETE) {
387        deleteCBs.add_hcb(loc, dbcb.with_type_changed_to(GB_CB_DELETE));
388    }
389    if (type & GB_CB_ALL_BUT_DELETE) {
390        changeCBs.add_hcb(loc, dbcb.with_type_changed_to(GB_CB_TYPE(type&GB_CB_ALL_BUT_DELETE)));
391    }
392    return NULp;
393}
394
395GB_ERROR GB_MAIN_TYPE::remove_hierarchy_cb(const gb_hierarchy_location& loc, const TypedDatabaseCallback& dbcb) {
396    CHECK_HIER_CB_CONDITION();
397
398    GB_CB_TYPE type = dbcb.get_type();
399    if (type & GB_CB_DELETE) {
400        deleteCBs.remove_hcb(loc, dbcb.with_type_changed_to(GB_CB_DELETE));
401    }
402    if (type & GB_CB_ALL_BUT_DELETE) {
403        changeCBs.remove_hcb(loc, dbcb.with_type_changed_to(GB_CB_TYPE(type&GB_CB_ALL_BUT_DELETE)));
404    }
405    return NULp;
406}
407
408#undef CHECK_HIER_CB_CONDITION
409
410#if defined(UNIT_TESTS)
411static GB_ERROR GB_add_hierarchy_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) { // currently only used locally in test-code (feel free to publish when needed)
412    /*! bind callback to ALL entries which are at the same DB-hierarchy as 'gbd'.
413     *
414     * Hierarchy callbacks are triggered before normal callbacks (added by GB_add_callback or GB_ensure_callback).
415     * Nevertheless delete callbacks take precedence over change callbacks
416     * (i.e. a normal delete callback is triggered before a hierarchical change callback).
417     *
418     * Hierarchy callbacks cannot be installed and will NOT be triggered in NO_TRANSACTION_MODE
419     * (i.e. it will not work in ARBs property DBs)
420     *
421     * @return error if in NO_TRANSACTION_MODE or if hierarchy location is invalid
422     */
423    return GB_MAIN(gbd)->add_hierarchy_cb(gb_hierarchy_location(gbd), TypedDatabaseCallback(dbcb, type));
424}
425
426static GB_ERROR GB_remove_hierarchy_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) { // currently only used locally in test-code (feel free to publish when needed)
427    //! remove callback added with GB_add_hierarchy_callback()
428    return GB_MAIN(gbd)->remove_hierarchy_cb(gb_hierarchy_location(gbd), TypedDatabaseCallback(dbcb, type));
429}
430#endif
431
432GB_ERROR GB_add_hierarchy_callback(GBDATA *gb_main, const char *db_path, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
433    /*! same as overloaded GB_add_hierarchy_callback(), but using db_path instead of GBDATA to define hierarchy location.
434     *  If the path starts with '/', the full path has to match to trigger the callback.
435     *  Otherwise only the given path has to match (from bottom up),
436     *  e.g. "gb_group" will trigger on any "gb_group" entry (regardless of its parent-path).
437     */
438    return GB_MAIN(gb_main)->add_hierarchy_cb(gb_hierarchy_location(gb_main, db_path), TypedDatabaseCallback(dbcb, type));
439}
440GB_ERROR GB_remove_hierarchy_callback(GBDATA *gb_main, const char *db_path, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
441    //! same as overloaded GB_remove_hierarchy_callback(), but using db_path instead of GBDATA to define hierarchy location
442    return GB_MAIN(gb_main)->remove_hierarchy_cb(gb_hierarchy_location(gb_main, db_path), TypedDatabaseCallback(dbcb, type));
443}
444
445GB_ERROR GB_ensure_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
446    TypedDatabaseCallback newcb(dbcb, type);
447    gb_callback_list *cbl = gbd->get_callbacks();
448    if (cbl) {
449        for (gb_callback_list::itertype cb = cbl->callbacks.begin(); cb != cbl->callbacks.end(); ++cb) {
450            if (cb->spec.is_equal_to(newcb) && !cb->spec.is_marked_for_removal()) {
451                return NULp; // already in cb list
452            }
453        }
454    }
455    return gb_add_callback(gbd, newcb);
456}
457
458void GB_atclose_callback(GBDATA *gbd, const DatabaseCallback& atClose) {
459    /*! Add a callback, which gets called directly before GB_close destroys all data.
460     * This is the recommended way to remove all callbacks from DB elements.
461     */
462
463    GB_MAIN_TYPE *Main = GB_MAIN(gbd);
464    // use standard delete callback, but put in separate list:
465    add_to_callback_chain(Main->close_callbacks, TypedDatabaseCallback(atClose, GB_CB_DELETE));
466}
467
468// --------------------------------------------------------------------------------
469
470#ifdef UNIT_TESTS
471
472#include <string> // needed b4 test_unit.h!
473#include <arb_file.h>
474
475#ifndef TEST_UNIT_H
476#include <test_unit.h>
477#endif
478
479
480// -----------------------
481//      test callbacks
482
483static void test_count_cb(GBDATA *, int *counter) {
484    fprintf(stderr, "test_count_cb: var.add=%p old.val=%i ", counter, *counter);
485    (*counter)++;
486    fprintf(stderr, "new.val=%i\n", *counter);
487    fflush(stderr);
488}
489
490static void remove_self_cb(GBDATA *gbe, GB_CB_TYPE cbtype) {
491    GB_remove_callback(gbe, cbtype, makeDatabaseCallback(remove_self_cb));
492}
493
494static void re_add_self_cb(GBDATA *gbe, int *calledCounter, GB_CB_TYPE cbtype) {
495    ++(*calledCounter);
496
497    DatabaseCallback dbcb = makeDatabaseCallback(re_add_self_cb, calledCounter);
498    GB_remove_callback(gbe, cbtype, dbcb);
499
500    ASSERT_NO_ERROR(GB_add_callback(gbe, cbtype, dbcb));
501}
502
503void TEST_db_callbacks_ta_nota() {
504    GB_shell shell;
505
506    enum TAmode {
507        NO_TA   = 1, // no transaction mode
508        WITH_TA = 2, // transaction mode
509
510        BOTH_TA_MODES = (NO_TA|WITH_TA)
511    };
512
513    for (TAmode ta_mode = NO_TA; ta_mode <= WITH_TA; ta_mode = TAmode(ta_mode+1)) {
514        GBDATA   *gb_main = GB_open("no.arb", "c");
515        GB_ERROR  error;
516
517        TEST_ANNOTATE(ta_mode == NO_TA ? "NO_TA" : "WITH_TA");
518        if (ta_mode == NO_TA) {
519            error = GB_no_transaction(gb_main); TEST_EXPECT_NO_ERROR(error);
520        }
521
522        // create some DB entries
523        GBDATA *gbc;
524        GBDATA *gbe1;
525        GBDATA *gbe2;
526        GBDATA *gbe3;
527        {
528            GB_transaction ta(gb_main);
529
530            gbc  = GB_create_container(gb_main, "cont");
531            gbe1 = GB_create(gbc, "entry", GB_STRING);
532            gbe2 = GB_create(gb_main, "entry", GB_INT);
533        }
534
535        // counters to detect called callbacks
536        int e1_changed    = 0;
537        int e2_changed    = 0;
538        int c_changed     = 0;
539        int c_son_created = 0;
540
541        int e1_deleted = 0;
542        int e2_deleted = 0;
543        int e3_deleted = 0;
544        int c_deleted  = 0;
545
546#define CHCB_COUNTERS_EXPECTATION(e1c,e2c,cc,csc)       \
547        that(e1_changed).is_equal_to(e1c),              \
548            that(e2_changed).is_equal_to(e2c),          \
549            that(c_changed).is_equal_to(cc),            \
550            that(c_son_created).is_equal_to(csc)
551
552#define DLCB_COUNTERS_EXPECTATION(e1d,e2d,e3d,cd)       \
553        that(e1_deleted).is_equal_to(e1d),              \
554            that(e2_deleted).is_equal_to(e2d),          \
555            that(e3_deleted).is_equal_to(e3d),          \
556            that(c_deleted).is_equal_to(cd)
557
558#define TEST_EXPECT_CHCB_COUNTERS(e1c,e2c,cc,csc,tam) do{ if (ta_mode & (tam)) TEST_EXPECTATION(all().of(CHCB_COUNTERS_EXPECTATION(e1c,e2c,cc,csc))); }while(0)
559#define TEST_EXPECT_CHCB___WANTED(e1c,e2c,cc,csc,tam) do{ if (ta_mode & (tam)) TEST_EXPECTATION__WANTED(all().of(CHCB_COUNTERS_EXPECTATION(e1c,e2c,cc,csc))); }while(0)
560
561#define TEST_EXPECT_DLCB_COUNTERS(e1d,e2d,e3d,cd,tam) do{ if (ta_mode & (tam)) TEST_EXPECTATION(all().of(DLCB_COUNTERS_EXPECTATION(e1d,e2d,e3d,cd))); }while(0)
562#define TEST_EXPECT_DLCB___WANTED(e1d,e2d,e3d,cd,tam) do{ if (ta_mode & (tam)) TEST_EXPECTATION__WANTED(all().of(DLCB_COUNTERS_EXPECTATION(e1d,e2d,e3d,cd))); }while(0)
563
564#define TEST_EXPECT_COUNTER(tam,cnt,expected)             do{ if (ta_mode & (tam)) TEST_EXPECT_EQUAL(cnt, expected); }while(0)
565#define TEST_EXPECT_COUNTER__BROKEN(tam,cnt,expected,got) do{ if (ta_mode & (tam)) TEST_EXPECT_EQUAL__BROKEN(cnt, expected, got); }while(0)
566
567#define RESET_CHCB_COUNTERS()   do{ e1_changed = e2_changed = c_changed = c_son_created = 0; }while(0)
568#define RESET_DLCB_COUNTERS()   do{ e1_deleted = e2_deleted = e3_deleted = c_deleted = 0; }while(0)
569#define RESET_ALL_CB_COUNTERS() do{ RESET_CHCB_COUNTERS(); RESET_DLCB_COUNTERS(); }while(0)
570
571        // install some DB callbacks
572        {
573            GB_transaction ta(gb_main);
574            GB_add_callback(gbe1, GB_CB_CHANGED,     makeDatabaseCallback(test_count_cb, &e1_changed));
575            GB_add_callback(gbe2, GB_CB_CHANGED,     makeDatabaseCallback(test_count_cb, &e2_changed));
576            GB_add_callback(gbc,  GB_CB_CHANGED,     makeDatabaseCallback(test_count_cb, &c_changed));
577            GB_add_callback(gbc,  GB_CB_SON_CREATED, makeDatabaseCallback(test_count_cb, &c_son_created));
578        }
579
580        // check callbacks were not called yet
581        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, BOTH_TA_MODES);
582
583        // trigger callbacks
584        {
585            GB_transaction ta(gb_main);
586
587            error = GB_write_string(gbe1, "hi"); TEST_EXPECT_NO_ERROR(error);
588            error = GB_write_int(gbe2, 666);     TEST_EXPECT_NO_ERROR(error);
589
590            TEST_EXPECT_CHCB_COUNTERS(1, 1, 1, 0, NO_TA);   // callbacks triggered instantly in NO_TA mode
591            TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, WITH_TA); // callbacks delayed until transaction is committed
592
593        } // [Note: callbacks happen here in ta_mode]
594
595        // test that GB_CB_SON_CREATED is not triggered here:
596        TEST_EXPECT_CHCB_COUNTERS(1, 1, 1, 0, NO_TA);
597        TEST_EXPECT_CHCB_COUNTERS(1, 1, 1, 0, WITH_TA);
598
599        // really create a son
600        RESET_CHCB_COUNTERS();
601        {
602            GB_transaction ta(gb_main);
603            gbe3 = GB_create(gbc, "e3", GB_STRING);
604        }
605        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, NO_TA); // broken
606        TEST_EXPECT_CHCB___WANTED(0, 0, 1, 1, NO_TA);
607        TEST_EXPECT_CHCB_COUNTERS(0, 0, 1, 1, WITH_TA);
608
609        // change that son
610        RESET_CHCB_COUNTERS();
611        {
612            GB_transaction ta(gb_main);
613            error = GB_write_string(gbe3, "bla"); TEST_EXPECT_NO_ERROR(error);
614        }
615        TEST_EXPECT_CHCB_COUNTERS(0, 0, 1, 0, BOTH_TA_MODES);
616
617
618        // test delete callbacks
619        RESET_CHCB_COUNTERS();
620        {
621            GB_transaction ta(gb_main);
622
623            GB_add_callback(gbe1, GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &e1_deleted));
624            GB_add_callback(gbe2, GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &e2_deleted));
625            GB_add_callback(gbe3, GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &e3_deleted));
626            GB_add_callback(gbc,  GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &c_deleted));
627        }
628        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, BOTH_TA_MODES); // adding callbacks does not trigger existing change-callbacks
629        {
630            GB_transaction ta(gb_main);
631
632            error = GB_delete(gbe3); TEST_EXPECT_NO_ERROR(error);
633            error = GB_delete(gbe2); TEST_EXPECT_NO_ERROR(error);
634
635            TEST_EXPECT_DLCB_COUNTERS(0, 1, 1, 0, NO_TA);
636            TEST_EXPECT_DLCB_COUNTERS(0, 0, 0, 0, WITH_TA);
637        }
638
639        TEST_EXPECT_CHCB_COUNTERS(0, 0, 1, 0, WITH_TA); // container changed by deleting a son (gbe3); no longer triggers via GB_SON_CHANGED
640        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, NO_TA);   // change is not triggered in NO_TA mode (error?)
641        TEST_EXPECT_CHCB___WANTED(0, 0, 1, 1, NO_TA);
642
643        TEST_EXPECT_DLCB_COUNTERS(0, 1, 1, 0, BOTH_TA_MODES);
644
645        RESET_ALL_CB_COUNTERS();
646        {
647            GB_transaction ta(gb_main);
648            error = GB_delete(gbc);  TEST_EXPECT_NO_ERROR(error); // delete the container containing gbe1 and gbe3 (gbe3 alreay deleted)
649        }
650        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, BOTH_TA_MODES); // deleting the container does not trigger any change callbacks
651        TEST_EXPECT_DLCB_COUNTERS(1, 0, 0, 1, BOTH_TA_MODES); // deleting the container does also trigger the delete callback for gbe1
652
653        // --------------------------------------------------------------------------------
654        // document that a callback now can be removed while it is running
655        // (in NO_TA mode; always worked in WITH_TA mode)
656        {
657            GBDATA *gbe;
658            {
659                GB_transaction ta(gb_main);
660                gbe = GB_create(gb_main, "new_e1", GB_INT); // recreate
661                GB_add_callback(gbe, GB_CB_CHANGED, makeDatabaseCallback(remove_self_cb));
662            }
663            { GB_transaction ta(gb_main); GB_touch(gbe); }
664        }
665
666        // test that a callback may remove and re-add itself
667        {
668            GBDATA *gbn1;
669            GBDATA *gbn2;
670
671            int counter1 = 0;
672            int counter2 = 0;
673
674            {
675                GB_transaction ta(gb_main);
676                gbn1 = GB_create(gb_main, "new_e1", GB_INT);
677                gbn2 = GB_create(gb_main, "new_e2", GB_INT);
678                GB_add_callback(gbn1, GB_CB_CHANGED, makeDatabaseCallback(re_add_self_cb, &counter1));
679            }
680
681            TEST_EXPECT_COUNTER(NO_TA,         counter1, 0); // no callback triggered (trigger happens BEFORE call to GB_add_callback in NO_TA mode!)
682            TEST_EXPECT_COUNTER(WITH_TA,       counter1, 1); // callback gets triggered by GB_create
683            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 0);
684
685            counter1 = 0; counter2 = 0;
686
687            // test no callback is triggered by just adding a callback
688            {
689                GB_transaction ta(gb_main);
690                GB_add_callback(gbn2, GB_CB_CHANGED, makeDatabaseCallback(re_add_self_cb, &counter2));
691            }
692            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 0);
693            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 0);
694
695            { GB_transaction ta(gb_main); GB_touch(gbn1); }
696            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 1);
697            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 0);
698
699            { GB_transaction ta(gb_main); GB_touch(gbn2); }
700            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 1);
701            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 1);
702
703            { GB_transaction ta(gb_main);  }
704            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 1);
705            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 1);
706        }
707
708        GB_close(gb_main);
709    }
710}
711
712struct calledWith {
713    RefPtr<GBDATA> gbd;
714    GB_CB_TYPE     type;
715    int            time_called;
716
717    static int timer;
718
719    calledWith(GBDATA *gbd_, GB_CB_TYPE type_) : gbd(gbd_), type(type_), time_called(++timer) {}
720};
721
722using std::string;
723using std::list;
724
725class callback_trace;
726
727class ct_registry {
728    static ct_registry *SINGLETON;
729
730    typedef list<callback_trace*> ctl;
731
732    ctl traces;
733public:
734    ct_registry() {
735        gb_assert(!SINGLETON);
736        SINGLETON = this;
737    }
738    ~ct_registry() {
739        gb_assert(this == SINGLETON);
740        SINGLETON = NULp;
741        gb_assert(traces.empty());
742    }
743
744    static ct_registry *instance() { gb_assert(SINGLETON); return SINGLETON; }
745
746    void add(callback_trace *ct) { traces.push_back(ct); }
747    void remove(callback_trace *ct) { traces.remove(ct); }
748
749    arb_test::match_expectation expect_all_calls_checked();
750};
751ct_registry *ct_registry::SINGLETON = NULp;
752
753int calledWith::timer = 0;
754
755class callback_trace {
756    typedef list<calledWith> calledList;
757    typedef calledList::iterator  calledIter;
758
759    calledList called;
760    string     name;
761
762    calledIter find(GBDATA *gbd) {
763        calledIter c = called.begin();
764        while (c != called.end()) {
765            if (c->gbd == gbd) break;
766            ++c;
767        }
768        return c;
769    }
770    calledIter find(GB_CB_TYPE exp_type) {
771        calledIter c = called.begin();
772        while (c != called.end()) {
773            if (c->type&exp_type) break;
774            ++c;
775        }
776        return c;
777    }
778    calledIter find(GBDATA *gbd, GB_CB_TYPE exp_type) {
779        calledIter c = called.begin();
780        while (c != called.end()) {
781            if (c->gbd == gbd && (c->type&exp_type)) break;
782            ++c;
783        }
784        return c;
785    }
786
787    bool removed(calledIter c) {
788        if (c == called.end()) return false;
789        called.erase(c);
790        return true;
791    }
792
793public:
794    callback_trace(const char *name_)
795        : name(name_)
796    {
797        called.clear();
798        ct_registry::instance()->add(this);
799    }
800    ~callback_trace() {
801        if (was_called()) TEST_EXPECT_EQUAL(name, "has unchecked calls in dtor"); // you forgot to check some calls using TEST_EXPECT_..._TRIGGERED
802        ct_registry::instance()->remove(this);
803    }
804
805    const string& get_name() const { return name; }
806
807    void set_called_by(GBDATA *gbd, GB_CB_TYPE type) { called.push_back(calledWith(gbd, type)); }
808
809    bool was_called_by(GBDATA *gbd) { return removed(find(gbd)); }
810    bool was_called_by(GB_CB_TYPE exp_type) { return removed(find(exp_type)); }
811    bool was_called_by(GBDATA *gbd, GB_CB_TYPE exp_type) { return removed(find(gbd, exp_type)); }
812
813    int call_time(GBDATA *gbd, GB_CB_TYPE exp_type) {
814        calledIter found = find(gbd, exp_type);
815        if (found == called.end()) return -1;
816
817        int t = found->time_called;
818        removed(found);
819        return t;
820    }
821
822    bool was_not_called() const { return called.empty(); }
823    bool was_called() const { return !was_not_called(); }
824};
825
826arb_test::match_expectation ct_registry::expect_all_calls_checked() {
827    using namespace   arb_test;
828    expectation_group expected;
829
830    // add failing expectations for all traces with unchecked calls
831    for (ctl::iterator t = traces.begin(); t != traces.end(); ++t) {
832        callback_trace *ct = *t;
833        if (ct->was_called()) {
834            expectation_group bad_trace;
835            bad_trace.add(that(ct->was_called()).is_equal_to(false));
836
837            const char *failing_trace = ct->get_name().c_str();
838            bad_trace.add(that(failing_trace).does_differ_from(failing_trace)); // display failing_trace in failing expectation
839
840            expected.add(all().ofgroup(bad_trace));
841        }
842    }
843
844    return all().ofgroup(expected);
845}
846
847
848static void some_cb(GBDATA *gbd, callback_trace *trace, GB_CB_TYPE cbtype) {
849    trace->set_called_by(gbd, cbtype);
850}
851
852#define TRACESTRUCT(ELEM,FLAVOR)           trace_##ELEM##_##FLAVOR
853#define HIERARCHY_TRACESTRUCT(ELEM,FLAVOR) traceHier_##ELEM##_##FLAVOR
854
855#define CONSTRUCT(name)                       name(#name) // pass instance-name to callback_trace-ctor as char*
856#define TRACECONSTRUCT(ELEM,FLAVOR)           CONSTRUCT(TRACESTRUCT(ELEM,FLAVOR))
857#define HIERARCHY_TRACECONSTRUCT(ELEM,FLAVOR) CONSTRUCT(HIERARCHY_TRACESTRUCT(ELEM,FLAVOR))
858
859#define ADD_CHANGED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,changed))))
860#define ADD_DELETED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,deleted))))
861#define ADD_NWCHILD_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,newchild))))
862
863#define ADD_CHANGED_HIERARCHY_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(elem,changed))))
864#define ADD_DELETED_HIERARCHY_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(elem,deleted))))
865#define ADD_NWCHILD_HIERARCHY_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(elem,newchild))))
866
867#define ADD_CHANGED_HIERARCHY_CALLBACK2(gbm,path,id) TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gbm, path, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(id,changed))))
868#define ADD_DELETED_HIERARCHY_CALLBACK2(gbm,path,id) TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gbm, path, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(id,deleted))))
869#define ADD_NWCHILD_HIERARCHY_CALLBACK2(gbm,path,id) TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gbm, path, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(id,newchild))))
870
871#define ENSURE_CHANGED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_ensure_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,changed))))
872#define ENSURE_DELETED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_ensure_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,deleted))))
873#define ENSURE_NWCHILD_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_ensure_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,newchild))))
874
875#define REMOVE_CHANGED_CALLBACK(elem) GB_remove_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,changed)))
876#define REMOVE_DELETED_CALLBACK(elem) GB_remove_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,deleted)))
877#define REMOVE_NWCHILD_CALLBACK(elem) GB_remove_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,newchild)))
878
879#define REMOVE_CHANGED_HIERARCHY_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(elem,changed))))
880#define REMOVE_DELETED_HIERARCHY_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(elem,deleted))))
881#define REMOVE_NWCHILD_HIERARCHY_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(elem,newchild))))
882
883#define REMOVE_CHANGED_HIERARCHY_CALLBACK2(gbm,path,id) TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(gbm, path, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(id, changed))))
884#define REMOVE_DELETED_HIERARCHY_CALLBACK2(gbm,path,id) TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(gbm, path, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(id, deleted))))
885#define REMOVE_NWCHILD_HIERARCHY_CALLBACK2(gbm,path,id) TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(gbm, path, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(id, newchild))))
886
887#define INIT_CHANGED_CALLBACK(elem) callback_trace TRACECONSTRUCT(elem,changed);  ADD_CHANGED_CALLBACK(elem)
888#define INIT_DELETED_CALLBACK(elem) callback_trace TRACECONSTRUCT(elem,deleted);  ADD_DELETED_CALLBACK(elem)
889#define INIT_NWCHILD_CALLBACK(elem) callback_trace TRACECONSTRUCT(elem,newchild); ADD_NWCHILD_CALLBACK(elem)
890
891#define INIT_CHANGED_HIERARCHY_CALLBACK(elem) callback_trace HIERARCHY_TRACECONSTRUCT(elem,changed);  ADD_CHANGED_HIERARCHY_CALLBACK(elem)
892#define INIT_DELETED_HIERARCHY_CALLBACK(elem) callback_trace HIERARCHY_TRACECONSTRUCT(elem,deleted);  ADD_DELETED_HIERARCHY_CALLBACK(elem)
893#define INIT_NWCHILD_HIERARCHY_CALLBACK(elem) callback_trace HIERARCHY_TRACECONSTRUCT(elem,newchild); ADD_NWCHILD_HIERARCHY_CALLBACK(elem)
894
895#define INIT_CHANGED_HIERARCHY_CALLBACK2(gbm,path,id) callback_trace HIERARCHY_TRACECONSTRUCT(id,changed);  ADD_CHANGED_HIERARCHY_CALLBACK2(gbm,path,id)
896#define INIT_DELETED_HIERARCHY_CALLBACK2(gbm,path,id) callback_trace HIERARCHY_TRACECONSTRUCT(id,deleted);  ADD_DELETED_HIERARCHY_CALLBACK2(gbm,path,id)
897#define INIT_NWCHILD_HIERARCHY_CALLBACK2(gbm,path,id) callback_trace HIERARCHY_TRACECONSTRUCT(id,newchild); ADD_NWCHILD_HIERARCHY_CALLBACK2(gbm,path,id)
898
899#define ENSURE_ENTRY_CALLBACKS(entry)    ENSURE_CHANGED_CALLBACK(entry); ENSURE_DELETED_CALLBACK(entry)
900#define ENSURE_CONTAINER_CALLBACKS(cont) ENSURE_CHANGED_CALLBACK(cont);  ENSURE_NWCHILD_CALLBACK(cont); ENSURE_DELETED_CALLBACK(cont)
901
902#define REMOVE_ENTRY_CALLBACKS(entry)    REMOVE_CHANGED_CALLBACK(entry); REMOVE_DELETED_CALLBACK(entry)
903#define REMOVE_CONTAINER_CALLBACKS(cont) REMOVE_CHANGED_CALLBACK(cont);  REMOVE_NWCHILD_CALLBACK(cont); REMOVE_DELETED_CALLBACK(cont)
904
905#define INIT_ENTRY_CALLBACKS(entry)    INIT_CHANGED_CALLBACK(entry); INIT_DELETED_CALLBACK(entry)
906#define INIT_CONTAINER_CALLBACKS(cont) INIT_CHANGED_CALLBACK(cont);  INIT_NWCHILD_CALLBACK(cont); INIT_DELETED_CALLBACK(cont)
907
908#define TRIGGER_CHANGE(gbd) do {                \
909        GB_initial_transaction ta(gb_main);     \
910        if (ta.ok()) GB_touch(gbd);             \
911        TEST_EXPECT_NO_ERROR(ta.close(NULp));   \
912    } while(0)
913
914#define TRIGGER_2_CHANGES(gbd1, gbd2) do {      \
915        GB_initial_transaction ta(gb_main);     \
916        if (ta.ok()) {                          \
917            GB_touch(gbd1);                     \
918            GB_touch(gbd2);                     \
919        }                                       \
920        TEST_EXPECT_NO_ERROR(ta.close(NULp));   \
921    } while(0)
922
923#define TRIGGER_DELETE(gbd) do {                \
924        GB_initial_transaction ta(gb_main);     \
925        GB_ERROR error = NULp;                  \
926        if (ta.ok()) error = GB_delete(gbd);    \
927        TEST_EXPECT_NO_ERROR(ta.close(error));  \
928    } while(0)
929
930#define TEST_EXPECT_CB_TRIGGERED(TRACE,GBD,TYPE)         TEST_EXPECT(TRACE.was_called_by(GBD, TYPE))
931#define TEST_EXPECT_CB_TRIGGERED_AT(TRACE,GBD,TYPE,TIME) TEST_EXPECT_EQUAL(TRACE.call_time(GBD, TYPE), TIME)
932
933#define TEST_EXPECT_CHANGE_TRIGGERED(GBD) TEST_EXPECT_CB_TRIGGERED(TRACESTRUCT(GBD, changed),  GBD, GB_CB_CHANGED)
934#define TEST_EXPECT_DELETE_TRIGGERED(GBD) TEST_EXPECT_CB_TRIGGERED(TRACESTRUCT(GBD, deleted),  GBD, GB_CB_DELETE)
935#define TEST_EXPECT_NCHILD_TRIGGERED(GBD) TEST_EXPECT_CB_TRIGGERED(TRACESTRUCT(GBD, newchild), GBD, GB_CB_SON_CREATED)
936
937#define TEST_EXPECT_CHANGE_HIER_TRIGGERED(NAME,GBD) TEST_EXPECT_CB_TRIGGERED(HIERARCHY_TRACESTRUCT(NAME, changed),  GBD, GB_CB_CHANGED)
938#define TEST_EXPECT_DELETE_HIER_TRIGGERED(NAME,GBD) TEST_EXPECT_CB_TRIGGERED(HIERARCHY_TRACESTRUCT(NAME, deleted),  GBD, GB_CB_DELETE)
939#define TEST_EXPECT_NCHILD_HIER_TRIGGERED(NAME,GBD) TEST_EXPECT_CB_TRIGGERED(HIERARCHY_TRACESTRUCT(NAME, newchild), GBD, GB_CB_SON_CREATED)
940
941
942#define TEST_EXPECT_CHANGE_TRIGGERED_AT(TRACE,GBD,TIME) TEST_EXPECT_CB_TRIGGERED_AT(TRACE, GBD, GB_CB_CHANGED, TIME)
943#define TEST_EXPECT_DELETE_TRIGGERED_AT(TRACE,GBD,TIME) TEST_EXPECT_CB_TRIGGERED_AT(TRACE, GBD, GB_CB_DELETE, TIME)
944
945#define TEST_EXPECT_TRIGGERS_CHECKED() TEST_EXPECTATION(trace_registry.expect_all_calls_checked())
946
947void TEST_db_callbacks() {
948    GB_shell  shell;
949    GBDATA   *gb_main = GB_open("new.arb", "c");
950
951    // create some data
952    GB_begin_transaction(gb_main);
953
954    GBDATA *cont_top = GB_create_container(gb_main,  "cont_top"); TEST_REJECT_NULL(cont_top);
955    GBDATA *cont_son = GB_create_container(cont_top, "cont_son"); TEST_REJECT_NULL(cont_son);
956
957    GBDATA *top       = GB_create(gb_main,  "top",       GB_STRING); TEST_REJECT_NULL(top);
958    GBDATA *son       = GB_create(cont_top, "son",       GB_INT);    TEST_REJECT_NULL(son);
959    GBDATA *grandson  = GB_create(cont_son, "grandson",  GB_STRING); TEST_REJECT_NULL(grandson);
960    GBDATA *ograndson = GB_create(cont_son, "ograndson", GB_STRING); TEST_REJECT_NULL(ograndson);
961
962    GB_commit_transaction(gb_main);
963
964    // install callbacks
965    GB_begin_transaction(gb_main);
966
967    ct_registry trace_registry;
968    INIT_CONTAINER_CALLBACKS(cont_top);
969    INIT_CONTAINER_CALLBACKS(cont_son);
970    INIT_ENTRY_CALLBACKS(top);
971    INIT_ENTRY_CALLBACKS(son);
972    INIT_ENTRY_CALLBACKS(grandson);
973    INIT_ENTRY_CALLBACKS(ograndson);
974
975    GB_commit_transaction(gb_main);
976
977    TEST_EXPECT_TRIGGERS_CHECKED();
978
979    // trigger change callbacks via change
980    GB_begin_transaction(gb_main);
981    GB_write_string(top, "hello world");
982    GB_commit_transaction(gb_main);
983    TEST_EXPECT_CHANGE_TRIGGERED(top);
984    TEST_EXPECT_TRIGGERS_CHECKED();
985
986    GB_begin_transaction(gb_main);
987    GB_write_string(top, "hello world"); // no change
988    GB_commit_transaction(gb_main);
989    TEST_EXPECT_TRIGGERS_CHECKED();
990
991#if 0
992    // code is wrong (cannot set terminal entry to "marked")
993    GB_begin_transaction(gb_main);
994    GB_write_flag(son, 1);                                  // only change "mark"
995    GB_commit_transaction(gb_main);
996    TEST_EXPECT_CHANGE_TRIGGERED(son);
997    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
998    TEST_EXPECT_TRIGGER__UNWANTED(trace_cont_top_newchild); // @@@ modifying son should not trigger newchild callback
999    TEST_EXPECT_TRIGGERS_CHECKED();
1000#else
1001    // @@@ add test code similar to wrong section above
1002#endif
1003
1004    GB_begin_transaction(gb_main);
1005    GB_touch(grandson);
1006    GB_commit_transaction(gb_main);
1007    TEST_EXPECT_CHANGE_TRIGGERED(grandson);
1008    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1009    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1010    TEST_EXPECT_TRIGGERS_CHECKED();
1011
1012    // trigger change- and soncreate-callbacks via create
1013
1014    GB_begin_transaction(gb_main);
1015    GBDATA *son2 = GB_create(cont_top, "son2", GB_INT); TEST_REJECT_NULL(son2);
1016    GB_commit_transaction(gb_main);
1017    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1018    TEST_EXPECT_NCHILD_TRIGGERED(cont_top);
1019    TEST_EXPECT_TRIGGERS_CHECKED();
1020
1021    GB_begin_transaction(gb_main);
1022    GBDATA *grandson2 = GB_create(cont_son, "grandson2", GB_STRING); TEST_REJECT_NULL(grandson2);
1023    GB_commit_transaction(gb_main);
1024    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1025    TEST_EXPECT_NCHILD_TRIGGERED(cont_son);
1026    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1027    TEST_EXPECT_TRIGGERS_CHECKED();
1028
1029    // trigger callbacks via delete
1030
1031    TRIGGER_DELETE(son2);
1032    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1033    TEST_EXPECT_TRIGGERS_CHECKED();
1034
1035    TRIGGER_DELETE(grandson2);
1036    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1037    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1038    TEST_EXPECT_TRIGGERS_CHECKED();
1039
1040    TEST_EXPECT_NO_ERROR(GB_request_undo_type(gb_main, GB_UNDO_UNDO));
1041
1042    TRIGGER_DELETE(top);
1043    TEST_EXPECT_DELETE_TRIGGERED(top);
1044    TEST_EXPECT_TRIGGERS_CHECKED();
1045
1046    TRIGGER_DELETE(grandson);
1047    TEST_EXPECT_DELETE_TRIGGERED(grandson);
1048    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1049    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1050    TEST_EXPECT_TRIGGERS_CHECKED();
1051
1052    TRIGGER_DELETE(cont_son);
1053    TEST_EXPECT_DELETE_TRIGGERED(ograndson);
1054    TEST_EXPECT_DELETE_TRIGGERED(cont_son);
1055    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1056    TEST_EXPECT_TRIGGERS_CHECKED();
1057
1058    // trigger callbacks by undoing last 3 delete transactions
1059
1060    TEST_EXPECT_NO_ERROR(GB_undo(gb_main, GB_UNDO_UNDO)); // undo delete of cont_son
1061    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1062    TEST_EXPECT_NCHILD_TRIGGERED(cont_top);
1063    TEST_EXPECT_TRIGGERS_CHECKED();
1064
1065    TEST_EXPECT_NO_ERROR(GB_undo(gb_main, GB_UNDO_UNDO)); // undo delete of grandson
1066    // cont_son callbacks are not triggered (they are not restored by undo)
1067    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1068    TEST_EXPECT_TRIGGERS_CHECKED();
1069
1070    TEST_EXPECT_NO_ERROR(GB_undo(gb_main, GB_UNDO_UNDO)); // undo delete of top
1071    TEST_EXPECT_TRIGGERS_CHECKED();
1072
1073    // reinstall callbacks that were removed by deletes
1074
1075    GB_begin_transaction(gb_main);
1076    ENSURE_CONTAINER_CALLBACKS(cont_top);
1077    ENSURE_CONTAINER_CALLBACKS(cont_son);
1078    ENSURE_ENTRY_CALLBACKS(top);
1079    ENSURE_ENTRY_CALLBACKS(son);
1080    ENSURE_ENTRY_CALLBACKS(grandson);
1081    GB_commit_transaction(gb_main);
1082    TEST_EXPECT_TRIGGERS_CHECKED();
1083
1084    // trigger callbacks which will be removed
1085
1086    TRIGGER_CHANGE(son);
1087    TEST_EXPECT_CHANGE_TRIGGERED(son);
1088    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1089    TEST_EXPECT_TRIGGERS_CHECKED();
1090
1091    GB_begin_transaction(gb_main);
1092    GBDATA *son3 = GB_create(cont_top, "son3", GB_INT); TEST_REJECT_NULL(son3);
1093    GB_commit_transaction(gb_main);
1094    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1095    TEST_EXPECT_NCHILD_TRIGGERED(cont_top);
1096    TEST_EXPECT_TRIGGERS_CHECKED();
1097
1098    // test remove callback
1099
1100    GB_begin_transaction(gb_main);
1101    REMOVE_ENTRY_CALLBACKS(son);
1102    REMOVE_CONTAINER_CALLBACKS(cont_top);
1103    GB_commit_transaction(gb_main);
1104    TEST_EXPECT_TRIGGERS_CHECKED();
1105
1106    // "trigger" removed callbacks
1107
1108    TRIGGER_CHANGE(son);
1109    TEST_EXPECT_TRIGGERS_CHECKED();
1110
1111    GB_begin_transaction(gb_main);
1112    GBDATA *son4 = GB_create(cont_top, "son4", GB_INT); TEST_REJECT_NULL(son4);
1113    GB_commit_transaction(gb_main);
1114    TEST_EXPECT_TRIGGERS_CHECKED();
1115
1116    // avoid that GB_close calls delete callbacks (@@@ which might be an error in GB_close!)
1117    // by removing remaining callbacks
1118    REMOVE_ENTRY_CALLBACKS(grandson);
1119    REMOVE_ENTRY_CALLBACKS(top);
1120    REMOVE_CONTAINER_CALLBACKS(cont_son);
1121
1122    GB_close(gb_main);
1123}
1124
1125__ATTR__REDUCED_OPTIMIZE void TEST_hierarchy_callbacks() {
1126    GB_shell    shell;
1127    const char *DBNAME  = "tmp_hier_cb.arb";
1128
1129    for (int pass = 1; pass<=2; ++pass) {
1130        bool creating = pass == 1;
1131        TEST_ANNOTATE(GBS_global_string("%s database", creating ? "created" : "reloaded"));
1132
1133        GBDATA *gb_main = pass == 1 ? GB_open(DBNAME, "cw") : GB_open(DBNAME, "r");
1134        TEST_REJECT_NULL(gb_main);
1135
1136        // create some data
1137        GB_begin_transaction(gb_main);
1138
1139        GBDATA *cont_top1 = creating ? GB_create_container(gb_main, "cont_top") : GB_entry(gb_main, "cont_top"); TEST_REJECT_NULL(cont_top1);
1140        GBDATA *cont_top2 = creating ? GB_create_container(gb_main, "cont_top") : GB_nextEntry(cont_top1);       TEST_REJECT_NULL(cont_top2);
1141
1142        GBDATA *cont_son11 = creating ? GB_create_container(cont_top1, "cont_son") : GB_entry(cont_top1, "cont_son"); TEST_REJECT_NULL(cont_son11);
1143        GBDATA *cont_son12 = creating ? GB_create_container(cont_top1, "cont_son") : GB_nextEntry(cont_son11);        TEST_REJECT_NULL(cont_son12); // leave this container empty!
1144        GBDATA *cont_son21 = creating ? GB_create_container(cont_top2, "cont_son") : GB_entry(cont_top2, "cont_son"); TEST_REJECT_NULL(cont_son21);
1145        GBDATA *cont_son22 = creating ? GB_create_container(cont_top2, "cont_son") : GB_nextEntry(cont_son21);        TEST_REJECT_NULL(cont_son22);
1146
1147        GBDATA *cson_deep = creating ? GB_create_container(cont_son11, "cont_son") : GB_entry(cont_son11, "cont_son"); TEST_REJECT_NULL(cson_deep);
1148
1149        GBDATA *top1 = creating ? GB_create(gb_main, "top", GB_STRING) : GB_entry(gb_main, "top"); TEST_REJECT_NULL(top1);
1150        GBDATA *top2 = creating ? GB_create(gb_main, "top", GB_STRING) : GB_nextEntry(top1);       TEST_REJECT_NULL(top2);
1151
1152        GBDATA *son11 = creating ? GB_create(cont_top1, "son", GB_INT) : GB_entry(cont_top1, "son"); TEST_REJECT_NULL(son11);
1153        GBDATA *son12 = creating ? GB_create(cont_top1, "son", GB_INT) : GB_nextEntry(son11);        TEST_REJECT_NULL(son12);
1154        GBDATA *son21 = creating ? GB_create(cont_top2, "son", GB_INT) : GB_entry(cont_top2, "son"); TEST_REJECT_NULL(son21);
1155
1156        GBDATA *grandson111 = creating ? GB_create(cont_son11, "grandson", GB_STRING) : GB_entry(cont_son11, "grandson"); TEST_REJECT_NULL(grandson111);
1157        GBDATA *grandson112 = creating ? GB_create(cont_son11, "grandson", GB_STRING) : GB_nextEntry(grandson111);        TEST_REJECT_NULL(grandson112);
1158        GBDATA *grandson211 = creating ? GB_create(cont_son21, "grandson", GB_STRING) : GB_entry(cont_son21, "grandson"); TEST_REJECT_NULL(grandson211);
1159        GBDATA *grandson221 = creating ? GB_create(cont_son22, "grandson", GB_STRING) : GB_entry(cont_son22, "grandson"); TEST_REJECT_NULL(grandson221);
1160        GBDATA *grandson222 = creating ? GB_create(cont_son22, "grandson", GB_STRING) : GB_nextEntry(grandson221);        TEST_REJECT_NULL(grandson222);
1161
1162        // create some entries at uncommon hierarchy locations (compared to entries created above)
1163        GBDATA *ctop_top = creating ? GB_create          (cont_top2, "top", GB_STRING)      : GB_entry(cont_top2, "top");      TEST_REJECT_NULL(ctop_top);
1164        GBDATA *top_son  = creating ? GB_create          (gb_main,   "son", GB_INT)         : GB_entry(gb_main,   "son");      TEST_REJECT_NULL(top_son);
1165        GBDATA *cson     = creating ? GB_create_container(gb_main,   "cont_son")            : GB_entry(gb_main,   "cont_son"); TEST_REJECT_NULL(cson);
1166        GBDATA *cson_gs  = creating ? GB_create          (cson,      "grandson", GB_STRING) : GB_entry(cson,      "grandson"); TEST_REJECT_NULL(cson_gs);
1167
1168        GB_commit_transaction(gb_main);
1169
1170        // test gb_hierarchy_location
1171        {
1172            GB_transaction ta(gb_main);
1173
1174            gb_hierarchy_location loc_top(top1);
1175            gb_hierarchy_location loc_son(son11);
1176            gb_hierarchy_location loc_grandson(grandson222);
1177
1178            TEST_REJECT(loc_top.is_submatch());
1179            TEST_REJECT(loc_son.is_submatch());
1180            TEST_REJECT(loc_grandson.is_submatch());
1181
1182            TEST_EXPECT(loc_top.matches(top1));
1183            TEST_EXPECT(loc_top.matches(top2));
1184            TEST_EXPECT(!loc_top.matches(cont_top1));
1185            TEST_EXPECT(!loc_top.matches(son12));
1186            TEST_EXPECT(!loc_top.matches(cont_son22));
1187            TEST_EXPECT(!loc_top.matches(ctop_top));
1188
1189            TEST_EXPECT(loc_son.matches(son11));
1190            TEST_EXPECT(loc_son.matches(son21));
1191            TEST_EXPECT(!loc_son.matches(top1));
1192            TEST_EXPECT(!loc_son.matches(grandson111));
1193            TEST_EXPECT(!loc_son.matches(cont_son22));
1194            TEST_EXPECT(!loc_son.matches(top_son));
1195
1196            TEST_EXPECT(loc_grandson.matches(grandson222));
1197            TEST_EXPECT(loc_grandson.matches(grandson111));
1198            TEST_EXPECT(!loc_grandson.matches(son11));
1199            TEST_EXPECT(!loc_grandson.matches(top1));
1200            TEST_EXPECT(!loc_grandson.matches(cont_son22));
1201            TEST_EXPECT(!loc_grandson.matches(cson_gs));
1202            TEST_EXPECT(!loc_grandson.matches(gb_main)); // nothing matches gb_main
1203
1204            gb_hierarchy_location loc_ctop_top(ctop_top);
1205            TEST_EXPECT(loc_ctop_top.matches(ctop_top));
1206            TEST_EXPECT(!loc_ctop_top.matches(top1));
1207
1208            gb_hierarchy_location loc_top_son(top_son);
1209            TEST_EXPECT(loc_top_son.matches(top_son));
1210            TEST_EXPECT(!loc_top_son.matches(son11));
1211            TEST_EXPECT(!loc_top_son.matches(gb_main)); // nothing matches gb_main
1212
1213            gb_hierarchy_location loc_gs(cson_gs);
1214            TEST_EXPECT(loc_gs.matches(cson_gs));
1215            TEST_EXPECT(!loc_gs.matches(grandson211));
1216
1217            gb_hierarchy_location loc_root(gb_main);
1218            TEST_REJECT(loc_root.is_valid()); // deny binding hierarchy callback to gb_main
1219            TEST_EXPECT(!loc_root.matches(gb_main));   // nothing matches gb_main
1220            TEST_EXPECT(!loc_root.matches(cont_top1)); // nothing matches an invalid location
1221
1222            // test location from/to path
1223            {
1224                char *path_grandson = loc_grandson.get_db_path(gb_main);
1225                TEST_EXPECT_EQUAL(path_grandson, "/cont_top/cont_son/grandson");
1226
1227                gb_hierarchy_location loc_grandson2(gb_main, path_grandson);
1228                TEST_EXPECT(loc_grandson2.is_valid());
1229                TEST_EXPECT(loc_grandson == loc_grandson2);
1230
1231                char *path_grandson2 = loc_grandson2.get_db_path(gb_main);
1232                TEST_EXPECT_EQUAL(path_grandson, path_grandson2);
1233
1234                free(path_grandson2);
1235                free(path_grandson);
1236            }
1237
1238            gb_hierarchy_location loc_invalid(gb_main, "");
1239            TEST_REJECT(loc_invalid.is_valid());
1240            TEST_REJECT(loc_invalid == loc_invalid); // invalid locations equal nothing
1241
1242            TEST_EXPECT(gb_hierarchy_location(gb_main, "/grandson/cont_top/son").is_valid()); // non-existing path with existing keys is valid
1243            TEST_EXPECT(gb_hierarchy_location(gb_main, "/no/such/path").is_valid());          // non-existing keys generate key-quarks on-the-fly
1244            TEST_EXPECT(gb_hierarchy_location(gb_main, "/grandson/missing/son").is_valid());  // non-existing keys generate key-quarks on-the-fly
1245
1246            gb_hierarchy_location loc_submatch(gb_main, "cont_son/grandson"); // sub-location (any parents will be accepted)
1247            TEST_EXPECT(loc_submatch.is_valid());
1248            TEST_EXPECT(loc_submatch.is_submatch());
1249            TEST_EXPECT(loc_submatch.matches(cson_gs));
1250            TEST_EXPECT(loc_submatch.matches(grandson111));
1251            TEST_REJECT(loc_submatch == loc_grandson); // but loc_grandson.matches(grandson111)!
1252
1253            gb_hierarchy_location loc_anyson(gb_main, "son"); // sub-location (any 'son' location)
1254            TEST_EXPECT(loc_anyson.is_valid());
1255            TEST_EXPECT(loc_anyson.is_submatch());
1256            TEST_EXPECT(loc_anyson.matches(top_son));
1257            TEST_EXPECT(loc_anyson.matches(son11));
1258            TEST_REJECT(loc_anyson == loc_top_son); // but loc_top_son.matches(top_son)!
1259            TEST_REJECT(loc_anyson == loc_son);     // but loc_son.matches(son11)!
1260
1261            TEST_EXPECT_EQUAL(&*SmartCharPtr(loc_submatch.get_db_path(gb_main)), "cont_son/grandson");
1262            TEST_EXPECT_EQUAL(&*SmartCharPtr(loc_anyson  .get_db_path(gb_main)), "son");
1263
1264            // test some pathological locations:
1265            TEST_REJECT(gb_hierarchy_location(gb_main, "/")    .is_valid());
1266            TEST_REJECT(gb_hierarchy_location(gb_main, "//")   .is_valid());
1267            TEST_EXPECT(gb_hierarchy_location(gb_main, "  /  ").is_valid());    // now a valid sub-location (seems like '  ' is a valid key)
1268            TEST_REJECT(gb_hierarchy_location(gb_main, "son//son").is_valid()); // invalid sub-location
1269            TEST_REJECT(gb_hierarchy_location(gb_main, NULp)   .is_valid());
1270        }
1271
1272        if (pass == 1) {
1273            TEST_EXPECT_NO_ERROR(GB_save_as(gb_main, DBNAME, "wb"));
1274        }
1275
1276        // instanciate callback_trace data and install hierarchy callbacks
1277        GBDATA *anySon = son11;
1278
1279        GBDATA *anySonContainer     = cont_son11;
1280        GBDATA *anotherSonContainer = cont_son22;
1281
1282        GBDATA *anyGrandson     = grandson221;
1283        GBDATA *anotherGrandson = grandson112;
1284        GBDATA *elimGrandson    = grandson222;
1285        GBDATA *elimGrandson2   = grandson111;
1286        GBDATA *newGrandson     = NULp;
1287
1288        ct_registry    trace_registry;
1289        callback_trace HIERARCHY_TRACECONSTRUCT(anyElem,changed); // no CB added yet
1290        INIT_CHANGED_HIERARCHY_CALLBACK(anyGrandson);
1291        INIT_DELETED_HIERARCHY_CALLBACK(anyGrandson);
1292        INIT_DELETED_HIERARCHY_CALLBACK(anySonContainer);
1293        INIT_NWCHILD_HIERARCHY_CALLBACK(anySonContainer);
1294
1295        GB_begin_transaction(gb_main);
1296        INIT_CHANGED_HIERARCHY_CALLBACK2(gb_main, "son",               sub_any_son);
1297        INIT_DELETED_HIERARCHY_CALLBACK2(gb_main, "cont_son/grandson", sub_son_grandson);
1298        INIT_DELETED_HIERARCHY_CALLBACK2(gb_main, "cont_son",          sub_any_sonCont);
1299        INIT_NWCHILD_HIERARCHY_CALLBACK2(gb_main, "cont_son",          sub_contson);
1300        GB_commit_transaction(gb_main);
1301
1302        TEST_EXPECT_TRIGGERS_CHECKED();
1303
1304        // trigger change-callback using same DB entry
1305        TRIGGER_CHANGE(anyGrandson);
1306        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anyGrandson);
1307        TEST_EXPECT_TRIGGERS_CHECKED();
1308
1309        // trigger change-callback using another DB entry (same hierarchy)
1310        TRIGGER_CHANGE(anotherGrandson);
1311        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anotherGrandson);
1312        TEST_EXPECT_TRIGGERS_CHECKED();
1313
1314        // check only sub-hierarchy-callback is triggered by an element at different hierarchy
1315        TRIGGER_CHANGE(anySon);
1316        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, anySon);
1317        TEST_EXPECT_TRIGGERS_CHECKED();
1318
1319        // trigger change-callback using both DB entries (in two TAs)
1320        TRIGGER_CHANGE(anyGrandson);
1321        TRIGGER_CHANGE(anotherGrandson);
1322        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anyGrandson);
1323        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anotherGrandson);
1324        TEST_EXPECT_TRIGGERS_CHECKED();
1325
1326        // trigger change-callback using both DB entries (in one TA)
1327        TRIGGER_2_CHANGES(anyGrandson, anotherGrandson);
1328        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anyGrandson);
1329        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anotherGrandson);
1330        TEST_EXPECT_TRIGGERS_CHECKED();
1331
1332        // trigger son-created-callback
1333        {
1334            GB_initial_transaction ta(gb_main);
1335            if (ta.ok()) {
1336                GBDATA *someson = GB_create(anySonContainer, "someson", GB_STRING); TEST_REJECT_NULL(someson);
1337            }
1338            TEST_EXPECT_NO_ERROR(ta.close(NULp));
1339        }
1340        TEST_EXPECT_NCHILD_HIER_TRIGGERED(anySonContainer, anySonContainer);
1341        TEST_EXPECT_NCHILD_HIER_TRIGGERED(sub_contson,     anySonContainer); // sub-hierarchy-callback
1342        TEST_EXPECT_TRIGGERS_CHECKED();
1343
1344        // trigger 2 son-created-callbacks (for 2 containers) and one change-callback (for a newly created son)
1345        {
1346            GB_initial_transaction ta(gb_main);
1347            if (ta.ok()) {
1348                newGrandson     = GB_create(anotherSonContainer, "grandson", GB_STRING); TEST_REJECT_NULL(newGrandson);
1349                GBDATA *someson = GB_create(anySonContainer,     "someson",  GB_STRING); TEST_REJECT_NULL(someson);
1350            }
1351            TEST_EXPECT_NO_ERROR(ta.close(NULp));
1352        }
1353        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson,     newGrandson);
1354        TEST_EXPECT_NCHILD_HIER_TRIGGERED(anySonContainer, anotherSonContainer);
1355        TEST_EXPECT_NCHILD_HIER_TRIGGERED(anySonContainer, anySonContainer);
1356        TEST_EXPECT_NCHILD_HIER_TRIGGERED(sub_contson,     anotherSonContainer); // sub-hierarchy-callback
1357        TEST_EXPECT_NCHILD_HIER_TRIGGERED(sub_contson,     anySonContainer);     // sub-hierarchy-callback
1358        TEST_EXPECT_TRIGGERS_CHECKED();
1359
1360        // trigger delete-callback
1361        {
1362            GB_initial_transaction ta(gb_main);
1363            TEST_EXPECT_NO_ERROR(GB_delete(elimGrandson));
1364            TEST_EXPECT_NO_ERROR(ta.close(NULp));
1365        }
1366        TEST_EXPECT_DELETE_HIER_TRIGGERED(anyGrandson,      elimGrandson);
1367        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_son_grandson, elimGrandson); // sub-hierarchy-callback is triggered as well
1368        TEST_EXPECT_TRIGGERS_CHECKED();
1369
1370        // bind normal (non-hierarchical) callbacks to entries which trigger hierarchical callbacks and ..
1371        calledWith::timer = 0;
1372        GB_begin_transaction(gb_main);
1373
1374        INIT_CHANGED_CALLBACK(anotherGrandson);
1375        INIT_DELETED_CALLBACK(elimGrandson2);
1376
1377        GB_commit_transaction(gb_main);
1378
1379        TEST_EXPECT_TRIGGERS_CHECKED();
1380
1381        {
1382            GB_initial_transaction ta(gb_main);
1383            if (ta.ok()) {
1384                GB_touch(anotherGrandson);
1385                GB_touch(elimGrandson2);
1386                TEST_EXPECT_NO_ERROR(GB_delete(elimGrandson2));
1387            }
1388        }
1389
1390        // .. test call-order (delete before change, hierarchical before normal):
1391        TEST_EXPECT_DELETE_TRIGGERED_AT(traceHier_anyGrandson_deleted,      elimGrandson2,   1);
1392        TEST_EXPECT_DELETE_TRIGGERED_AT(traceHier_sub_son_grandson_deleted, elimGrandson2,   2); // sub-hierarchy-callback
1393        TEST_EXPECT_DELETE_TRIGGERED_AT(trace_elimGrandson2_deleted,        elimGrandson2,   3);
1394        TEST_EXPECT_CHANGE_TRIGGERED_AT(traceHier_anyGrandson_changed,      anotherGrandson, 4);
1395        TEST_EXPECT_CHANGE_TRIGGERED_AT(trace_anotherGrandson_changed,      anotherGrandson, 5);
1396
1397        TEST_EXPECT_TRIGGERS_CHECKED();
1398
1399        // test removed hierarchy callbacks stop to trigger
1400        REMOVE_CHANGED_HIERARCHY_CALLBACK(anyGrandson);
1401        REMOVE_DELETED_HIERARCHY_CALLBACK(anyGrandson);
1402        GB_begin_transaction(gb_main);
1403        REMOVE_DELETED_HIERARCHY_CALLBACK2(gb_main, "cont_son/grandson", sub_son_grandson);
1404        GB_commit_transaction(gb_main);
1405        TRIGGER_CHANGE(anyGrandson);
1406        {
1407            GB_initial_transaction ta(gb_main);
1408            if (ta.ok()) TEST_EXPECT_NO_ERROR(GB_delete(anyGrandson));
1409        }
1410        TEST_EXPECT_TRIGGERS_CHECKED();
1411
1412        GBDATA *anyElem;
1413
1414        // bind SAME callback to different hierarchy locations
1415        anyElem = top1;  ADD_CHANGED_HIERARCHY_CALLBACK(anyElem); // binds      hierarchy cb to "/top"
1416        anyElem = son11; ADD_CHANGED_HIERARCHY_CALLBACK(anyElem); // binds SAME hierarchy cb to "/cont_top/son"
1417
1418        // - check both trigger independently and together
1419        TRIGGER_CHANGE(top1);
1420        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, top1);
1421        TEST_EXPECT_TRIGGERS_CHECKED();
1422
1423        TRIGGER_CHANGE(son11);
1424        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     son11);
1425        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11);
1426        TEST_EXPECT_TRIGGERS_CHECKED();
1427
1428        TRIGGER_2_CHANGES(top1, son11);
1429        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     top1);
1430        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     son11);
1431        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11);
1432        TEST_EXPECT_TRIGGERS_CHECKED();
1433
1434        // - check removing one does not disable the other
1435        anyElem = son11;  REMOVE_CHANGED_HIERARCHY_CALLBACK(anyElem); // remove hierarchy cb from "/cont_top/son"
1436
1437        TRIGGER_2_CHANGES(top1, son11);
1438        // son11 no longer triggers -> ok
1439        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11); // only sub-hierarchy-callback triggers
1440        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     top1);
1441        TEST_EXPECT_TRIGGERS_CHECKED();
1442
1443        // test add/remove hierarchy cb by path
1444        // (Note: sub-hierarchy-callbacks above also use paths)
1445        {
1446            const char       *locpath = "/cont_top/son";
1447            DatabaseCallback  dbcb    = makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(anyElem,changed));
1448
1449            {
1450                GB_transaction ta(gb_main);
1451                TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gb_main, locpath, GB_CB_CHANGED, dbcb));
1452            }
1453
1454            // now both should trigger again
1455            TRIGGER_2_CHANGES(top1, son11);
1456            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, top1);
1457            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, son11);
1458            TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11); // plus sub-hierarchy-callback
1459            TEST_EXPECT_TRIGGERS_CHECKED();
1460
1461            {
1462                GB_transaction ta(gb_main);
1463                TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(gb_main, locpath, GB_CB_CHANGED, dbcb));
1464            }
1465
1466            TRIGGER_2_CHANGES(top1, son11);
1467            // son11 no longer triggers -> ok
1468            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     top1);
1469            TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11);
1470            TEST_EXPECT_TRIGGERS_CHECKED();
1471
1472            // check some failing binds
1473            const char *invalidPath = "//such";
1474            {
1475                GB_transaction ta(gb_main);
1476                TEST_EXPECT_ERROR_CONTAINS(GB_add_hierarchy_callback(gb_main, invalidPath,   GB_CB_CHANGED, dbcb), "invalid hierarchy location");
1477                TEST_EXPECT_ERROR_CONTAINS(GB_add_hierarchy_callback(gb_main,                GB_CB_CHANGED, dbcb), "invalid hierarchy location");
1478            }
1479
1480            // bind a hierarchy callback to a "not yet existing" path (i.e. path containing yet unused keys),
1481            // then create an db-entry at that path and test that callback is trigger
1482            const char *unknownPath = "/unknownPath/unknownEntry";
1483            {
1484                GB_transaction ta(gb_main);
1485                TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gb_main, unknownPath, GB_CB_CHANGED, dbcb));
1486            }
1487            TEST_EXPECT_TRIGGERS_CHECKED();
1488
1489            GBDATA *gb_unknown;
1490            {
1491                GB_transaction ta(gb_main);
1492                TEST_EXPECT_RESULT__NOERROREXPORTED(gb_unknown = GB_search(gb_main, unknownPath, GB_STRING));
1493            }
1494            TEST_EXPECT_TRIGGERS_CHECKED(); // creating an entry does not trigger callback (could call a new callback-type)
1495
1496            TRIGGER_CHANGE(gb_unknown);
1497            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, gb_unknown);
1498            TEST_EXPECT_TRIGGERS_CHECKED();
1499        }
1500
1501        // check container delete callbacks
1502        GBDATA *emptySonContainer = cont_son12;
1503
1504        TRIGGER_DELETE(emptySonContainer);
1505        TEST_EXPECT_DELETE_HIER_TRIGGERED(anySonContainer, emptySonContainer);
1506        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, emptySonContainer);
1507        TEST_EXPECT_TRIGGERS_CHECKED();
1508
1509        TRIGGER_DELETE(cont_top1); // father of cont_son11
1510        TEST_EXPECT_DELETE_HIER_TRIGGERED(anySonContainer, cont_son11);
1511        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, cont_son11);
1512        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, cson_deep);
1513        TEST_EXPECT_TRIGGERS_CHECKED();
1514
1515        TRIGGER_DELETE(cson); // son container at top-level
1516        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, cson);
1517        TEST_EXPECT_TRIGGERS_CHECKED();
1518
1519        // cleanup
1520        GB_close(gb_main);
1521    }
1522
1523    GB_unlink(DBNAME);
1524}
1525
1526#endif // UNIT_TESTS
1527
1528// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.