return EXIT_FAILURE;
}
-int handle_set(const struct parsed_params *params, char *config_path, struct log_config *conf)
+int handle_set(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
__attribute__((cleanup(free_ptr))) char *key;
if (asprintf(&key, "limiter|%s|%c=", params->tag, params->prio) < 0) {
return copy_file_with_exceptions(config_path, &kv);
}
-int handle_clear(const struct parsed_params *params, char *config_path, struct log_config *conf)
+int handle_clear(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
char *key = "limiter|"; // note: no '=' at the end, so as to match all rules
const char *value = NULL; // removal
return copy_file_with_exceptions(config_path, &kv);
}
-int handle_plog(const struct parsed_params *params, char *config_path, struct log_config *conf)
+int handle_plog(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
const int buffers_bitset = params->plog_buffers_bitset ?: ((1 << LOG_ID_MAX) - 1);
return copy_file_with_exceptions(config_path, &kv);
}
+#ifndef UNIT_TEST
static void print_limit(int limit, const char *tag, char prio, bool *shadowed, bool dynamic)
{
const char *const shadow_str = *shadowed ? "[shadowed] " : "";
*shadowed = true;
}
-static void get_limits(const struct parsed_params *params, char *config_path, struct log_config *conf)
+static void get_limits(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
struct limiter_limits lims_static = __log_limiter_get_limits(params->tag, params->prio);
}
}
-static void get_prio_limits(const struct parsed_params *params, char *config_path, struct log_config *conf)
+static void get_prio_limits(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
static const char prio_list[] = {'V', 'D', 'I', 'W', 'E', 'F' , 'S' , '*'};
struct prio_applies_to applies_dynamic = { .count = 0 };
}
-int handle_get(const struct parsed_params *params, char *config_path, struct log_config *conf)
+int handle_get(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
if (!__log_limiter_create(conf)) {
ERR("error creating limiter\n");
return EXIT_SUCCESS;
}
-int handle_dump(const struct parsed_params *params, char *config_path, struct log_config *conf)
+int handle_dump(const struct parsed_params *params, const char *config_path, struct log_config *conf)
{
if (!__log_limiter_create(conf)) {
ERR("error creating limiter\n");
return EXIT_SUCCESS;
}
-#ifndef UNIT_TEST
int main(int argc, const char **argv)
{
int exit_code = EXIT_FAILURE;
return EXIT_FAILURE;
}
- int (*handles[])(const struct parsed_params *, char *, struct log_config *) = {
+ int (*handles[])(const struct parsed_params *, const char *, struct log_config *) = {
[ACTION_GET] = handle_get,
[ACTION_SET] = handle_set,
[ACTION_DUMP] = handle_dump,
return exit_code;
}
-#endif
+#endif // !UNIT_TEST
#include <logcommon.h>
#include <logctl.h>
+static const char *const TMPFILE_PATH = "src/tests/logctl_testfile";
+
+void _prepare_file(const char *const *lines, size_t lines_cnt)
+{
+ __attribute__((cleanup(close_FILE))) FILE *const file_prep = fopen(TMPFILE_PATH, "w");
+ assert(file_prep);
+
+ for (size_t i = 0; i < lines_cnt; ++i) {
+ fputs(lines[i], file_prep);
+ fputc('\n', file_prep);
+ }
+}
+#define PREPARE_FILE(lines) _prepare_file(lines, NELEMS(lines))
+
+void _compare_file(const char *const *lines, size_t lines_cnt)
+{
+ __attribute__((cleanup(close_FILE))) FILE *const file_result = fopen(TMPFILE_PATH, "r");
+ assert(file_result);
+
+ char line[1024];
+ size_t lines_read = 0;
+ while (fgets(line, sizeof line, file_result)) {
+ assert(lines_read < lines_cnt);
+
+ const size_t expected_len = strlen(lines[lines_read]);
+ assert(line[expected_len] == '\n');
+ assert(!strncmp(line, lines[lines_read], expected_len));
+
+ ++lines_read;
+ }
+}
+#define COMPARE_FILE(lines) _compare_file(lines, NELEMS(lines))
+
void test_copy_file_with_exceptions()
{
- static const char *const tmpfile_path = "src/tests/logctl_testfile";
static const char *const lines_in[] = {
"FOO=BAR",
"FOOOO=BAZ",
{"QWER=", "ASDF"},
};
+ PREPARE_FILE(lines_in);
+
struct keys_values kv = {
.keys = calloc(NELEMS(kv_pairs), sizeof *kv.keys),
.values = calloc(NELEMS(kv_pairs), sizeof *kv.values),
kv.values[i] = kv_pairs[i].value;
}
- FILE *const file_prep = fopen(tmpfile_path, "w");
- assert(file_prep);
- for (size_t i = 0; i < NELEMS(lines_in); ++i) {
- fputs(lines_in[i], file_prep);
- fputc('\n', file_prep);
- }
- fclose(file_prep);
-
- const int ret = copy_file_with_exceptions(tmpfile_path, &kv);
+ const int ret = copy_file_with_exceptions(TMPFILE_PATH, &kv);
assert(ret == EXIT_SUCCESS);
- FILE *const file_result = fopen(tmpfile_path, "r");
- assert(file_result);
+ COMPARE_FILE(lines_out);
+}
- char line[1024];
- size_t lines_read = 0;
- while (fgets(line, sizeof line, file_result)) {
- assert(lines_read <= NELEMS(lines_out));
- if (lines_read == NELEMS(lines_out))
- continue;
+void test_handle_clear()
+{
+ static const char *const lines_in[] = {
+ "limiter|x|y=BAR",
+ "FOOOO=BAZ",
+ "ABC=XYZ",
+ "limiter|zzz|*=15",
+ "ABC=ZYX",
+ "limiter|qwe|*=22",
+ "limiter|qwe|*=22",
+ "QWEZ=ASDX",
+ };
+ static const char *const lines_out[] = {
+ "FOOOO=BAZ",
+ "ABC=XYZ",
+ "ABC=ZYX",
+ "QWEZ=ASDX",
+ };
- const size_t expected_len = strlen(lines_out[lines_read]);
- assert(line[expected_len] == '\n');
- assert(!strncmp(line, lines_out[lines_read], expected_len));
+ PREPARE_FILE(lines_in);
+ assert(EXIT_SUCCESS == handle_clear(NULL, TMPFILE_PATH, NULL));
+ COMPARE_FILE(lines_out);
+}
- ++lines_read;
- }
- fclose(file_result);
+void test_handle_set()
+{
+ static const char *const lines_in[] = {
+ "QWER=QWER",
+ "limiter|x|y=BAR",
+ "FOOOO=BAZ",
+ "limiter|z|t=FOO",
+ "ABC=XYZ",
+ };
+ static const char *const lines_set[] = {
+ "QWER=QWER",
+ "FOOOO=BAZ",
+ "limiter|z|t=FOO",
+ "ABC=XYZ",
+ "limiter|x|y=QUUX",
+ };
+ static const char *const lines_clear[] = {
+ "QWER=QWER",
+ "FOOOO=BAZ",
+ "ABC=XYZ",
+ "limiter|x|y=QUUX",
+ };
+
+ struct parsed_params pp;
+ PREPARE_FILE(lines_in);
+
+ pp.tag = "x";
+ pp.prio = 'y';
+ pp.value = "QUUX";
+ assert(EXIT_SUCCESS == handle_set(&pp, TMPFILE_PATH, NULL));
+ COMPARE_FILE(lines_set);
+
+ pp.tag = "z";
+ pp.prio = 't';
+ pp.value = NULL;
+ assert(EXIT_SUCCESS == handle_set(&pp, TMPFILE_PATH, NULL));
+ COMPARE_FILE(lines_clear);
+}
+
+void test_handle_plog()
+{
+ static const char *const lines_in[] = {
+ "QWER=QWER",
+ "enable_main=1",
+ "FOOOO=BAZ",
+ "enable_radio=0",
+ "ABC=XYZ",
+ };
+ static const char *const lines_enabled[] = {
+ "QWER=QWER",
+ "FOOOO=BAZ",
+ "enable_radio=0",
+ "ABC=XYZ",
+ "enable_main=1",
+ "enable_system=1",
+ };
+ static const char *const lines_disabled[] = {
+ "QWER=QWER",
+ "FOOOO=BAZ",
+ "ABC=XYZ",
+ "enable_system=1",
+ "enable_main=0",
+ "enable_radio=0",
+ };
+ static const char *const lines_all[] = {
+ "QWER=QWER",
+ "FOOOO=BAZ",
+ "ABC=XYZ",
+ "enable_main=1",
+ "enable_radio=1",
+ "enable_system=1",
+ "enable_apps=1",
+ "enable_kmsg=1",
+ "enable_syslog=1",
+ };
+
+ struct parsed_params pp;
+ PREPARE_FILE(lines_in);
+
+ pp.plog_enable = true;
+ pp.plog_buffers_bitset = (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM);
+ assert(EXIT_SUCCESS == handle_plog(&pp, TMPFILE_PATH, NULL));
+ COMPARE_FILE(lines_enabled);
+
+ pp.plog_enable = false;
+ pp.plog_buffers_bitset = (1 << LOG_ID_MAIN) | (1 << LOG_ID_RADIO);
+ assert(EXIT_SUCCESS == handle_plog(&pp, TMPFILE_PATH, NULL));
+ COMPARE_FILE(lines_disabled);
+
+ pp.plog_enable = true;
+ pp.plog_buffers_bitset = 0;
+ assert(EXIT_SUCCESS == handle_plog(&pp, TMPFILE_PATH, NULL));
+ COMPARE_FILE(lines_all);
+}
+
+struct expected_opts {
+ int exit_code;
+ bool retval;
+ struct parsed_params pp;
+};
+
+void check_option_set(size_t argc, const char **argv, const struct expected_opts *exp)
+{
+ struct parsed_params pp = {0};
+ int exit_code = 42;
+ bool retval = parse_options((int) argc, argv, &pp, &exit_code);
+
+ optind = 0;
+ optopt = 0;
+ optarg = NULL;
+
+ assert(retval == exp->retval);
+ assert(retval || (exit_code == exp->exit_code));
+ assert(pp.action == exp->pp.action);
+ assert(pp.prio == exp->pp.prio);
+ assert(pp.plog_buffers_bitset == exp->pp.plog_buffers_bitset);
+ assert(pp.plog_enable == exp->pp.plog_enable);
+ assert(pp.value || !exp->pp.value);
+ assert(!pp.value || exp->pp.value);
+ assert(!pp.value || !strcmp(exp->pp.value, pp.value));
+ assert(pp.tag || !exp->pp.tag);
+ assert(!pp.tag || exp->pp.tag);
+ assert(!pp.tag || !strcmp(exp->pp.tag, pp.tag));
+}
+
+void test_parse_options()
+{
+ /* These are primarily covered by integration tests but there's some value
+ * in running this at build time as well to detect any breakage ASAP. */
+
+ const char *none_args[] = { "x" , NULL};
+ const char *garbage_args[] = { "x", "--cactus" , NULL};
+ const char *clear_args[] = { "x", "-c", "-t", "tag_to_clear" , NULL};
+ const char *clr_all_args[] = { "x", "-c" , NULL};
+ const char *set_args[] = { "x", "-s", "15", "--priority", "e" , NULL};
+ const char *get_args[] = { "x", "-g", "-p", "*", "-t", "*" , NULL};
+ const char *dump_args[] = { "x", "-g" , NULL};
+ const char *enable_args[] = { "x", "--enable", "-b", "main", "-b", "radio", NULL};
+ const char *disable_args[] = { "x", "--disable", "-b", "main", "-b", "radio", NULL};
+ const char *help_args[] = { "x", "--help" , NULL};
+
+ const char *prio_err_args[] = { "x", "--priority", "]" , NULL};
+ const char *buf_err_args[] = { "x", "--buffer", "invalid" , NULL};
+ const char *plog_err_args[] = { "x", "-g", "--buffer", "main" , NULL};
+
+ static const struct expected_opts none_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } };
+ static const struct expected_opts garbage_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } };
+ static const struct expected_opts clear_opts = { EXIT_SUCCESS, true , { ACTION_SET , "tag_to_clear", '*', NULL, false, 0 } };
+ static const struct expected_opts clr_all_opts = { EXIT_SUCCESS, true , { ACTION_CLEAR, "*" , '\0', NULL, false, 0 } };
+ static const struct expected_opts set_opts = { EXIT_SUCCESS, true , { ACTION_SET , "*" , 'E', "15", false, 0 } };
+ static const struct expected_opts get_opts = { EXIT_SUCCESS, true , { ACTION_GET , "*" , '*', NULL, false, 0 } };
+ static const struct expected_opts dump_opts = { EXIT_SUCCESS, true , { ACTION_DUMP , "*" , '\0', NULL, false, 0 } };
+ static const struct expected_opts enable_opts = { EXIT_SUCCESS, true , { ACTION_PLOG , "*" , '\0', NULL, true , 1 << LOG_ID_MAIN | 1 << LOG_ID_RADIO } };
+ static const struct expected_opts disable_opts = { EXIT_SUCCESS, true , { ACTION_PLOG , "*" , '\0', NULL, false, 1 << LOG_ID_MAIN | 1 << LOG_ID_RADIO } };
+ static const struct expected_opts help_opts = { EXIT_SUCCESS, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } };
+
+ static const struct expected_opts prio_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , ']', NULL, false, 0 } };
+ static const struct expected_opts buf_err_opts = { EXIT_FAILURE, false, { ACTION_NONE , "*" , '\0', NULL, false, 0 } };
+ static const struct expected_opts plog_err_opts = { EXIT_FAILURE, false, { ACTION_GET , "*" , '\0', NULL, false, 1 << LOG_ID_MAIN } };
+
+#define CHECK_OPTION_SET(TYPE) check_option_set(NELEMS(TYPE##_args) - 1, TYPE##_args, &(TYPE##_opts))
+ CHECK_OPTION_SET(none);
+ CHECK_OPTION_SET(garbage);
+ CHECK_OPTION_SET(clear);
+ CHECK_OPTION_SET(clr_all);
+ CHECK_OPTION_SET(set);
+ CHECK_OPTION_SET(get);
+ CHECK_OPTION_SET(dump);
+ CHECK_OPTION_SET(enable);
+ CHECK_OPTION_SET(disable);
+ CHECK_OPTION_SET(help);
+
+ CHECK_OPTION_SET(prio_err);
+ CHECK_OPTION_SET(buf_err);
+ CHECK_OPTION_SET(plog_err);
+#undef CHECK_OPTION_SET
}
int main()
{
test_copy_file_with_exceptions();
+ test_parse_options();
+ test_handle_clear();
+ test_handle_set();
+ test_handle_plog();
}