source: tags/ms_r17q2/ARBDB/ad_cb.cxx

Last change on this file was 15759, checked in by westram, 7 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.8 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 = NULL; // points to callback during callback; NULL otherwise
21static GB_MAIN_TYPE          *inside_callback_main  = NULL; // points to DB root during callback; NULL 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 = NULL;
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 = NULL;
93}
94
95inline void GB_MAIN_TYPE::callback_group::forget_hcbs() {
96    delete hierarchy_cbs;
97    hierarchy_cbs = NULL;
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 NULL.
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 = NULL;
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 NULL;
173    }
174    if (!currently_called_back->old) {
175        GB_export_error("No old value available in GB_read_old_value");
176        return NULL;
177    }
178    data = GB_GETDATA_TS(currently_called_back->old);
179    if (!data) return NULL;
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 0
228    char *result = 0;
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*)NULL), 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 0;
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 NULL;
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 NULL;
406}
407
408#undef CHECK_HIER_CB_CONDITION
409
410GB_ERROR GB_add_hierarchy_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
411    /*! bind callback to ALL entries which are at the same DB-hierarchy as 'gbd'.
412     *
413     * Hierarchy callbacks are triggered before normal callbacks (added by GB_add_callback or GB_ensure_callback).
414     * Nevertheless delete callbacks take precedence over change callbacks
415     * (i.e. a normal delete callback is triggered before a hierarchical change callback).
416     *
417     * Hierarchy callbacks cannot be installed and will NOT be triggered in NO_TRANSACTION_MODE
418     * (i.e. it will not work in ARBs property DBs)
419     *
420     * @return error if in NO_TRANSACTION_MODE or if hierarchy location is invalid
421     */
422    return GB_MAIN(gbd)->add_hierarchy_cb(gb_hierarchy_location(gbd), TypedDatabaseCallback(dbcb, type));
423}
424
425GB_ERROR GB_remove_hierarchy_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
426    //! remove callback added with GB_add_hierarchy_callback()
427    return GB_MAIN(gbd)->remove_hierarchy_cb(gb_hierarchy_location(gbd), TypedDatabaseCallback(dbcb, type));
428}
429
430GB_ERROR GB_add_hierarchy_callback(GBDATA *gb_main, const char *db_path, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
431    /*! same as overloaded GB_add_hierarchy_callback(), but using db_path instead of GBDATA to define hierarchy location.
432     *  If the path starts with '/', the full path has to match to trigger the callback.
433     *  Otherwise only the given path has to match (from bottom up),
434     *  e.g. "gb_group" will trigger on any "gb_group" entry (regardless of its parent-path).
435     */
436    return GB_MAIN(gb_main)->add_hierarchy_cb(gb_hierarchy_location(gb_main, db_path), TypedDatabaseCallback(dbcb, type));
437}
438GB_ERROR GB_remove_hierarchy_callback(GBDATA *gb_main, const char *db_path, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
439    //! same as overloaded GB_remove_hierarchy_callback(), but using db_path instead of GBDATA to define hierarchy location
440    return GB_MAIN(gb_main)->remove_hierarchy_cb(gb_hierarchy_location(gb_main, db_path), TypedDatabaseCallback(dbcb, type));
441}
442
443GB_ERROR GB_ensure_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback& dbcb) {
444    TypedDatabaseCallback newcb(dbcb, type);
445    gb_callback_list *cbl = gbd->get_callbacks();
446    if (cbl) {
447        for (gb_callback_list::itertype cb = cbl->callbacks.begin(); cb != cbl->callbacks.end(); ++cb) {
448            if (cb->spec.is_equal_to(newcb) && !cb->spec.is_marked_for_removal()) {
449                return NULL; // already in cb list
450            }
451        }
452    }
453    return gb_add_callback(gbd, newcb);
454}
455
456// --------------------------------------------------------------------------------
457
458#ifdef UNIT_TESTS
459
460#include <string> // needed b4 test_unit.h!
461#include <arb_file.h>
462
463#ifndef TEST_UNIT_H
464#include <test_unit.h>
465#endif
466
467
468// -----------------------
469//      test callbacks
470
471static void test_count_cb(GBDATA *, int *counter) {
472    fprintf(stderr, "test_count_cb: var.add=%p old.val=%i ", counter, *counter);
473    (*counter)++;
474    fprintf(stderr, "new.val=%i\n", *counter);
475    fflush(stderr);
476}
477
478static void remove_self_cb(GBDATA *gbe, GB_CB_TYPE cbtype) {
479    GB_remove_callback(gbe, cbtype, makeDatabaseCallback(remove_self_cb));
480}
481
482static void re_add_self_cb(GBDATA *gbe, int *calledCounter, GB_CB_TYPE cbtype) {
483    ++(*calledCounter);
484
485    DatabaseCallback dbcb = makeDatabaseCallback(re_add_self_cb, calledCounter);
486    GB_remove_callback(gbe, cbtype, dbcb);
487
488    ASSERT_NO_ERROR(GB_add_callback(gbe, cbtype, dbcb));
489}
490
491void TEST_db_callbacks_ta_nota() {
492    GB_shell shell;
493
494    enum TAmode {
495        NO_TA   = 1, // no transaction mode
496        WITH_TA = 2, // transaction mode
497
498        BOTH_TA_MODES = (NO_TA|WITH_TA)
499    };
500
501    for (TAmode ta_mode = NO_TA; ta_mode <= WITH_TA; ta_mode = TAmode(ta_mode+1)) {
502        GBDATA   *gb_main = GB_open("no.arb", "c");
503        GB_ERROR  error;
504
505        TEST_ANNOTATE(ta_mode == NO_TA ? "NO_TA" : "WITH_TA");
506        if (ta_mode == NO_TA) {
507            error = GB_no_transaction(gb_main); TEST_EXPECT_NO_ERROR(error);
508        }
509
510        // create some DB entries
511        GBDATA *gbc;
512        GBDATA *gbe1;
513        GBDATA *gbe2;
514        GBDATA *gbe3;
515        {
516            GB_transaction ta(gb_main);
517
518            gbc  = GB_create_container(gb_main, "cont");
519            gbe1 = GB_create(gbc, "entry", GB_STRING);
520            gbe2 = GB_create(gb_main, "entry", GB_INT);
521        }
522
523        // counters to detect called callbacks
524        int e1_changed    = 0;
525        int e2_changed    = 0;
526        int c_changed     = 0;
527        int c_son_created = 0;
528
529        int e1_deleted = 0;
530        int e2_deleted = 0;
531        int e3_deleted = 0;
532        int c_deleted  = 0;
533
534#define CHCB_COUNTERS_EXPECTATION(e1c,e2c,cc,csc)       \
535        that(e1_changed).is_equal_to(e1c),              \
536            that(e2_changed).is_equal_to(e2c),          \
537            that(c_changed).is_equal_to(cc),            \
538            that(c_son_created).is_equal_to(csc)
539
540#define DLCB_COUNTERS_EXPECTATION(e1d,e2d,e3d,cd)       \
541        that(e1_deleted).is_equal_to(e1d),              \
542            that(e2_deleted).is_equal_to(e2d),          \
543            that(e3_deleted).is_equal_to(e3d),          \
544            that(c_deleted).is_equal_to(cd)
545
546#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)
547#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)
548
549#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)
550#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)
551
552#define TEST_EXPECT_COUNTER(tam,cnt,expected)             do{ if (ta_mode & (tam)) TEST_EXPECT_EQUAL(cnt, expected); }while(0)
553#define TEST_EXPECT_COUNTER__BROKEN(tam,cnt,expected,got) do{ if (ta_mode & (tam)) TEST_EXPECT_EQUAL__BROKEN(cnt, expected, got); }while(0)
554
555#define RESET_CHCB_COUNTERS()   do{ e1_changed = e2_changed = c_changed = c_son_created = 0; }while(0)
556#define RESET_DLCB_COUNTERS()   do{ e1_deleted = e2_deleted = e3_deleted = c_deleted = 0; }while(0)
557#define RESET_ALL_CB_COUNTERS() do{ RESET_CHCB_COUNTERS(); RESET_DLCB_COUNTERS(); }while(0)
558
559        // install some DB callbacks
560        {
561            GB_transaction ta(gb_main);
562            GB_add_callback(gbe1, GB_CB_CHANGED,     makeDatabaseCallback(test_count_cb, &e1_changed));
563            GB_add_callback(gbe2, GB_CB_CHANGED,     makeDatabaseCallback(test_count_cb, &e2_changed));
564            GB_add_callback(gbc,  GB_CB_CHANGED,     makeDatabaseCallback(test_count_cb, &c_changed));
565            GB_add_callback(gbc,  GB_CB_SON_CREATED, makeDatabaseCallback(test_count_cb, &c_son_created));
566        }
567
568        // check callbacks were not called yet
569        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, BOTH_TA_MODES);
570
571        // trigger callbacks
572        {
573            GB_transaction ta(gb_main);
574
575            error = GB_write_string(gbe1, "hi"); TEST_EXPECT_NO_ERROR(error);
576            error = GB_write_int(gbe2, 666);     TEST_EXPECT_NO_ERROR(error);
577
578            TEST_EXPECT_CHCB_COUNTERS(1, 1, 1, 0, NO_TA);   // callbacks triggered instantly in NO_TA mode
579            TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, WITH_TA); // callbacks delayed until transaction is committed
580
581        } // [Note: callbacks happen here in ta_mode]
582
583        // test that GB_CB_SON_CREATED is not triggered here:
584        TEST_EXPECT_CHCB_COUNTERS(1, 1, 1, 0, NO_TA);
585        TEST_EXPECT_CHCB_COUNTERS(1, 1, 1, 0, WITH_TA);
586
587        // really create a son
588        RESET_CHCB_COUNTERS();
589        {
590            GB_transaction ta(gb_main);
591            gbe3 = GB_create(gbc, "e3", GB_STRING);
592        }
593        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, NO_TA); // broken
594        TEST_EXPECT_CHCB___WANTED(0, 0, 1, 1, NO_TA);
595        TEST_EXPECT_CHCB_COUNTERS(0, 0, 1, 1, WITH_TA);
596
597        // change that son
598        RESET_CHCB_COUNTERS();
599        {
600            GB_transaction ta(gb_main);
601            error = GB_write_string(gbe3, "bla"); TEST_EXPECT_NO_ERROR(error);
602        }
603        TEST_EXPECT_CHCB_COUNTERS(0, 0, 1, 0, BOTH_TA_MODES);
604
605
606        // test delete callbacks
607        RESET_CHCB_COUNTERS();
608        {
609            GB_transaction ta(gb_main);
610
611            GB_add_callback(gbe1, GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &e1_deleted));
612            GB_add_callback(gbe2, GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &e2_deleted));
613            GB_add_callback(gbe3, GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &e3_deleted));
614            GB_add_callback(gbc,  GB_CB_DELETE, makeDatabaseCallback(test_count_cb, &c_deleted));
615        }
616        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, BOTH_TA_MODES); // adding callbacks does not trigger existing change-callbacks
617        {
618            GB_transaction ta(gb_main);
619
620            error = GB_delete(gbe3); TEST_EXPECT_NO_ERROR(error);
621            error = GB_delete(gbe2); TEST_EXPECT_NO_ERROR(error);
622
623            TEST_EXPECT_DLCB_COUNTERS(0, 1, 1, 0, NO_TA);
624            TEST_EXPECT_DLCB_COUNTERS(0, 0, 0, 0, WITH_TA);
625        }
626
627        TEST_EXPECT_CHCB_COUNTERS(0, 0, 1, 0, WITH_TA); // container changed by deleting a son (gbe3); no longer triggers via GB_SON_CHANGED
628        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, NO_TA);   // change is not triggered in NO_TA mode (error?)
629        TEST_EXPECT_CHCB___WANTED(0, 0, 1, 1, NO_TA);
630
631        TEST_EXPECT_DLCB_COUNTERS(0, 1, 1, 0, BOTH_TA_MODES);
632
633        RESET_ALL_CB_COUNTERS();
634        {
635            GB_transaction ta(gb_main);
636            error = GB_delete(gbc);  TEST_EXPECT_NO_ERROR(error); // delete the container containing gbe1 and gbe3 (gbe3 alreay deleted)
637        }
638        TEST_EXPECT_CHCB_COUNTERS(0, 0, 0, 0, BOTH_TA_MODES); // deleting the container does not trigger any change callbacks
639        TEST_EXPECT_DLCB_COUNTERS(1, 0, 0, 1, BOTH_TA_MODES); // deleting the container does also trigger the delete callback for gbe1
640
641        // --------------------------------------------------------------------------------
642        // document that a callback now can be removed while it is running
643        // (in NO_TA mode; always worked in WITH_TA mode)
644        {
645            GBDATA *gbe;
646            {
647                GB_transaction ta(gb_main);
648                gbe = GB_create(gb_main, "new_e1", GB_INT); // recreate
649                GB_add_callback(gbe, GB_CB_CHANGED, makeDatabaseCallback(remove_self_cb));
650            }
651            { GB_transaction ta(gb_main); GB_touch(gbe); }
652        }
653
654        // test that a callback may remove and re-add itself
655        {
656            GBDATA *gbn1;
657            GBDATA *gbn2;
658
659            int counter1 = 0;
660            int counter2 = 0;
661
662            {
663                GB_transaction ta(gb_main);
664                gbn1 = GB_create(gb_main, "new_e1", GB_INT);
665                gbn2 = GB_create(gb_main, "new_e2", GB_INT);
666                GB_add_callback(gbn1, GB_CB_CHANGED, makeDatabaseCallback(re_add_self_cb, &counter1));
667            }
668
669            TEST_EXPECT_COUNTER(NO_TA,         counter1, 0); // no callback triggered (trigger happens BEFORE call to GB_add_callback in NO_TA mode!)
670            TEST_EXPECT_COUNTER(WITH_TA,       counter1, 1); // callback gets triggered by GB_create
671            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 0);
672
673            counter1 = 0; counter2 = 0;
674
675            // test no callback is triggered by just adding a callback
676            {
677                GB_transaction ta(gb_main);
678                GB_add_callback(gbn2, GB_CB_CHANGED, makeDatabaseCallback(re_add_self_cb, &counter2));
679            }
680            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 0);
681            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 0);
682
683            { GB_transaction ta(gb_main); GB_touch(gbn1); }
684            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 1);
685            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 0);
686
687            { GB_transaction ta(gb_main); GB_touch(gbn2); }
688            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 1);
689            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 1);
690
691            { GB_transaction ta(gb_main);  }
692            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter1, 1);
693            TEST_EXPECT_COUNTER(BOTH_TA_MODES, counter2, 1);
694        }
695
696        GB_close(gb_main);
697    }
698}
699
700struct calledWith {
701    RefPtr<GBDATA> gbd;
702    GB_CB_TYPE     type;
703    int            time_called;
704
705    static int timer;
706
707    calledWith(GBDATA *gbd_, GB_CB_TYPE type_) : gbd(gbd_), type(type_), time_called(++timer) {}
708};
709
710using std::string;
711using std::list;
712
713class callback_trace;
714
715class ct_registry {
716    static ct_registry *SINGLETON;
717
718    typedef list<callback_trace*> ctl;
719
720    ctl traces;
721public:
722    ct_registry() {
723        gb_assert(!SINGLETON);
724        SINGLETON = this;
725    }
726    ~ct_registry() {
727        gb_assert(this == SINGLETON);
728        SINGLETON = NULL;
729        gb_assert(traces.empty());
730    }
731
732    static ct_registry *instance() { gb_assert(SINGLETON); return SINGLETON; }
733
734    void add(callback_trace *ct) { traces.push_back(ct); }
735    void remove(callback_trace *ct) { traces.remove(ct); }
736
737    arb_test::match_expectation expect_all_calls_checked();
738};
739ct_registry *ct_registry::SINGLETON = NULL;
740
741int calledWith::timer = 0;
742
743class callback_trace {
744    typedef list<calledWith> calledList;
745    typedef calledList::iterator  calledIter;
746
747    calledList called;
748    string     name;
749
750    calledIter find(GBDATA *gbd) {
751        calledIter c = called.begin();
752        while (c != called.end()) {
753            if (c->gbd == gbd) break;
754            ++c;
755        }
756        return c;
757    }
758    calledIter find(GB_CB_TYPE exp_type) {
759        calledIter c = called.begin();
760        while (c != called.end()) {
761            if (c->type&exp_type) break;
762            ++c;
763        }
764        return c;
765    }
766    calledIter find(GBDATA *gbd, GB_CB_TYPE exp_type) {
767        calledIter c = called.begin();
768        while (c != called.end()) {
769            if (c->gbd == gbd && (c->type&exp_type)) break;
770            ++c;
771        }
772        return c;
773    }
774
775    bool removed(calledIter c) {
776        if (c == called.end()) return false;
777        called.erase(c);
778        return true;
779    }
780
781public:
782    callback_trace(const char *name_)
783        : name(name_)
784    {
785        called.clear();
786        ct_registry::instance()->add(this);
787    }
788    ~callback_trace() {
789        if (was_called()) TEST_EXPECT_EQUAL(name, "has unchecked calls in dtor"); // you forgot to check some calls using TEST_EXPECT_..._TRIGGERED
790        ct_registry::instance()->remove(this);
791    }
792
793    const string& get_name() const { return name; }
794
795    void set_called_by(GBDATA *gbd, GB_CB_TYPE type) { called.push_back(calledWith(gbd, type)); }
796
797    bool was_called_by(GBDATA *gbd) { return removed(find(gbd)); }
798    bool was_called_by(GB_CB_TYPE exp_type) { return removed(find(exp_type)); }
799    bool was_called_by(GBDATA *gbd, GB_CB_TYPE exp_type) { return removed(find(gbd, exp_type)); }
800
801    int call_time(GBDATA *gbd, GB_CB_TYPE exp_type) {
802        calledIter found = find(gbd, exp_type);
803        if (found == called.end()) return -1;
804
805        int t = found->time_called;
806        removed(found);
807        return t;
808    }
809
810    bool was_not_called() const { return called.empty(); }
811    bool was_called() const { return !was_not_called(); }
812};
813
814arb_test::match_expectation ct_registry::expect_all_calls_checked() {
815    using namespace   arb_test;
816    expectation_group expected;
817
818    // add failing expectations for all traces with unchecked calls
819    for (ctl::iterator t = traces.begin(); t != traces.end(); ++t) {
820        callback_trace *ct = *t;
821        if (ct->was_called()) {
822            expectation_group bad_trace;
823            bad_trace.add(that(ct->was_called()).is_equal_to(false));
824
825            const char *failing_trace = ct->get_name().c_str();
826            bad_trace.add(that(failing_trace).does_differ_from(failing_trace)); // display failing_trace in failing expectation
827
828            expected.add(all().ofgroup(bad_trace));
829        }
830    }
831
832    return all().ofgroup(expected);
833}
834
835
836static void some_cb(GBDATA *gbd, callback_trace *trace, GB_CB_TYPE cbtype) {
837    trace->set_called_by(gbd, cbtype);
838}
839
840#define TRACESTRUCT(ELEM,FLAVOR)           trace_##ELEM##_##FLAVOR
841#define HIERARCHY_TRACESTRUCT(ELEM,FLAVOR) traceHier_##ELEM##_##FLAVOR
842
843#define CONSTRUCT(name)                       name(#name) // pass instance-name to callback_trace-ctor as char*
844#define TRACECONSTRUCT(ELEM,FLAVOR)           CONSTRUCT(TRACESTRUCT(ELEM,FLAVOR))
845#define HIERARCHY_TRACECONSTRUCT(ELEM,FLAVOR) CONSTRUCT(HIERARCHY_TRACESTRUCT(ELEM,FLAVOR))
846
847#define ADD_CHANGED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,changed))))
848#define ADD_DELETED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,deleted))))
849#define ADD_NWCHILD_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_add_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,newchild))))
850
851#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))))
852#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))))
853#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))))
854
855#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))))
856#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))))
857#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))))
858
859#define ENSURE_CHANGED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_ensure_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,changed))))
860#define ENSURE_DELETED_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_ensure_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,deleted))))
861#define ENSURE_NWCHILD_CALLBACK(elem) TEST_EXPECT_NO_ERROR(GB_ensure_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,newchild))))
862
863#define REMOVE_CHANGED_CALLBACK(elem) GB_remove_callback(elem, GB_CB_CHANGED,     makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,changed)))
864#define REMOVE_DELETED_CALLBACK(elem) GB_remove_callback(elem, GB_CB_DELETE,      makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,deleted)))
865#define REMOVE_NWCHILD_CALLBACK(elem) GB_remove_callback(elem, GB_CB_SON_CREATED, makeDatabaseCallback(some_cb, &TRACESTRUCT(elem,newchild)))
866
867#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))))
868#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))))
869#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))))
870
871#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))))
872#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))))
873#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))))
874
875#define INIT_CHANGED_CALLBACK(elem) callback_trace TRACECONSTRUCT(elem,changed);  ADD_CHANGED_CALLBACK(elem)
876#define INIT_DELETED_CALLBACK(elem) callback_trace TRACECONSTRUCT(elem,deleted);  ADD_DELETED_CALLBACK(elem)
877#define INIT_NWCHILD_CALLBACK(elem) callback_trace TRACECONSTRUCT(elem,newchild); ADD_NWCHILD_CALLBACK(elem)
878
879#define INIT_CHANGED_HIERARCHY_CALLBACK(elem) callback_trace HIERARCHY_TRACECONSTRUCT(elem,changed);  ADD_CHANGED_HIERARCHY_CALLBACK(elem)
880#define INIT_DELETED_HIERARCHY_CALLBACK(elem) callback_trace HIERARCHY_TRACECONSTRUCT(elem,deleted);  ADD_DELETED_HIERARCHY_CALLBACK(elem)
881#define INIT_NWCHILD_HIERARCHY_CALLBACK(elem) callback_trace HIERARCHY_TRACECONSTRUCT(elem,newchild); ADD_NWCHILD_HIERARCHY_CALLBACK(elem)
882
883#define INIT_CHANGED_HIERARCHY_CALLBACK2(gbm,path,id) callback_trace HIERARCHY_TRACECONSTRUCT(id,changed);  ADD_CHANGED_HIERARCHY_CALLBACK2(gbm,path,id)
884#define INIT_DELETED_HIERARCHY_CALLBACK2(gbm,path,id) callback_trace HIERARCHY_TRACECONSTRUCT(id,deleted);  ADD_DELETED_HIERARCHY_CALLBACK2(gbm,path,id)
885#define INIT_NWCHILD_HIERARCHY_CALLBACK2(gbm,path,id) callback_trace HIERARCHY_TRACECONSTRUCT(id,newchild); ADD_NWCHILD_HIERARCHY_CALLBACK2(gbm,path,id)
886
887#define ENSURE_ENTRY_CALLBACKS(entry)    ENSURE_CHANGED_CALLBACK(entry); ENSURE_DELETED_CALLBACK(entry)
888#define ENSURE_CONTAINER_CALLBACKS(cont) ENSURE_CHANGED_CALLBACK(cont);  ENSURE_NWCHILD_CALLBACK(cont); ENSURE_DELETED_CALLBACK(cont)
889
890#define REMOVE_ENTRY_CALLBACKS(entry)    REMOVE_CHANGED_CALLBACK(entry); REMOVE_DELETED_CALLBACK(entry)
891#define REMOVE_CONTAINER_CALLBACKS(cont) REMOVE_CHANGED_CALLBACK(cont);  REMOVE_NWCHILD_CALLBACK(cont); REMOVE_DELETED_CALLBACK(cont)
892
893#define INIT_ENTRY_CALLBACKS(entry)    INIT_CHANGED_CALLBACK(entry); INIT_DELETED_CALLBACK(entry)
894#define INIT_CONTAINER_CALLBACKS(cont) INIT_CHANGED_CALLBACK(cont);  INIT_NWCHILD_CALLBACK(cont); INIT_DELETED_CALLBACK(cont)
895
896#define TRIGGER_CHANGE(gbd) do {                \
897        GB_initial_transaction ta(gb_main);     \
898        if (ta.ok()) GB_touch(gbd);             \
899        TEST_EXPECT_NO_ERROR(ta.close(NULL));   \
900    } while(0)
901
902#define TRIGGER_2_CHANGES(gbd1, gbd2) do {      \
903        GB_initial_transaction ta(gb_main);     \
904        if (ta.ok()) {                          \
905            GB_touch(gbd1);                     \
906            GB_touch(gbd2);                     \
907        }                                       \
908        TEST_EXPECT_NO_ERROR(ta.close(NULL));   \
909    } while(0)
910
911#define TRIGGER_DELETE(gbd) do {                \
912        GB_initial_transaction ta(gb_main);     \
913        GB_ERROR error = NULL;                  \
914        if (ta.ok()) error = GB_delete(gbd);    \
915        TEST_EXPECT_NO_ERROR(ta.close(error));  \
916    } while(0)
917
918#define TEST_EXPECT_CB_TRIGGERED(TRACE,GBD,TYPE)         TEST_EXPECT(TRACE.was_called_by(GBD, TYPE))
919#define TEST_EXPECT_CB_TRIGGERED_AT(TRACE,GBD,TYPE,TIME) TEST_EXPECT_EQUAL(TRACE.call_time(GBD, TYPE), TIME)
920
921#define TEST_EXPECT_CHANGE_TRIGGERED(GBD) TEST_EXPECT_CB_TRIGGERED(TRACESTRUCT(GBD, changed),  GBD, GB_CB_CHANGED)
922#define TEST_EXPECT_DELETE_TRIGGERED(GBD) TEST_EXPECT_CB_TRIGGERED(TRACESTRUCT(GBD, deleted),  GBD, GB_CB_DELETE)
923#define TEST_EXPECT_NCHILD_TRIGGERED(GBD) TEST_EXPECT_CB_TRIGGERED(TRACESTRUCT(GBD, newchild), GBD, GB_CB_SON_CREATED)
924
925#define TEST_EXPECT_CHANGE_HIER_TRIGGERED(NAME,GBD) TEST_EXPECT_CB_TRIGGERED(HIERARCHY_TRACESTRUCT(NAME, changed),  GBD, GB_CB_CHANGED)
926#define TEST_EXPECT_DELETE_HIER_TRIGGERED(NAME,GBD) TEST_EXPECT_CB_TRIGGERED(HIERARCHY_TRACESTRUCT(NAME, deleted),  GBD, GB_CB_DELETE)
927#define TEST_EXPECT_NCHILD_HIER_TRIGGERED(NAME,GBD) TEST_EXPECT_CB_TRIGGERED(HIERARCHY_TRACESTRUCT(NAME, newchild), GBD, GB_CB_SON_CREATED)
928
929
930#define TEST_EXPECT_CHANGE_TRIGGERED_AT(TRACE,GBD,TIME) TEST_EXPECT_CB_TRIGGERED_AT(TRACE, GBD, GB_CB_CHANGED, TIME)
931#define TEST_EXPECT_DELETE_TRIGGERED_AT(TRACE,GBD,TIME) TEST_EXPECT_CB_TRIGGERED_AT(TRACE, GBD, GB_CB_DELETE, TIME)
932
933#define TEST_EXPECT_TRIGGERS_CHECKED() TEST_EXPECTATION(trace_registry.expect_all_calls_checked())
934
935void TEST_db_callbacks() {
936    GB_shell  shell;
937    GBDATA   *gb_main = GB_open("new.arb", "c");
938
939    // create some data
940    GB_begin_transaction(gb_main);
941
942    GBDATA *cont_top = GB_create_container(gb_main,  "cont_top"); TEST_REJECT_NULL(cont_top);
943    GBDATA *cont_son = GB_create_container(cont_top, "cont_son"); TEST_REJECT_NULL(cont_son);
944
945    GBDATA *top       = GB_create(gb_main,  "top",       GB_STRING); TEST_REJECT_NULL(top);
946    GBDATA *son       = GB_create(cont_top, "son",       GB_INT);    TEST_REJECT_NULL(son);
947    GBDATA *grandson  = GB_create(cont_son, "grandson",  GB_STRING); TEST_REJECT_NULL(grandson);
948    GBDATA *ograndson = GB_create(cont_son, "ograndson", GB_STRING); TEST_REJECT_NULL(ograndson);
949
950    GB_commit_transaction(gb_main);
951
952    // install callbacks
953    GB_begin_transaction(gb_main);
954
955    ct_registry trace_registry;
956    INIT_CONTAINER_CALLBACKS(cont_top);
957    INIT_CONTAINER_CALLBACKS(cont_son);
958    INIT_ENTRY_CALLBACKS(top);
959    INIT_ENTRY_CALLBACKS(son);
960    INIT_ENTRY_CALLBACKS(grandson);
961    INIT_ENTRY_CALLBACKS(ograndson);
962
963    GB_commit_transaction(gb_main);
964
965    TEST_EXPECT_TRIGGERS_CHECKED();
966
967    // trigger change callbacks via change
968    GB_begin_transaction(gb_main);
969    GB_write_string(top, "hello world");
970    GB_commit_transaction(gb_main);
971    TEST_EXPECT_CHANGE_TRIGGERED(top);
972    TEST_EXPECT_TRIGGERS_CHECKED();
973
974    GB_begin_transaction(gb_main);
975    GB_write_string(top, "hello world"); // no change
976    GB_commit_transaction(gb_main);
977    TEST_EXPECT_TRIGGERS_CHECKED();
978
979#if 0
980    // code is wrong (cannot set terminal entry to "marked")
981    GB_begin_transaction(gb_main);
982    GB_write_flag(son, 1);                                  // only change "mark"
983    GB_commit_transaction(gb_main);
984    TEST_EXPECT_CHANGE_TRIGGERED(son);
985    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
986    TEST_EXPECT_TRIGGER__UNWANTED(trace_cont_top_newchild); // @@@ modifying son should not trigger newchild callback
987    TEST_EXPECT_TRIGGERS_CHECKED();
988#else
989    // @@@ add test code similar to wrong section above
990#endif
991
992    GB_begin_transaction(gb_main);
993    GB_touch(grandson);
994    GB_commit_transaction(gb_main);
995    TEST_EXPECT_CHANGE_TRIGGERED(grandson);
996    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
997    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
998    TEST_EXPECT_TRIGGERS_CHECKED();
999
1000    // trigger change- and soncreate-callbacks via create
1001
1002    GB_begin_transaction(gb_main);
1003    GBDATA *son2 = GB_create(cont_top, "son2", GB_INT); TEST_REJECT_NULL(son2);
1004    GB_commit_transaction(gb_main);
1005    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1006    TEST_EXPECT_NCHILD_TRIGGERED(cont_top);
1007    TEST_EXPECT_TRIGGERS_CHECKED();
1008
1009    GB_begin_transaction(gb_main);
1010    GBDATA *grandson2 = GB_create(cont_son, "grandson2", GB_STRING); TEST_REJECT_NULL(grandson2);
1011    GB_commit_transaction(gb_main);
1012    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1013    TEST_EXPECT_NCHILD_TRIGGERED(cont_son);
1014    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1015    TEST_EXPECT_TRIGGERS_CHECKED();
1016
1017    // trigger callbacks via delete
1018
1019    TRIGGER_DELETE(son2);
1020    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1021    TEST_EXPECT_TRIGGERS_CHECKED();
1022
1023    TRIGGER_DELETE(grandson2);
1024    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1025    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1026    TEST_EXPECT_TRIGGERS_CHECKED();
1027
1028    TEST_EXPECT_NO_ERROR(GB_request_undo_type(gb_main, GB_UNDO_UNDO));
1029
1030    TRIGGER_DELETE(top);
1031    TEST_EXPECT_DELETE_TRIGGERED(top);
1032    TEST_EXPECT_TRIGGERS_CHECKED();
1033
1034    TRIGGER_DELETE(grandson);
1035    TEST_EXPECT_DELETE_TRIGGERED(grandson);
1036    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1037    TEST_EXPECT_CHANGE_TRIGGERED(cont_son);
1038    TEST_EXPECT_TRIGGERS_CHECKED();
1039
1040    TRIGGER_DELETE(cont_son);
1041    TEST_EXPECT_DELETE_TRIGGERED(ograndson);
1042    TEST_EXPECT_DELETE_TRIGGERED(cont_son);
1043    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1044    TEST_EXPECT_TRIGGERS_CHECKED();
1045
1046    // trigger callbacks by undoing last 3 delete transactions
1047
1048    TEST_EXPECT_NO_ERROR(GB_undo(gb_main, GB_UNDO_UNDO)); // undo delete of cont_son
1049    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1050    TEST_EXPECT_NCHILD_TRIGGERED(cont_top);
1051    TEST_EXPECT_TRIGGERS_CHECKED();
1052
1053    TEST_EXPECT_NO_ERROR(GB_undo(gb_main, GB_UNDO_UNDO)); // undo delete of grandson
1054    // cont_son callbacks are not triggered (they are not restored by undo)
1055    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1056    TEST_EXPECT_TRIGGERS_CHECKED();
1057
1058    TEST_EXPECT_NO_ERROR(GB_undo(gb_main, GB_UNDO_UNDO)); // undo delete of top
1059    TEST_EXPECT_TRIGGERS_CHECKED();
1060
1061    // reinstall callbacks that were removed by deletes
1062
1063    GB_begin_transaction(gb_main);
1064    ENSURE_CONTAINER_CALLBACKS(cont_top);
1065    ENSURE_CONTAINER_CALLBACKS(cont_son);
1066    ENSURE_ENTRY_CALLBACKS(top);
1067    ENSURE_ENTRY_CALLBACKS(son);
1068    ENSURE_ENTRY_CALLBACKS(grandson);
1069    GB_commit_transaction(gb_main);
1070    TEST_EXPECT_TRIGGERS_CHECKED();
1071
1072    // trigger callbacks which will be removed
1073
1074    TRIGGER_CHANGE(son);
1075    TEST_EXPECT_CHANGE_TRIGGERED(son);
1076    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1077    TEST_EXPECT_TRIGGERS_CHECKED();
1078
1079    GB_begin_transaction(gb_main);
1080    GBDATA *son3 = GB_create(cont_top, "son3", GB_INT); TEST_REJECT_NULL(son3);
1081    GB_commit_transaction(gb_main);
1082    TEST_EXPECT_CHANGE_TRIGGERED(cont_top);
1083    TEST_EXPECT_NCHILD_TRIGGERED(cont_top);
1084    TEST_EXPECT_TRIGGERS_CHECKED();
1085
1086    // test remove callback
1087
1088    GB_begin_transaction(gb_main);
1089    REMOVE_ENTRY_CALLBACKS(son);
1090    REMOVE_CONTAINER_CALLBACKS(cont_top);
1091    GB_commit_transaction(gb_main);
1092    TEST_EXPECT_TRIGGERS_CHECKED();
1093
1094    // "trigger" removed callbacks
1095
1096    TRIGGER_CHANGE(son);
1097    TEST_EXPECT_TRIGGERS_CHECKED();
1098
1099    GB_begin_transaction(gb_main);
1100    GBDATA *son4 = GB_create(cont_top, "son4", GB_INT); TEST_REJECT_NULL(son4);
1101    GB_commit_transaction(gb_main);
1102    TEST_EXPECT_TRIGGERS_CHECKED();
1103
1104    // avoid that GB_close calls delete callbacks (@@@ which might be an error in GB_close!)
1105    // by removing remaining callbacks
1106    REMOVE_ENTRY_CALLBACKS(grandson);
1107    REMOVE_ENTRY_CALLBACKS(top);
1108    REMOVE_CONTAINER_CALLBACKS(cont_son);
1109
1110    GB_close(gb_main);
1111}
1112
1113void TEST_hierarchy_callbacks() {
1114    GB_shell    shell;
1115    const char *DBNAME  = "tmp_hier_cb.arb";
1116
1117    for (int pass = 1; pass<=2; ++pass) {
1118        bool creating = pass == 1;
1119        TEST_ANNOTATE(GBS_global_string("%s database", creating ? "created" : "reloaded"));
1120
1121        GBDATA *gb_main = pass == 1 ? GB_open(DBNAME, "cw") : GB_open(DBNAME, "r");
1122        TEST_REJECT_NULL(gb_main);
1123
1124        // create some data
1125        GB_begin_transaction(gb_main);
1126
1127        GBDATA *cont_top1 = creating ? GB_create_container(gb_main, "cont_top") : GB_entry(gb_main, "cont_top"); TEST_REJECT_NULL(cont_top1);
1128        GBDATA *cont_top2 = creating ? GB_create_container(gb_main, "cont_top") : GB_nextEntry(cont_top1);       TEST_REJECT_NULL(cont_top2);
1129
1130        GBDATA *cont_son11 = creating ? GB_create_container(cont_top1, "cont_son") : GB_entry(cont_top1, "cont_son"); TEST_REJECT_NULL(cont_son11);
1131        GBDATA *cont_son12 = creating ? GB_create_container(cont_top1, "cont_son") : GB_nextEntry(cont_son11);        TEST_REJECT_NULL(cont_son12); // leave this container empty!
1132        GBDATA *cont_son21 = creating ? GB_create_container(cont_top2, "cont_son") : GB_entry(cont_top2, "cont_son"); TEST_REJECT_NULL(cont_son21);
1133        GBDATA *cont_son22 = creating ? GB_create_container(cont_top2, "cont_son") : GB_nextEntry(cont_son21);        TEST_REJECT_NULL(cont_son22);
1134
1135        GBDATA *cson_deep = creating ? GB_create_container(cont_son11, "cont_son") : GB_entry(cont_son11, "cont_son"); TEST_REJECT_NULL(cson_deep);
1136
1137        GBDATA *top1 = creating ? GB_create(gb_main, "top", GB_STRING) : GB_entry(gb_main, "top"); TEST_REJECT_NULL(top1);
1138        GBDATA *top2 = creating ? GB_create(gb_main, "top", GB_STRING) : GB_nextEntry(top1);       TEST_REJECT_NULL(top2);
1139
1140        GBDATA *son11 = creating ? GB_create(cont_top1, "son", GB_INT) : GB_entry(cont_top1, "son"); TEST_REJECT_NULL(son11);
1141        GBDATA *son12 = creating ? GB_create(cont_top1, "son", GB_INT) : GB_nextEntry(son11);        TEST_REJECT_NULL(son12);
1142        GBDATA *son21 = creating ? GB_create(cont_top2, "son", GB_INT) : GB_entry(cont_top2, "son"); TEST_REJECT_NULL(son21);
1143
1144        GBDATA *grandson111 = creating ? GB_create(cont_son11, "grandson", GB_STRING) : GB_entry(cont_son11, "grandson"); TEST_REJECT_NULL(grandson111);
1145        GBDATA *grandson112 = creating ? GB_create(cont_son11, "grandson", GB_STRING) : GB_nextEntry(grandson111);        TEST_REJECT_NULL(grandson112);
1146        GBDATA *grandson211 = creating ? GB_create(cont_son21, "grandson", GB_STRING) : GB_entry(cont_son21, "grandson"); TEST_REJECT_NULL(grandson211);
1147        GBDATA *grandson221 = creating ? GB_create(cont_son22, "grandson", GB_STRING) : GB_entry(cont_son22, "grandson"); TEST_REJECT_NULL(grandson221);
1148        GBDATA *grandson222 = creating ? GB_create(cont_son22, "grandson", GB_STRING) : GB_nextEntry(grandson221);        TEST_REJECT_NULL(grandson222);
1149
1150        // create some entries at uncommon hierarchy locations (compared to entries created above)
1151        GBDATA *ctop_top = creating ? GB_create          (cont_top2, "top", GB_STRING)      : GB_entry(cont_top2, "top");      TEST_REJECT_NULL(ctop_top);
1152        GBDATA *top_son  = creating ? GB_create          (gb_main,   "son", GB_INT)         : GB_entry(gb_main,   "son");      TEST_REJECT_NULL(top_son);
1153        GBDATA *cson     = creating ? GB_create_container(gb_main,   "cont_son")            : GB_entry(gb_main,   "cont_son"); TEST_REJECT_NULL(cson);
1154        GBDATA *cson_gs  = creating ? GB_create          (cson,      "grandson", GB_STRING) : GB_entry(cson,      "grandson"); TEST_REJECT_NULL(cson_gs);
1155
1156        GB_commit_transaction(gb_main);
1157
1158        // test gb_hierarchy_location
1159        {
1160            GB_transaction ta(gb_main);
1161
1162            gb_hierarchy_location loc_top(top1);
1163            gb_hierarchy_location loc_son(son11);
1164            gb_hierarchy_location loc_grandson(grandson222);
1165
1166            TEST_REJECT(loc_top.is_submatch());
1167            TEST_REJECT(loc_son.is_submatch());
1168            TEST_REJECT(loc_grandson.is_submatch());
1169
1170            TEST_EXPECT(loc_top.matches(top1));
1171            TEST_EXPECT(loc_top.matches(top2));
1172            TEST_EXPECT(!loc_top.matches(cont_top1));
1173            TEST_EXPECT(!loc_top.matches(son12));
1174            TEST_EXPECT(!loc_top.matches(cont_son22));
1175            TEST_EXPECT(!loc_top.matches(ctop_top));
1176
1177            TEST_EXPECT(loc_son.matches(son11));
1178            TEST_EXPECT(loc_son.matches(son21));
1179            TEST_EXPECT(!loc_son.matches(top1));
1180            TEST_EXPECT(!loc_son.matches(grandson111));
1181            TEST_EXPECT(!loc_son.matches(cont_son22));
1182            TEST_EXPECT(!loc_son.matches(top_son));
1183
1184            TEST_EXPECT(loc_grandson.matches(grandson222));
1185            TEST_EXPECT(loc_grandson.matches(grandson111));
1186            TEST_EXPECT(!loc_grandson.matches(son11));
1187            TEST_EXPECT(!loc_grandson.matches(top1));
1188            TEST_EXPECT(!loc_grandson.matches(cont_son22));
1189            TEST_EXPECT(!loc_grandson.matches(cson_gs));
1190            TEST_EXPECT(!loc_grandson.matches(gb_main)); // nothing matches gb_main
1191
1192            gb_hierarchy_location loc_ctop_top(ctop_top);
1193            TEST_EXPECT(loc_ctop_top.matches(ctop_top));
1194            TEST_EXPECT(!loc_ctop_top.matches(top1));
1195
1196            gb_hierarchy_location loc_top_son(top_son);
1197            TEST_EXPECT(loc_top_son.matches(top_son));
1198            TEST_EXPECT(!loc_top_son.matches(son11));
1199            TEST_EXPECT(!loc_top_son.matches(gb_main)); // nothing matches gb_main
1200
1201            gb_hierarchy_location loc_gs(cson_gs);
1202            TEST_EXPECT(loc_gs.matches(cson_gs));
1203            TEST_EXPECT(!loc_gs.matches(grandson211));
1204
1205            gb_hierarchy_location loc_root(gb_main);
1206            TEST_REJECT(loc_root.is_valid()); // deny binding hierarchy callback to gb_main
1207            TEST_EXPECT(!loc_root.matches(gb_main));   // nothing matches gb_main
1208            TEST_EXPECT(!loc_root.matches(cont_top1)); // nothing matches an invalid location
1209
1210            // test location from/to path
1211            {
1212                char *path_grandson = loc_grandson.get_db_path(gb_main);
1213                TEST_EXPECT_EQUAL(path_grandson, "/cont_top/cont_son/grandson");
1214
1215                gb_hierarchy_location loc_grandson2(gb_main, path_grandson);
1216                TEST_EXPECT(loc_grandson2.is_valid());
1217                TEST_EXPECT(loc_grandson == loc_grandson2);
1218
1219                char *path_grandson2 = loc_grandson2.get_db_path(gb_main);
1220                TEST_EXPECT_EQUAL(path_grandson, path_grandson2);
1221
1222                free(path_grandson2);
1223                free(path_grandson);
1224            }
1225
1226            gb_hierarchy_location loc_invalid(gb_main, "");
1227            TEST_REJECT(loc_invalid.is_valid());
1228            TEST_REJECT(loc_invalid == loc_invalid); // invalid locations equal nothing
1229
1230            TEST_EXPECT(gb_hierarchy_location(gb_main, "/grandson/cont_top/son").is_valid()); // non-existing path with existing keys is valid
1231            TEST_EXPECT(gb_hierarchy_location(gb_main, "/no/such/path").is_valid());          // non-existing keys generate key-quarks on-the-fly
1232            TEST_EXPECT(gb_hierarchy_location(gb_main, "/grandson/missing/son").is_valid());  // non-existing keys generate key-quarks on-the-fly
1233
1234            gb_hierarchy_location loc_submatch(gb_main, "cont_son/grandson"); // sub-location (any parents will be accepted)
1235            TEST_EXPECT(loc_submatch.is_valid());
1236            TEST_EXPECT(loc_submatch.is_submatch());
1237            TEST_EXPECT(loc_submatch.matches(cson_gs));
1238            TEST_EXPECT(loc_submatch.matches(grandson111));
1239            TEST_REJECT(loc_submatch == loc_grandson); // but loc_grandson.matches(grandson111)!
1240
1241            gb_hierarchy_location loc_anyson(gb_main, "son"); // sub-location (any 'son' location)
1242            TEST_EXPECT(loc_anyson.is_valid());
1243            TEST_EXPECT(loc_anyson.is_submatch());
1244            TEST_EXPECT(loc_anyson.matches(top_son));
1245            TEST_EXPECT(loc_anyson.matches(son11));
1246            TEST_REJECT(loc_anyson == loc_top_son); // but loc_top_son.matches(top_son)!
1247            TEST_REJECT(loc_anyson == loc_son);     // but loc_son.matches(son11)!
1248
1249            TEST_EXPECT_EQUAL(&*SmartCharPtr(loc_submatch.get_db_path(gb_main)), "cont_son/grandson");
1250            TEST_EXPECT_EQUAL(&*SmartCharPtr(loc_anyson  .get_db_path(gb_main)), "son");
1251
1252            // test some pathological locations:
1253            TEST_REJECT(gb_hierarchy_location(gb_main, "/")    .is_valid());
1254            TEST_REJECT(gb_hierarchy_location(gb_main, "//")   .is_valid());
1255            TEST_EXPECT(gb_hierarchy_location(gb_main, "  /  ").is_valid());    // now a valid sub-location (seems like '  ' is a valid key)
1256            TEST_REJECT(gb_hierarchy_location(gb_main, "son//son").is_valid()); // invalid sub-location
1257            TEST_REJECT(gb_hierarchy_location(gb_main, NULL)   .is_valid());
1258        }
1259
1260        if (pass == 1) {
1261            TEST_EXPECT_NO_ERROR(GB_save_as(gb_main, DBNAME, "wb"));
1262        }
1263
1264        // instanciate callback_trace data and install hierarchy callbacks
1265        GBDATA *anySon = son11;
1266
1267        GBDATA *anySonContainer     = cont_son11;
1268        GBDATA *anotherSonContainer = cont_son22;
1269
1270        GBDATA *anyGrandson     = grandson221;
1271        GBDATA *anotherGrandson = grandson112;
1272        GBDATA *elimGrandson    = grandson222;
1273        GBDATA *elimGrandson2   = grandson111;
1274        GBDATA *newGrandson     = NULL;
1275
1276        ct_registry    trace_registry;
1277        callback_trace HIERARCHY_TRACECONSTRUCT(anyElem,changed); // no CB added yet
1278        INIT_CHANGED_HIERARCHY_CALLBACK(anyGrandson);
1279        INIT_DELETED_HIERARCHY_CALLBACK(anyGrandson);
1280        INIT_DELETED_HIERARCHY_CALLBACK(anySonContainer);
1281        INIT_NWCHILD_HIERARCHY_CALLBACK(anySonContainer);
1282
1283        GB_begin_transaction(gb_main);
1284        INIT_CHANGED_HIERARCHY_CALLBACK2(gb_main, "son",               sub_any_son);
1285        INIT_DELETED_HIERARCHY_CALLBACK2(gb_main, "cont_son/grandson", sub_son_grandson);
1286        INIT_DELETED_HIERARCHY_CALLBACK2(gb_main, "cont_son",          sub_any_sonCont);
1287        INIT_NWCHILD_HIERARCHY_CALLBACK2(gb_main, "cont_son",          sub_contson);
1288        GB_commit_transaction(gb_main);
1289
1290        TEST_EXPECT_TRIGGERS_CHECKED();
1291
1292        // trigger change-callback using same DB entry
1293        TRIGGER_CHANGE(anyGrandson);
1294        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anyGrandson);
1295        TEST_EXPECT_TRIGGERS_CHECKED();
1296
1297        // trigger change-callback using another DB entry (same hierarchy)
1298        TRIGGER_CHANGE(anotherGrandson);
1299        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anotherGrandson);
1300        TEST_EXPECT_TRIGGERS_CHECKED();
1301
1302        // check only sub-hierarchy-callback is triggered by an element at different hierarchy
1303        TRIGGER_CHANGE(anySon);
1304        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, anySon);
1305        TEST_EXPECT_TRIGGERS_CHECKED();
1306
1307        // trigger change-callback using both DB entries (in two TAs)
1308        TRIGGER_CHANGE(anyGrandson);
1309        TRIGGER_CHANGE(anotherGrandson);
1310        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anyGrandson);
1311        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anotherGrandson);
1312        TEST_EXPECT_TRIGGERS_CHECKED();
1313
1314        // trigger change-callback using both DB entries (in one TA)
1315        TRIGGER_2_CHANGES(anyGrandson, anotherGrandson);
1316        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anyGrandson);
1317        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson, anotherGrandson);
1318        TEST_EXPECT_TRIGGERS_CHECKED();
1319
1320        // trigger son-created-callback
1321        {
1322            GB_initial_transaction ta(gb_main);
1323            if (ta.ok()) {
1324                GBDATA *someson = GB_create(anySonContainer, "someson", GB_STRING); TEST_REJECT_NULL(someson);
1325            }
1326            TEST_EXPECT_NO_ERROR(ta.close(NULL));
1327        }
1328        TEST_EXPECT_NCHILD_HIER_TRIGGERED(anySonContainer, anySonContainer);
1329        TEST_EXPECT_NCHILD_HIER_TRIGGERED(sub_contson,     anySonContainer); // sub-hierarchy-callback
1330        TEST_EXPECT_TRIGGERS_CHECKED();
1331
1332        // trigger 2 son-created-callbacks (for 2 containers) and one change-callback (for a newly created son)
1333        {
1334            GB_initial_transaction ta(gb_main);
1335            if (ta.ok()) {
1336                newGrandson     = GB_create(anotherSonContainer, "grandson", GB_STRING); TEST_REJECT_NULL(newGrandson);
1337                GBDATA *someson = GB_create(anySonContainer,     "someson",  GB_STRING); TEST_REJECT_NULL(someson);
1338            }
1339            TEST_EXPECT_NO_ERROR(ta.close(NULL));
1340        }
1341        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyGrandson,     newGrandson);
1342        TEST_EXPECT_NCHILD_HIER_TRIGGERED(anySonContainer, anotherSonContainer);
1343        TEST_EXPECT_NCHILD_HIER_TRIGGERED(anySonContainer, anySonContainer);
1344        TEST_EXPECT_NCHILD_HIER_TRIGGERED(sub_contson,     anotherSonContainer); // sub-hierarchy-callback
1345        TEST_EXPECT_NCHILD_HIER_TRIGGERED(sub_contson,     anySonContainer);     // sub-hierarchy-callback
1346        TEST_EXPECT_TRIGGERS_CHECKED();
1347
1348        // trigger delete-callback
1349        {
1350            GB_initial_transaction ta(gb_main);
1351            TEST_EXPECT_NO_ERROR(GB_delete(elimGrandson));
1352            TEST_EXPECT_NO_ERROR(ta.close(NULL));
1353        }
1354        TEST_EXPECT_DELETE_HIER_TRIGGERED(anyGrandson,      elimGrandson);
1355        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_son_grandson, elimGrandson); // sub-hierarchy-callback is triggered as well
1356        TEST_EXPECT_TRIGGERS_CHECKED();
1357
1358        // bind normal (non-hierarchical) callbacks to entries which trigger hierarchical callbacks and ..
1359        calledWith::timer = 0;
1360        GB_begin_transaction(gb_main);
1361
1362        INIT_CHANGED_CALLBACK(anotherGrandson);
1363        INIT_DELETED_CALLBACK(elimGrandson2);
1364
1365        GB_commit_transaction(gb_main);
1366
1367        TEST_EXPECT_TRIGGERS_CHECKED();
1368
1369        {
1370            GB_initial_transaction ta(gb_main);
1371            if (ta.ok()) {
1372                GB_touch(anotherGrandson);
1373                GB_touch(elimGrandson2);
1374                TEST_EXPECT_NO_ERROR(GB_delete(elimGrandson2));
1375            }
1376        }
1377
1378        // .. test call-order (delete before change, hierarchical before normal):
1379        TEST_EXPECT_DELETE_TRIGGERED_AT(traceHier_anyGrandson_deleted,      elimGrandson2,   1);
1380        TEST_EXPECT_DELETE_TRIGGERED_AT(traceHier_sub_son_grandson_deleted, elimGrandson2,   2); // sub-hierarchy-callback
1381        TEST_EXPECT_DELETE_TRIGGERED_AT(trace_elimGrandson2_deleted,        elimGrandson2,   3);
1382        TEST_EXPECT_CHANGE_TRIGGERED_AT(traceHier_anyGrandson_changed,      anotherGrandson, 4);
1383        TEST_EXPECT_CHANGE_TRIGGERED_AT(trace_anotherGrandson_changed,      anotherGrandson, 5);
1384
1385        TEST_EXPECT_TRIGGERS_CHECKED();
1386
1387        // test removed hierarchy callbacks stop to trigger
1388        REMOVE_CHANGED_HIERARCHY_CALLBACK(anyGrandson);
1389        REMOVE_DELETED_HIERARCHY_CALLBACK(anyGrandson);
1390        GB_begin_transaction(gb_main);
1391        REMOVE_DELETED_HIERARCHY_CALLBACK2(gb_main, "cont_son/grandson", sub_son_grandson);
1392        GB_commit_transaction(gb_main);
1393        TRIGGER_CHANGE(anyGrandson);
1394        {
1395            GB_initial_transaction ta(gb_main);
1396            if (ta.ok()) TEST_EXPECT_NO_ERROR(GB_delete(anyGrandson));
1397        }
1398        TEST_EXPECT_TRIGGERS_CHECKED();
1399
1400        GBDATA *anyElem;
1401
1402        // bind SAME callback to different hierarchy locations
1403        anyElem = top1;  ADD_CHANGED_HIERARCHY_CALLBACK(anyElem); // binds      hierarchy cb to "/top"
1404        anyElem = son11; ADD_CHANGED_HIERARCHY_CALLBACK(anyElem); // binds SAME hierarchy cb to "/cont_top/son"
1405
1406        // - check both trigger independently and together
1407        TRIGGER_CHANGE(top1);
1408        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, top1);
1409        TEST_EXPECT_TRIGGERS_CHECKED();
1410
1411        TRIGGER_CHANGE(son11);
1412        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     son11);
1413        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11);
1414        TEST_EXPECT_TRIGGERS_CHECKED();
1415
1416        TRIGGER_2_CHANGES(top1, son11);
1417        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     top1);
1418        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     son11);
1419        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11);
1420        TEST_EXPECT_TRIGGERS_CHECKED();
1421
1422        // - check removing one does not disable the other
1423        anyElem = son11;  REMOVE_CHANGED_HIERARCHY_CALLBACK(anyElem); // remove hierarchy cb from "/cont_top/son"
1424
1425        TRIGGER_2_CHANGES(top1, son11);
1426        // son11 no longer triggers -> ok
1427        TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11); // only sub-hierarchy-callback triggers
1428        TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     top1);
1429        TEST_EXPECT_TRIGGERS_CHECKED();
1430
1431        // test add/remove hierarchy cb by path
1432        // (Note: sub-hierarchy-callbacks above also use paths)
1433        {
1434            const char       *locpath = "/cont_top/son";
1435            DatabaseCallback  dbcb    = makeDatabaseCallback(some_cb, &HIERARCHY_TRACESTRUCT(anyElem,changed));
1436
1437            {
1438                GB_transaction ta(gb_main);
1439                TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gb_main, locpath, GB_CB_CHANGED, dbcb));
1440            }
1441
1442            // now both should trigger again
1443            TRIGGER_2_CHANGES(top1, son11);
1444            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, top1);
1445            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, son11);
1446            TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11); // plus sub-hierarchy-callback
1447            TEST_EXPECT_TRIGGERS_CHECKED();
1448
1449            {
1450                GB_transaction ta(gb_main);
1451                TEST_EXPECT_NO_ERROR(GB_remove_hierarchy_callback(gb_main, locpath, GB_CB_CHANGED, dbcb));
1452            }
1453
1454            TRIGGER_2_CHANGES(top1, son11);
1455            // son11 no longer triggers -> ok
1456            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem,     top1);
1457            TEST_EXPECT_CHANGE_HIER_TRIGGERED(sub_any_son, son11);
1458            TEST_EXPECT_TRIGGERS_CHECKED();
1459
1460            // check some failing binds
1461            const char *invalidPath = "//such";
1462            {
1463                GB_transaction ta(gb_main);
1464                TEST_EXPECT_ERROR_CONTAINS(GB_add_hierarchy_callback(gb_main, invalidPath,   GB_CB_CHANGED, dbcb), "invalid hierarchy location");
1465                TEST_EXPECT_ERROR_CONTAINS(GB_add_hierarchy_callback(gb_main,                GB_CB_CHANGED, dbcb), "invalid hierarchy location");
1466            }
1467
1468            // bind a hierarchy callback to a "not yet existing" path (i.e. path containing yet unused keys),
1469            // then create an db-entry at that path and test that callback is trigger
1470            const char *unknownPath = "/unknownPath/unknownEntry";
1471            {
1472                GB_transaction ta(gb_main);
1473                TEST_EXPECT_NO_ERROR(GB_add_hierarchy_callback(gb_main, unknownPath, GB_CB_CHANGED, dbcb));
1474            }
1475            TEST_EXPECT_TRIGGERS_CHECKED();
1476
1477            GBDATA *gb_unknown;
1478            {
1479                GB_transaction ta(gb_main);
1480                TEST_EXPECT_RESULT__NOERROREXPORTED(gb_unknown = GB_search(gb_main, unknownPath, GB_STRING));
1481            }
1482            TEST_EXPECT_TRIGGERS_CHECKED(); // creating an entry does not trigger callback (could call a new callback-type)
1483
1484            TRIGGER_CHANGE(gb_unknown);
1485            TEST_EXPECT_CHANGE_HIER_TRIGGERED(anyElem, gb_unknown);
1486            TEST_EXPECT_TRIGGERS_CHECKED();
1487        }
1488
1489        // check container delete callbacks
1490        GBDATA *emptySonContainer = cont_son12;
1491
1492        TRIGGER_DELETE(emptySonContainer);
1493        TEST_EXPECT_DELETE_HIER_TRIGGERED(anySonContainer, emptySonContainer);
1494        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, emptySonContainer);
1495        TEST_EXPECT_TRIGGERS_CHECKED();
1496
1497        TRIGGER_DELETE(cont_top1); // father of cont_son11
1498        TEST_EXPECT_DELETE_HIER_TRIGGERED(anySonContainer, cont_son11);
1499        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, cont_son11);
1500        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, cson_deep);
1501        TEST_EXPECT_TRIGGERS_CHECKED();
1502
1503        TRIGGER_DELETE(cson); // son container at top-level
1504        TEST_EXPECT_DELETE_HIER_TRIGGERED(sub_any_sonCont, cson);
1505        TEST_EXPECT_TRIGGERS_CHECKED();
1506
1507        // cleanup
1508        GB_close(gb_main);
1509    }
1510
1511    GB_unlink(DBNAME);
1512}
1513
1514#endif // UNIT_TESTS
1515
1516// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.