From 2d890f004497f00362de8d82f86249bc7b2b2b2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20=C5=BDidek?= <mzidek@redhat.com>
Date: Tue, 15 Mar 2016 17:35:05 +0100
Subject: [PATCH 3/4] tests: Tests for rules/validators infrastructure

Ticket:
https://fedorahosted.org/sssd/ticket/133
---
 Makefile.am                   |   9 +-
 ini/ini_validators_ut_check.c | 551 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 558 insertions(+), 2 deletions(-)
 create mode 100644 ini/ini_validators_ut_check.c

diff --git a/Makefile.am b/Makefile.am
index 5b1da33..1038860 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -69,8 +69,8 @@ libpath_utils_la_LDFLAGS = \
     -Wl,--version-script=$(top_srcdir)/path_utils/libpath_utils.sym
 
 if HAVE_CHECK
-    check_PROGRAMS += path_utils_ut ini_configmod_ut_check
-    TESTS += path_utils_ut ini_configmod_ut_check
+    check_PROGRAMS += path_utils_ut ini_configmod_ut_check ini_validators_ut_check
+    TESTS += path_utils_ut ini_configmod_ut_check ini_validators_ut_check
 endif
 
 path_utils_ut_SOURCES = path_utils/path_utils_ut.c
@@ -348,6 +348,11 @@ ini_configmod_ut_check_LDADD = libini_config.la libcollection.la \
                                libref_array.la \
                                $(CHECK_LIBS)
 
+ini_validators_ut_check_SOURCES = ini/ini_validators_ut_check.c
+ini_validators_ut_check_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS)
+ini_validators_ut_check_LDADD = libini_config.la $(CHECK_LIBS)
+
+
 ini_save_ut_SOURCES = ini/ini_save_ut.c
 ini_save_ut_LDADD = libini_config.la libcollection.la \
                          libbasicobjects.la libpath_utils.la libref_array.la
