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