dyncfg-unittest.c 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "dyncfg-internals.h"
  3. #include "dyncfg.h"
  4. // ----------------------------------------------------------------------------
  5. // unit test
  6. #define LINE_FILE_STR TOSTRING(__LINE__) "@" __FILE__
  7. struct dyncfg_unittest {
  8. bool enabled;
  9. size_t errors;
  10. DICTIONARY *nodes;
  11. SPINLOCK spinlock;
  12. struct dyncfg_unittest_action *queue;
  13. } dyncfg_unittest_data = { 0 };
  14. typedef struct {
  15. bool enabled;
  16. bool removed;
  17. struct {
  18. double dbl;
  19. bool bln;
  20. } value;
  21. } TEST_CFG;
  22. typedef struct {
  23. const char *id;
  24. const char *source;
  25. bool sync;
  26. DYNCFG_TYPE type;
  27. DYNCFG_CMDS cmds;
  28. DYNCFG_SOURCE_TYPE source_type;
  29. TEST_CFG current;
  30. TEST_CFG expected;
  31. bool received;
  32. bool finished;
  33. size_t last_saves;
  34. bool needs_save;
  35. } TEST;
  36. struct dyncfg_unittest_action {
  37. TEST *t;
  38. BUFFER *result;
  39. BUFFER *payload;
  40. DYNCFG_CMDS cmd;
  41. const char *add_name;
  42. const char *source;
  43. rrd_function_result_callback_t result_cb;
  44. void *result_cb_data;
  45. struct dyncfg_unittest_action *prev, *next;
  46. };
  47. static void dyncfg_unittest_register_error(const char *id, const char *msg) {
  48. if(msg)
  49. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG UNITTEST: error on id '%s': %s", id ? id : "", msg);
  50. __atomic_add_fetch(&dyncfg_unittest_data.errors, 1, __ATOMIC_RELAXED);
  51. }
  52. static int dyncfg_unittest_execute_cb(struct rrd_function_execute *rfe, void *data);
  53. bool dyncfg_unittest_parse_payload(BUFFER *payload, TEST *t, DYNCFG_CMDS cmd, const char *add_name, const char *source) {
  54. CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(payload));
  55. if(!jobj) {
  56. dyncfg_unittest_register_error(t->id, "cannot parse json payload");
  57. return false;
  58. }
  59. struct json_object *json_double;
  60. struct json_object *json_boolean;
  61. json_object_object_get_ex(jobj, "double", &json_double);
  62. double value_double = json_object_get_double(json_double);
  63. json_object_object_get_ex(jobj, "boolean", &json_boolean);
  64. int value_boolean = json_object_get_boolean(json_boolean);
  65. if(cmd == DYNCFG_CMD_UPDATE) {
  66. t->current.value.dbl = value_double;
  67. t->current.value.bln = value_boolean;
  68. }
  69. else if(cmd == DYNCFG_CMD_ADD) {
  70. char buf[strlen(t->id) + strlen(add_name) + 20];
  71. snprintfz(buf, sizeof(buf), "%s:%s", t->id, add_name);
  72. TEST tmp = {
  73. .id = strdupz(buf),
  74. .source = strdupz(source),
  75. .cmds = (t->cmds & ~DYNCFG_CMD_ADD) | DYNCFG_CMD_GET | DYNCFG_CMD_REMOVE | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_TEST,
  76. .sync = t->sync,
  77. .type = DYNCFG_TYPE_JOB,
  78. .source_type = DYNCFG_SOURCE_TYPE_DYNCFG,
  79. .received = true,
  80. .finished = true,
  81. .current =
  82. {.enabled = true,
  83. .removed = false,
  84. .value =
  85. {
  86. .dbl = value_double,
  87. .bln = value_boolean,
  88. }},
  89. .expected = {
  90. .enabled = true,
  91. .removed = false,
  92. .value = {
  93. .dbl = 3.14,
  94. .bln = true,
  95. }
  96. },
  97. .needs_save = true,
  98. };
  99. const DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(dyncfg_unittest_data.nodes, buf, &tmp, sizeof(tmp));
  100. TEST *t2 = dictionary_acquired_item_value(item);
  101. dictionary_acquired_item_release(dyncfg_unittest_data.nodes, item);
  102. dyncfg_add_low_level(localhost, t2->id, "/unittests",
  103. DYNCFG_STATUS_RUNNING, t2->type, t2->source_type, t2->source,
  104. t2->cmds, 0, 0, t2->sync,
  105. dyncfg_unittest_execute_cb, t2);
  106. }
  107. else {
  108. dyncfg_unittest_register_error(t->id, "invalid command received to parse payload");
  109. return false;
  110. }
  111. return true;
  112. }
  113. static int dyncfg_unittest_action(struct dyncfg_unittest_action *a) {
  114. TEST *t = a->t;
  115. int rc = HTTP_RESP_OK;
  116. if(a->cmd == DYNCFG_CMD_ENABLE)
  117. t->current.enabled = true;
  118. else if(a->cmd == DYNCFG_CMD_DISABLE)
  119. t->current.enabled = false;
  120. else if(a->cmd == DYNCFG_CMD_ADD || a->cmd == DYNCFG_CMD_UPDATE)
  121. rc = dyncfg_unittest_parse_payload(a->payload, a->t, a->cmd, a->add_name, a->source) ? HTTP_RESP_OK : HTTP_RESP_BAD_REQUEST;
  122. else if(a->cmd == DYNCFG_CMD_REMOVE)
  123. t->current.removed = true;
  124. else
  125. rc = HTTP_RESP_BAD_REQUEST;
  126. dyncfg_default_response(a->result, rc, NULL);
  127. a->result_cb(a->result, rc, a->result_cb_data);
  128. buffer_free(a->payload);
  129. freez((void *)a->add_name);
  130. freez(a);
  131. __atomic_store_n(&t->finished, true, __ATOMIC_RELAXED);
  132. return rc;
  133. }
  134. static void *dyncfg_unittest_thread_action(void *ptr __maybe_unused) {
  135. while(1) {
  136. struct dyncfg_unittest_action *a = NULL;
  137. spinlock_lock(&dyncfg_unittest_data.spinlock);
  138. a = dyncfg_unittest_data.queue;
  139. if(a)
  140. DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(dyncfg_unittest_data.queue, a, prev, next);
  141. spinlock_unlock(&dyncfg_unittest_data.spinlock);
  142. if(a)
  143. dyncfg_unittest_action(a);
  144. else
  145. sleep_usec(10 * USEC_PER_MS);
  146. }
  147. }
  148. static int dyncfg_unittest_execute_cb(struct rrd_function_execute *rfe, void *data) {
  149. int rc;
  150. bool run_the_callback = true;
  151. TEST *t = data;
  152. t->received = true;
  153. char buf[strlen(rfe->function) + 1];
  154. memcpy(buf, rfe->function, sizeof(buf));
  155. char *words[MAX_FUNCTION_PARAMETERS]; // an array of pointers for the words in this line
  156. size_t num_words = quoted_strings_splitter_pluginsd(buf, words, MAX_FUNCTION_PARAMETERS);
  157. const char *config = get_word(words, num_words, 0);
  158. const char *id = get_word(words, num_words, 1);
  159. const char *action = get_word(words, num_words, 2);
  160. const char *add_name = get_word(words, num_words, 3);
  161. if(!config || !*config || strcmp(config, PLUGINSD_FUNCTION_CONFIG) != 0) {
  162. char *msg = "did not receive a config call";
  163. dyncfg_unittest_register_error(id, msg);
  164. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  165. goto cleanup;
  166. }
  167. if(!id || !*id) {
  168. char *msg = "did not receive an id";
  169. dyncfg_unittest_register_error(id, msg);
  170. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  171. goto cleanup;
  172. }
  173. if(t->type != DYNCFG_TYPE_TEMPLATE && strcmp(t->id, id) != 0) {
  174. char *msg = "id received is not the expected";
  175. dyncfg_unittest_register_error(id, msg);
  176. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  177. goto cleanup;
  178. }
  179. if(!action || !*action) {
  180. char *msg = "did not receive an action";
  181. dyncfg_unittest_register_error(id, msg);
  182. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  183. goto cleanup;
  184. }
  185. DYNCFG_CMDS cmd = dyncfg_cmds2id(action);
  186. if(cmd == DYNCFG_CMD_NONE) {
  187. char *msg = "action received is not known";
  188. dyncfg_unittest_register_error(id, msg);
  189. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  190. goto cleanup;
  191. }
  192. if(!(t->cmds & cmd)) {
  193. char *msg = "received a command that is not supported";
  194. dyncfg_unittest_register_error(id, msg);
  195. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  196. goto cleanup;
  197. }
  198. if(t->current.removed && cmd != DYNCFG_CMD_ADD) {
  199. char *msg = "received a command for a removed entry";
  200. dyncfg_unittest_register_error(id, msg);
  201. rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST, msg);
  202. goto cleanup;
  203. }
  204. struct dyncfg_unittest_action *a = callocz(1, sizeof(*a));
  205. a->t = t;
  206. a->add_name = add_name ? strdupz(add_name) : NULL;
  207. a->source = rfe->source,
  208. a->result = rfe->result.wb;
  209. a->payload = buffer_dup(rfe->payload);
  210. a->cmd = cmd;
  211. a->result_cb = rfe->result.cb;
  212. a->result_cb_data = rfe->result.data;
  213. run_the_callback = false;
  214. if(t->sync)
  215. rc = dyncfg_unittest_action(a);
  216. else {
  217. spinlock_lock(&dyncfg_unittest_data.spinlock);
  218. DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(dyncfg_unittest_data.queue, a, prev, next);
  219. spinlock_unlock(&dyncfg_unittest_data.spinlock);
  220. rc = HTTP_RESP_OK;
  221. }
  222. cleanup:
  223. if(run_the_callback) {
  224. __atomic_store_n(&t->finished, true, __ATOMIC_RELAXED);
  225. if (rfe->result.cb)
  226. rfe->result.cb(rfe->result.wb, rc, rfe->result.data);
  227. }
  228. return rc;
  229. }
  230. static bool dyncfg_unittest_check(TEST *t, const char *cmd, bool received) {
  231. size_t errors = 0;
  232. fprintf(stderr, "CHECK '%s' after cmd '%s'...", t->id, cmd);
  233. if(t->received != received) {
  234. fprintf(stderr, "\n - received flag found '%s', expected '%s'",
  235. t->received?"true":"false",
  236. received?"true":"false");
  237. errors++;
  238. goto cleanup;
  239. }
  240. if(!received)
  241. goto cleanup;
  242. usec_t give_up_ut = now_monotonic_usec() + 2 * USEC_PER_SEC;
  243. while(!__atomic_load_n(&t->finished, __ATOMIC_RELAXED)) {
  244. static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 };
  245. nanosleep(&ns, NULL);
  246. if(now_monotonic_usec() > give_up_ut) {
  247. fprintf(stderr, "\n - gave up waiting for the plugin to process this!");
  248. errors++;
  249. goto cleanup;
  250. }
  251. }
  252. if(t->type != DYNCFG_TYPE_TEMPLATE && t->current.enabled != t->expected.enabled) {
  253. fprintf(stderr, "\n - enabled flag found '%s', expected '%s'",
  254. t->current.enabled?"true":"false",
  255. t->expected.enabled?"true":"false");
  256. errors++;
  257. }
  258. if(t->current.removed != t->expected.removed) {
  259. fprintf(stderr, "\n - removed flag found '%s', expected '%s'",
  260. t->current.removed?"true":"false",
  261. t->expected.removed?"true":"false");
  262. errors++;
  263. }
  264. if(t->current.value.bln != t->expected.value.bln) {
  265. fprintf(stderr, "\n - boolean value found '%s', expected '%s'",
  266. t->current.value.bln?"true":"false",
  267. t->expected.value.bln?"true":"false");
  268. errors++;
  269. }
  270. if(t->current.value.dbl != t->expected.value.dbl) {
  271. fprintf(stderr, "\n - double value found '%f', expected '%f'",
  272. t->current.value.dbl, t->expected.value.dbl);
  273. errors++;
  274. }
  275. DYNCFG *df = dictionary_get(dyncfg_globals.nodes, t->id);
  276. if(!df) {
  277. fprintf(stderr, "\n - not found in DYNCFG nodes dictionary!");
  278. errors++;
  279. }
  280. else if(df->cmds != t->cmds) {
  281. fprintf(stderr, "\n - has different cmds in DYNCFG nodes dictionary; found: ");
  282. dyncfg_cmds2fp(df->cmds, stderr);
  283. fprintf(stderr, ", expected: ");
  284. dyncfg_cmds2fp(t->cmds, stderr);
  285. fprintf(stderr, "\n");
  286. errors++;
  287. }
  288. else if(df->type == DYNCFG_TYPE_JOB && df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !df->saves) {
  289. fprintf(stderr, "\n - DYNCFG job has no saves!");
  290. errors++;
  291. }
  292. else if(df->type == DYNCFG_TYPE_JOB && df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && (!df->payload || !buffer_strlen(df->payload))) {
  293. fprintf(stderr, "\n - DYNCFG job has no payload!");
  294. errors++;
  295. }
  296. else if(df->user_disabled && !df->saves) {
  297. fprintf(stderr, "\n - DYNCFG disabled config has no saves!");
  298. errors++;
  299. }
  300. else if(t->source && string_strcmp(df->source, t->source) != 0) {
  301. fprintf(stderr, "\n - source does not match!");
  302. errors++;
  303. }
  304. else if(df->source && !t->source) {
  305. fprintf(stderr, "\n - there is a source but it shouldn't be any!");
  306. errors++;
  307. }
  308. else if(t->needs_save && df->saves <= t->last_saves) {
  309. fprintf(stderr, "\n - should be saved, but it is not saved!");
  310. errors++;
  311. }
  312. else if(!t->needs_save && df->saves > t->last_saves) {
  313. fprintf(stderr, "\n - should be not be saved, but it saved!");
  314. errors++;
  315. }
  316. cleanup:
  317. if(errors) {
  318. fprintf(stderr, "\n >>> FAILED\n\n");
  319. dyncfg_unittest_register_error(NULL, NULL);
  320. return false;
  321. }
  322. fprintf(stderr, " OK\n");
  323. return true;
  324. }
  325. static void dyncfg_unittest_reset(void) {
  326. TEST *t;
  327. dfe_start_read(dyncfg_unittest_data.nodes, t) {
  328. t->received = t->finished = false;
  329. t->needs_save = false;
  330. DYNCFG *df = dictionary_get(dyncfg_globals.nodes, t->id);
  331. if(!df) {
  332. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG UNITTEST: cannot find id '%s'", t->id);
  333. dyncfg_unittest_register_error(NULL, NULL);
  334. }
  335. else
  336. t->last_saves = df->saves;
  337. }
  338. dfe_done(t);
  339. }
  340. void should_be_saved(TEST *t, DYNCFG_CMDS c) {
  341. DYNCFG *df;
  342. if(t->type == DYNCFG_TYPE_TEMPLATE) {
  343. df = dictionary_get(dyncfg_globals.nodes, t->id);
  344. t->current.enabled = !df->user_disabled;
  345. }
  346. t->needs_save =
  347. c == DYNCFG_CMD_UPDATE ||
  348. (t->current.enabled && c == DYNCFG_CMD_DISABLE) ||
  349. (!t->current.enabled && c == DYNCFG_CMD_ENABLE);
  350. }
  351. static int dyncfg_unittest_run(const char *cmd, BUFFER *wb, const char *payload, const char *source) {
  352. dyncfg_unittest_reset();
  353. char buf[strlen(cmd) + 1];
  354. memcpy(buf, cmd, sizeof(buf));
  355. char *words[MAX_FUNCTION_PARAMETERS]; // an array of pointers for the words in this line
  356. size_t num_words = quoted_strings_splitter_pluginsd(buf, words, MAX_FUNCTION_PARAMETERS);
  357. // const char *config = get_word(words, num_words, 0);
  358. const char *id = get_word(words, num_words, 1);
  359. char *action = get_word(words, num_words, 2);
  360. const char *add_name = get_word(words, num_words, 3);
  361. DYNCFG_CMDS c = dyncfg_cmds2id(action);
  362. TEST *t = dictionary_get(dyncfg_unittest_data.nodes, id);
  363. if(!t) {
  364. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG UNITTEST: cannot find id '%s' from cmd: %s", id, cmd);
  365. dyncfg_unittest_register_error(NULL, NULL);
  366. return HTTP_RESP_NOT_FOUND;
  367. }
  368. if(t->type == DYNCFG_TYPE_TEMPLATE)
  369. t->received = t->finished = true;
  370. if(c == DYNCFG_CMD_DISABLE)
  371. t->expected.enabled = false;
  372. if(c == DYNCFG_CMD_ENABLE)
  373. t->expected.enabled = true;
  374. if(c == DYNCFG_CMD_UPDATE)
  375. memset(&t->current.value, 0, sizeof(t->current.value));
  376. buffer_flush(wb);
  377. CLEAN_BUFFER *pld = NULL;
  378. if(payload) {
  379. pld = buffer_create(1024, NULL);
  380. buffer_strcat(pld, payload);
  381. }
  382. should_be_saved(t, c);
  383. int rc = rrd_function_run(localhost, wb, 10, HTTP_ACCESS_ADMIN, cmd,
  384. true, NULL,
  385. NULL, NULL,
  386. NULL, NULL,
  387. NULL, NULL,
  388. pld, source);
  389. if(!DYNCFG_RESP_SUCCESS(rc)) {
  390. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG UNITTEST: failed to run: %s; returned code %d", cmd, rc);
  391. dyncfg_unittest_register_error(NULL, NULL);
  392. }
  393. dyncfg_unittest_check(t, cmd, true);
  394. if(rc == HTTP_RESP_OK && t->type == DYNCFG_TYPE_TEMPLATE) {
  395. if(c == DYNCFG_CMD_ADD) {
  396. char buf2[strlen(id) + strlen(add_name) + 2];
  397. snprintfz(buf2, sizeof(buf2), "%s:%s", id, add_name);
  398. TEST *tt = dictionary_get(dyncfg_unittest_data.nodes, buf2);
  399. if (!tt) {
  400. nd_log(NDLS_DAEMON, NDLP_ERR,
  401. "DYNCFG UNITTEST: failed to find newly added id '%s' of command: %s",
  402. id, cmd);
  403. dyncfg_unittest_register_error(NULL, NULL);
  404. }
  405. dyncfg_unittest_check(tt, cmd, true);
  406. }
  407. else {
  408. STRING *template = string_strdupz(t->id);
  409. DYNCFG *df;
  410. dfe_start_read(dyncfg_globals.nodes, df) {
  411. if(df->type == DYNCFG_TYPE_JOB && df->template == template) {
  412. TEST *tt = dictionary_get(dyncfg_unittest_data.nodes, df_dfe.name);
  413. if (!tt) {
  414. nd_log(NDLS_DAEMON, NDLP_ERR,
  415. "DYNCFG UNITTEST: failed to find id '%s' while running command: %s", df_dfe.name, cmd);
  416. dyncfg_unittest_register_error(NULL, NULL);
  417. }
  418. else {
  419. if(c == DYNCFG_CMD_DISABLE)
  420. tt->expected.enabled = false;
  421. if(c == DYNCFG_CMD_ENABLE)
  422. tt->expected.enabled = true;
  423. dyncfg_unittest_check(tt, cmd, true);
  424. }
  425. }
  426. }
  427. dfe_done(df);
  428. string_freez(template);
  429. }
  430. }
  431. return rc;
  432. }
  433. static void dyncfg_unittest_cleanup_files(void) {
  434. char path[FILENAME_MAX];
  435. snprintfz(path, sizeof(path) - 1, "%s/%s", netdata_configured_varlib_dir, "config");
  436. DIR *dir = opendir(path);
  437. if (!dir) {
  438. nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG UNITTEST: cannot open directory '%s'", path);
  439. return;
  440. }
  441. struct dirent *entry;
  442. char filename[FILENAME_MAX + sizeof(entry->d_name)];
  443. while ((entry = readdir(dir)) != NULL) {
  444. if ((entry->d_type == DT_REG || entry->d_type == DT_LNK) && strstartswith(entry->d_name, "unittest:") && strendswith(entry->d_name, ".dyncfg")) {
  445. snprintf(filename, sizeof(filename), "%s/%s", path, entry->d_name);
  446. nd_log(NDLS_DAEMON, NDLP_INFO, "DYNCFG UNITTEST: deleting file '%s'", filename);
  447. unlink(filename);
  448. }
  449. }
  450. closedir(dir);
  451. }
  452. static TEST *dyncfg_unittest_add(TEST t) {
  453. dyncfg_unittest_reset();
  454. TEST *ret = dictionary_set(dyncfg_unittest_data.nodes, t.id, &t, sizeof(t));
  455. if(!dyncfg_add_low_level(localhost, t.id, "/unittests", DYNCFG_STATUS_RUNNING, t.type,
  456. t.source_type, t.source,
  457. t.cmds, 0, 0, t.sync, dyncfg_unittest_execute_cb, ret)) {
  458. dyncfg_unittest_register_error(t.id, "addition of job failed");
  459. }
  460. dyncfg_unittest_check(ret, "plugin create", t.type != DYNCFG_TYPE_TEMPLATE);
  461. return ret;
  462. }
  463. void dyncfg_unittest_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
  464. TEST *v = value;
  465. freez((void *)v->id);
  466. freez((void *)v->source);
  467. }
  468. int dyncfg_unittest(void) {
  469. dyncfg_unittest_data.nodes = dictionary_create(DICT_OPTION_NONE);
  470. dictionary_register_delete_callback(dyncfg_unittest_data.nodes, dyncfg_unittest_delete_cb, NULL);
  471. dyncfg_unittest_cleanup_files();
  472. rrd_functions_inflight_init();
  473. dyncfg_init(false);
  474. // ------------------------------------------------------------------------
  475. // create the thread for testing async communication
  476. netdata_thread_t thread;
  477. netdata_thread_create(&thread, "unittest", NETDATA_THREAD_OPTION_JOINABLE,
  478. dyncfg_unittest_thread_action, NULL);
  479. // ------------------------------------------------------------------------
  480. // single
  481. TEST *single1 = dyncfg_unittest_add((TEST){
  482. .id = strdupz("unittest:sync:single1"),
  483. .source = strdupz(LINE_FILE_STR),
  484. .type = DYNCFG_TYPE_SINGLE,
  485. .cmds = DYNCFG_CMD_GET | DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  486. .source_type = DYNCFG_SOURCE_TYPE_INTERNAL,
  487. .sync = true,
  488. .current = {
  489. .enabled = true,
  490. },
  491. .expected = {
  492. .enabled = true,
  493. }
  494. }); (void)single1;
  495. TEST *single2 = dyncfg_unittest_add((TEST){
  496. .id = strdupz("unittest:async:single2"),
  497. .source = strdupz(LINE_FILE_STR),
  498. .type = DYNCFG_TYPE_SINGLE,
  499. .cmds = DYNCFG_CMD_GET | DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  500. .source_type = DYNCFG_SOURCE_TYPE_INTERNAL,
  501. .sync = false,
  502. .current = {
  503. .enabled = true,
  504. },
  505. .expected = {
  506. .enabled = true,
  507. }
  508. }); (void)single2;
  509. // ------------------------------------------------------------------------
  510. // template
  511. TEST *template1 = dyncfg_unittest_add((TEST){
  512. .id = strdupz("unittest:sync:template1"),
  513. .source = strdupz(LINE_FILE_STR),
  514. .type = DYNCFG_TYPE_TEMPLATE,
  515. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  516. .source_type = DYNCFG_SOURCE_TYPE_INTERNAL,
  517. .sync = true,
  518. }); (void)template1;
  519. TEST *template2 = dyncfg_unittest_add((TEST){
  520. .id = strdupz("unittest:async:template2"),
  521. .source = strdupz(LINE_FILE_STR),
  522. .type = DYNCFG_TYPE_TEMPLATE,
  523. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  524. .source_type = DYNCFG_SOURCE_TYPE_INTERNAL,
  525. .sync = false,
  526. }); (void)template2;
  527. // ------------------------------------------------------------------------
  528. // job
  529. TEST *user1 = dyncfg_unittest_add((TEST){
  530. .id = strdupz("unittest:sync:template1:user1"),
  531. .source = strdupz(LINE_FILE_STR),
  532. .type = DYNCFG_TYPE_JOB,
  533. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  534. .source_type = DYNCFG_SOURCE_TYPE_USER,
  535. .sync = true,
  536. .current = {
  537. .enabled = true,
  538. },
  539. .expected = {
  540. .enabled = true,
  541. }
  542. }); (void)user1;
  543. TEST *user2 = dyncfg_unittest_add((TEST){
  544. .id = strdupz("unittest:async:template2:user2"),
  545. .source = strdupz(LINE_FILE_STR),
  546. .type = DYNCFG_TYPE_JOB,
  547. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  548. .source_type = DYNCFG_SOURCE_TYPE_USER,
  549. .sync = false,
  550. .expected = {
  551. .enabled = true,
  552. }
  553. }); (void)user2;
  554. // ------------------------------------------------------------------------
  555. int rc; (void)rc;
  556. BUFFER *wb = buffer_create(0, NULL);
  557. // ------------------------------------------------------------------------
  558. // dynamic job
  559. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1 add dyn1", wb, "{\"double\":3.14,\"boolean\":true}", LINE_FILE_STR);
  560. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1 add dyn2", wb, "{\"double\":3.14,\"boolean\":true}", LINE_FILE_STR);
  561. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2 add dyn3", wb, "{\"double\":3.14,\"boolean\":true}", LINE_FILE_STR);
  562. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2 add dyn4", wb, "{\"double\":3.14,\"boolean\":true}", LINE_FILE_STR);
  563. // ------------------------------------------------------------------------
  564. // saving of user_disabled
  565. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:single1 disable", wb, NULL, LINE_FILE_STR);
  566. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:single2 disable", wb, NULL, LINE_FILE_STR);
  567. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:user1 disable", wb, NULL, LINE_FILE_STR);
  568. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:user2 disable", wb, NULL, LINE_FILE_STR);
  569. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:dyn1 disable", wb, NULL, LINE_FILE_STR);
  570. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:dyn2 disable", wb, NULL, LINE_FILE_STR);
  571. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:dyn3 disable", wb, NULL, LINE_FILE_STR);
  572. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:dyn4 disable", wb, NULL, LINE_FILE_STR);
  573. // ------------------------------------------------------------------------
  574. // enabling
  575. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:single1 enable", wb, NULL, LINE_FILE_STR);
  576. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:single2 enable", wb, NULL, LINE_FILE_STR);
  577. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:user1 enable", wb, NULL, LINE_FILE_STR);
  578. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:user2 enable", wb, NULL, LINE_FILE_STR);
  579. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:dyn1 enable", wb, NULL, LINE_FILE_STR);
  580. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:dyn2 enable", wb, NULL, LINE_FILE_STR);
  581. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:dyn3 enable", wb, NULL, LINE_FILE_STR);
  582. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:dyn4 enable", wb, NULL, LINE_FILE_STR);
  583. // ------------------------------------------------------------------------
  584. // disabling template
  585. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1 disable", wb, NULL, LINE_FILE_STR);
  586. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2 disable", wb, NULL, LINE_FILE_STR);
  587. // ------------------------------------------------------------------------
  588. // enabling template
  589. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1 enable", wb, NULL, LINE_FILE_STR);
  590. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2 enable", wb, NULL, LINE_FILE_STR);
  591. // ------------------------------------------------------------------------
  592. // adding job on disabled template
  593. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1 disable", wb, NULL, LINE_FILE_STR);
  594. dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2 disable", wb, NULL, LINE_FILE_STR);
  595. TEST *user3 = dyncfg_unittest_add((TEST){
  596. .id = strdupz("unittest:sync:template1:user3"),
  597. .source = strdupz(LINE_FILE_STR),
  598. .type = DYNCFG_TYPE_JOB,
  599. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  600. .source_type = DYNCFG_SOURCE_TYPE_USER,
  601. .sync = true,
  602. .expected = {
  603. .enabled = false,
  604. }
  605. }); (void)user3;
  606. TEST *user4 = dyncfg_unittest_add((TEST){
  607. .id = strdupz("unittest:async:template2:user4"),
  608. .source = strdupz(LINE_FILE_STR),
  609. .type = DYNCFG_TYPE_JOB,
  610. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  611. .source_type = DYNCFG_SOURCE_TYPE_USER,
  612. .sync = false,
  613. .expected = {
  614. .enabled = false,
  615. }
  616. }); (void)user4;
  617. TEST *user5 = dyncfg_unittest_add((TEST){
  618. .id = strdupz("unittest:sync:template1:user5"),
  619. .source = strdupz(LINE_FILE_STR),
  620. .type = DYNCFG_TYPE_JOB,
  621. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  622. .source_type = DYNCFG_SOURCE_TYPE_USER,
  623. .sync = true,
  624. .expected = {
  625. .enabled = false,
  626. }
  627. }); (void)user5;
  628. TEST *user6 = dyncfg_unittest_add((TEST){
  629. .id = strdupz("unittest:async:template2:user6"),
  630. .source = strdupz(LINE_FILE_STR),
  631. .type = DYNCFG_TYPE_JOB,
  632. .cmds = DYNCFG_CMD_SCHEMA | DYNCFG_CMD_UPDATE | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
  633. .source_type = DYNCFG_SOURCE_TYPE_USER,
  634. .sync = false,
  635. .expected = {
  636. .enabled = false,
  637. }
  638. }); (void)user6;
  639. // dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1:user5 disable", wb, NULL, LINE_FILE_STR);
  640. // dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2:user6 disable", wb, NULL, LINE_FILE_STR);
  641. // // ------------------------------------------------------------------------
  642. // // enable template with disabled jobs
  643. //
  644. // user3->expected.enabled = true;
  645. // user5->expected.enabled = false;
  646. // dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:sync:template1 enable", wb, NULL, LINE_FILE_STR);
  647. //
  648. // user4->expected.enabled = true;
  649. // user6->expected.enabled = false;
  650. // dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " unittest:async:template2 enable", wb, NULL, LINE_FILE_STR);
  651. // // ------------------------------------------------------------------------
  652. //
  653. // rc = dyncfg_unittest_run(PLUGINSD_FUNCTION_CONFIG " tree", wb, NULL);
  654. // if(rc == HTTP_RESP_OK)
  655. // fprintf(stderr, "%s\n", buffer_tostring(wb));
  656. void *ptr;
  657. netdata_thread_cancel(thread);
  658. netdata_thread_join(thread, &ptr);
  659. dyncfg_unittest_cleanup_files();
  660. dictionary_destroy(dyncfg_unittest_data.nodes);
  661. buffer_free(wb);
  662. return __atomic_load_n(&dyncfg_unittest_data.errors, __ATOMIC_RELAXED) > 0 ? 1 : 0;
  663. }