diff --git a/ini/ini_validators_ut_check.c b/ini/ini_validators_ut_check.c
new file mode 100644
index 0000000..cf208ed
--- /dev/null
+++ b/ini/ini_validators_ut_check.c
@@ -0,0 +1,551 @@
+/*
+    INI LIBRARY
+
+    Unit test for the configuration file validators API.
+
+    Copyright (C) Michal Zidek <mzidek@redhat.com> 2016
+
+    INI Library is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    INI Library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with INI Library.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <check.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+/* #define TRACE_LEVEL 7 */
+#define TRACE_HOME
+#include "trace.h"
+#include "ini_configobj.h"
+#include "ini_config_priv.h"
+
+#define TEST_DIR_PATH ""
+#define TEST_RULES_FILE TEST_DIR_PATH"test_rules.ini"
+
+static void create_rules_from_str(const char *rules,
+                                  struct ini_cfgobj **_rules_obj)
+{
+    FILE *file;
+    size_t written;
+    int ret;
+
+    /* We want to test actual reading from file using
+     * ini_read_rules_from_file, so we create the file here */
+    file = fopen(TEST_RULES_FILE, "w");
+    fail_if(file == NULL, "fopen() failed: %s", strerror(errno));
+    written = fwrite(rules, 1, strlen(rules), file);
+    fail_unless(written == strlen(rules));
+
+    /* allow reading */
+    ret = chmod(TEST_RULES_FILE, 0664);
+    fail_unless(ret == 0, "chmod() failed: %s", strerror(errno));
+
+    fclose(file);
+
+    ret = ini_read_rules_from_file(TEST_RULES_FILE, _rules_obj);
+    fail_unless(ret == 0, "read_rules_from_file() failed: %s", strerror(ret));
+}
+
+static struct ini_cfgobj *get_ini_config_from_str(char input_data[],
+                                                  size_t input_data_len)
+{
+    struct ini_cfgobj *in_cfg;
+    struct ini_cfgfile *file_ctx;
+    int ret;
+
+    ret = ini_config_create(&in_cfg);
+    fail_unless(ret == EOK, "Failed to create config. Error %d.\n", ret);
+
+    ret = ini_config_file_from_mem(input_data, input_data_len, &file_ctx);
+    fail_unless(ret == EOK, "Failed to load config. Error %d.\n", ret);
+
+    ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, INI_MV1S_ALLOW, 0,
+                           in_cfg);
+    fail_unless(ret == EOK, "Failed to parse config. Error %d.\n", ret);
+
+    ini_config_file_destroy(file_ctx);
+
+    return in_cfg;
+}
+
+START_TEST(test_ini_errobj)
+{
+    struct ini_errobj *errobj;
+    int ret;
+    const char TEST_MSG1[] = "Test message one.";
+    const char TEST_MSG2[] = "Test message two.";
+    const char TEST_MSG3[] = "Test message three.";
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    /* We just created the errobj, it should be empty */
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    /* Now add three messages, after adding each message,
+     * check if the errobj has correct content. */
+    ret = ini_errobj_add_msg(errobj, TEST_MSG1);
+    fail_if(ret != 0, "ini_errobj_add_msg() failed: %s", strerror(ret));
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ret = strcmp(TEST_MSG1, ini_errobj_get_msg(errobj));
+    fail_if(ret != 0, "TEST_MSG1 was not found.");
+    ini_errobj_next(errobj);
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ret = ini_errobj_add_msg(errobj, TEST_MSG2);
+    fail_if(ret != 0, "ini_errobj_add_msg() failed: %s", strerror(ret));
+    ini_errobj_reset(errobj); /* strart from first message */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ret = strcmp(TEST_MSG1, ini_errobj_get_msg(errobj));
+    fail_if(ret != 0, "TEST_MSG1 was not found.");
+    ini_errobj_next(errobj);
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ret = strcmp(TEST_MSG2, ini_errobj_get_msg(errobj));
+    fail_if(ret != 0, "TEST_MSG2 was not found.");
+    ini_errobj_next(errobj);
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ret = ini_errobj_add_msg(errobj, TEST_MSG3);
+    fail_if(ret != 0, "ini_errobj_add_msg() failed: %s", strerror(ret));
+    ini_errobj_reset(errobj); /* strart from first message */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ret = strcmp(TEST_MSG1, ini_errobj_get_msg(errobj));
+    fail_if(ret != 0, "TEST_MSG1 was not found.");
+    ini_errobj_next(errobj);
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ret = strcmp(TEST_MSG2, ini_errobj_get_msg(errobj));
+    fail_if(ret != 0, "TEST_MSG2 was not found.");
+    ini_errobj_next(errobj);
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ret = strcmp(TEST_MSG3, ini_errobj_get_msg(errobj));
+    fail_if(ret != 0, "TEST_MSG3 was not found.");
+    ini_errobj_next(errobj);
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+}
+END_TEST
+
+
+START_TEST(test_ini_noerror)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    char input_rules[] =
+        "[rule/always_succeed]\n"
+        "validator = ini_dummy_noerror\n";
+
+    char input_cfg[] =
+        "[section]\n"
+        "# Content of this file should not matter\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_error)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+    const char *errmsg;
+
+    char input_rules[] =
+        "[rule/generate_error]\n"
+        "validator = ini_dummy_error\n";
+
+    char input_cfg[] =
+        "[section]\n"
+        "# Content of this file should not matter\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate exactly one error */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    errmsg = ini_errobj_get_msg(errobj);
+    ret = strcmp(errmsg, "[rule/generate_error]: Error");
+    fail_unless(ret == 0, "Got msg: %s", errmsg);
+    ini_errobj_next(errobj);
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_unknown_validator)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    char input_rules[] =
+        "[rule/always_succeed]\n"
+        "validator = nonexistent_validator\n";
+
+    char input_cfg[] =
+        "[section]\n"
+        "# Content of this file should not matter\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate exactly one error */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    ini_errobj_next(errobj);
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+static int custom_noerror(const char *rule_name,
+                              struct ini_cfgobj *rules_obj,
+                              struct ini_cfgobj *config_obj,
+                              struct ini_errobj *errobj)
+{
+    return 0;
+}
+
+static int custom_error(const char *rule_name,
+                            struct ini_cfgobj *rules_obj,
+                            struct ini_cfgobj *config_obj,
+                            struct ini_errobj *errobj)
+{
+    return ini_errobj_add_msg(errobj, "Error");
+}
+
+START_TEST(test_custom_noerror)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+    struct ini_validator noerror = {"custom_noerror", custom_noerror};
+
+    char input_rules[] =
+        "[rule/custom_succeed]\n"
+        "validator = custom_noerror\n";
+
+    char input_cfg[] =
+        "[section]\n"
+        "# Content of this file should not matter\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    /* Pass the custom validator to ini_rules_check() */
+    ret = ini_rules_check(rules_obj, cfg_obj, &noerror, 1, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate no errors */
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_custom_error)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+    struct ini_validator error = {"custom_error", custom_error};
+    const char *errmsg;
+
+    char input_rules[] =
+        "[rule/custom_error]\n"
+        "validator = custom_error\n";
+
+    char input_cfg[] =
+        "[section]\n"
+        "# Content of this file should not matter\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    /* Pass the custom validator to ini_rules_check() */
+    ret = ini_rules_check(rules_obj, cfg_obj, &error, 1, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate one error */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+    errmsg = ini_errobj_get_msg(errobj);
+    ret = strcmp(errmsg, "[rule/custom_error]: Error");
+    fail_unless(ret == 0, "Got msg: %s", errmsg);
+    ini_errobj_next(errobj);
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_options_ok)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    /* Only bar and baz are allowed for foo section */
+    char input_rules[] =
+        "[rule/options_for_foo]\n"
+        "validator = ini_allowed_options\n"
+        "section_re = ^foo$\n"
+        "option = bar\n"
+        "option = baz\n";
+
+    /* Should check only foo section, other sections are
+     * irrelevant and can contain any option */
+    char input_cfg[] =
+        "[foo]\n"
+        "bar = 0\n"
+        "baz = 0\n"
+        "[oof]\n"
+        "opt1 = 1\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate no errors */
+    fail_unless(ini_errobj_no_more_msgs(errobj));
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_options_no_section)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+    size_t num_err;
+
+    /* Ommit section_re to generate error */
+    char input_rules[] =
+        "[rule/options_for_foo]\n"
+        "validator = ini_allowed_options\n"
+        /* "section_re = ^foo$\n" */
+        "option = bar\n"
+        "option = baz\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[foo]\n"
+        "bar = 0\n"
+        "baz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 2 errors (one from rules_check and one
+     * from the validator itself) */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+
+    num_err = ini_errobj_count(errobj);
+    fail_unless(num_err == 2, "Expected 2 errors, got %d", num_err);
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+
+START_TEST(test_ini_allowed_options_typos)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+    size_t num_err;
+
+    /* Only bar and baz are allowed for foo section */
+    char input_rules[] =
+        "[rule/options_for_foo]\n"
+        "validator = ini_allowed_options\n"
+        "section_re = ^foo$\n"
+        "option = bar\n"
+        "option = baz\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[foo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "abr = 0\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 4 errors */
+    fail_if(ini_errobj_no_more_msgs(errobj));
+
+    num_err = ini_errobj_count(errobj);
+    fail_unless(num_err == 4, "Expected 4 errors, got %d", num_err);
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_sections_str_ok)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    /* Only bar and baz are allowed for foo section */
+    char input_rules[] =
+        "[rule/section_list]\n"
+        "validator = ini_allowed_sections\n"
+        "section = foo\n"
+        "section = bar\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[foo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "[bar]\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 0 errors */
+    fail_unless(ini_errobj_no_more_msgs(errobj), "Unexpected errors found");
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+
+}
+END_TEST
+
+static Suite *ini_validators_utils_suite(void)
+{
+    Suite *s = suite_create("ini_validators");
+
+    TCase *tc_infrastructure = tcase_create("infrastructure");
+    tcase_add_test(tc_infrastructure, test_ini_errobj);
+    tcase_add_test(tc_infrastructure, test_ini_noerror);
+    tcase_add_test(tc_infrastructure, test_ini_error);
+    tcase_add_test(tc_infrastructure, test_unknown_validator);
+    tcase_add_test(tc_infrastructure, test_custom_noerror);
+    tcase_add_test(tc_infrastructure, test_custom_error);
+
+    TCase *tc_allowed_options = tcase_create("ini_allowed_options");
+    tcase_add_test(tc_allowed_options, test_ini_allowed_options_ok);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_options_typos);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_options_no_section);
+
+    suite_add_tcase(s, tc_infrastructure);
+    suite_add_tcase(s, tc_allowed_options);
+
+    return s;
+}
+
+int main(void)
+{
+    int number_failed;
+
+    Suite *s = ini_validators_utils_suite();
+    SRunner *sr = srunner_create(s);
+    /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+    srunner_run_all(sr, CK_ENV);
+    number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
-- 
2.5.0

