source: tags/ms_r16q3/ARBDB/ad_cb.cxx

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