| 1 | #include "cmdline/internal.h" |
| 2 | #include "tests/test_internal.h" |
| 3 | #include <cmdline.h> |
| 4 | |
| 5 | TEST_GROUP_DECLARE(cmdline); |
| 6 | |
| 7 | struct shared_schema_probe { |
| 8 | bool enabled; |
| 9 | }; |
| 10 | |
| 11 | static struct shared_schema_probe shared_left; |
| 12 | static struct shared_schema_probe shared_right; |
| 13 | |
| 14 | static void *shared_left_resolve(const char *path, size_t len) { |
| 15 | return len == strlen(str: "left" ) && strncmp(s1: path, s2: "left" , n: len) == 0 |
| 16 | ? &shared_left |
| 17 | : NULL; |
| 18 | } |
| 19 | |
| 20 | static void *shared_right_resolve(const char *path, size_t len) { |
| 21 | return len == strlen(str: "right" ) && strncmp(s1: path, s2: "right" , n: len) == 0 |
| 22 | ? &shared_right |
| 23 | : NULL; |
| 24 | } |
| 25 | |
| 26 | CMDLINE_SCHEMA_DECLARE(shared_left_schema, "schema_probe" , "left" , |
| 27 | "shared-root resolver probe" , shared_left_resolve, |
| 28 | CMDLINE_SCHEMA_PROP(struct shared_schema_probe, |
| 29 | enabled)); |
| 30 | |
| 31 | CMDLINE_SCHEMA_DECLARE(shared_right_schema, "schema_probe" , "right" , |
| 32 | "shared-root resolver probe" , shared_right_resolve, |
| 33 | CMDLINE_SCHEMA_PROP(struct shared_schema_probe, |
| 34 | enabled)); |
| 35 | |
| 36 | TEST_DECLARE_UNIT(cmdline, shared_schema_root) { |
| 37 | cmdline_dispatch(var: "schema_probe.right.enabled" , val: "true" ); |
| 38 | cmdline_dispatch(var: "schema_probe.left.enabled" , val: "true" ); |
| 39 | TEST_ASSERT(shared_left.enabled); |
| 40 | TEST_ASSERT(shared_right.enabled); |
| 41 | return TEST_SUCCESS; |
| 42 | } |
| 43 | |
| 44 | TEST_DECLARE_UNIT(cmdline, xmacro_descriptors) { |
| 45 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_BOOL), "bool" ); |
| 46 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_INT), "int" ); |
| 47 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_UINT), "uint" ); |
| 48 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_FX), "fx" ); |
| 49 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_DURATION), "duration" ); |
| 50 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_DATA_SIZE), |
| 51 | "data_size" ); |
| 52 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_RANGE), "range" ); |
| 53 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_CPU_MASK), "cpu_mask" ); |
| 54 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_MAC), "mac" ); |
| 55 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_STRING), "string" ); |
| 56 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_LIST), "list" ); |
| 57 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_ERR), "err" ); |
| 58 | TEST_ASSERT_STR_EQ(cmdline_type_to_str(CMDLINE_TYPE_NONE), "none" ); |
| 59 | |
| 60 | TEST_ASSERT_STR_EQ(cmdline_expr_type_to_str(CMDLINE_TYPE_BOOL), "<on/off>" ); |
| 61 | TEST_ASSERT_STR_EQ(cmdline_expr_type_to_str(CMDLINE_TYPE_DURATION), |
| 62 | "<time>" ); |
| 63 | TEST_ASSERT_STR_EQ(cmdline_expr_type_to_str(CMDLINE_TYPE_DATA_SIZE), |
| 64 | "<size>" ); |
| 65 | TEST_ASSERT_STR_EQ(cmdline_expr_type_to_str(CMDLINE_TYPE_FX), "<float>" ); |
| 66 | |
| 67 | TEST_ASSERT_STR_EQ(cmdline_type_raw_hint(CMDLINE_TYPE_BOOL), "on/off" ); |
| 68 | TEST_ASSERT_STR_EQ(cmdline_type_raw_hint(CMDLINE_TYPE_DURATION), "time" ); |
| 69 | TEST_ASSERT_STR_EQ(cmdline_type_raw_hint(CMDLINE_TYPE_DATA_SIZE), "size" ); |
| 70 | |
| 71 | return TEST_SUCCESS; |
| 72 | } |
| 73 | |
| 74 | TEST_DECLARE_UNIT(cmdline, polymorphic_parsing) { |
| 75 | uint64_t mask = 0; /* 0 means unconstrained */ |
| 76 | |
| 77 | struct cmdline_value vb = cmdline_parse_value_for(value: "true" , accepted: mask); |
| 78 | TEST_ASSERT(vb.type == CMDLINE_TYPE_BOOL && vb.b == true); |
| 79 | |
| 80 | struct cmdline_value vu = cmdline_parse_value_for(value: "12345" , accepted: mask); |
| 81 | TEST_ASSERT(vu.type == CMDLINE_TYPE_UINT && vu.u64 == 12345); |
| 82 | |
| 83 | struct cmdline_value vi = cmdline_parse_value_for(value: "-999" , accepted: mask); |
| 84 | TEST_ASSERT(vi.type == CMDLINE_TYPE_INT && vi.i64 == -999); |
| 85 | |
| 86 | struct cmdline_value vfx = cmdline_parse_value_for(value: "0.75" , accepted: mask); |
| 87 | TEST_ASSERT(vfx.type == CMDLINE_TYPE_FX && vfx.fx == FX(0.75)); |
| 88 | |
| 89 | struct cmdline_value vdur = cmdline_parse_value_for(value: "250ms" , accepted: mask); |
| 90 | TEST_ASSERT(vdur.type == CMDLINE_TYPE_DURATION && |
| 91 | vdur.duration == MS_TO_NS(250)); |
| 92 | |
| 93 | struct cmdline_value vsz = cmdline_parse_value_for(value: "64MiB" , accepted: mask); |
| 94 | TEST_ASSERT(vsz.type == CMDLINE_TYPE_DATA_SIZE && |
| 95 | vsz.u64 == 64ULL * 1024 * 1024); |
| 96 | |
| 97 | struct cmdline_value vstr = cmdline_parse_value_for(value: "hello_world" , accepted: mask); |
| 98 | TEST_ASSERT(vstr.type == CMDLINE_TYPE_STRING && |
| 99 | strcmp((char *) vstr.data, "hello_world" ) == 0); |
| 100 | |
| 101 | uint64_t bool_only_mask = CMDLINE_TYPES(CMDLINE_TYPE_BOOL); |
| 102 | struct cmdline_value v_err = |
| 103 | cmdline_parse_value_for(value: "12345" , accepted: bool_only_mask); |
| 104 | TEST_ASSERT_EQ(v_err.type, CMDLINE_TYPE_ERR); |
| 105 | |
| 106 | return TEST_SUCCESS; |
| 107 | } |
| 108 | |
| 109 | TEST_DECLARE_UNIT(cmdline, extraction_helpers) { |
| 110 | struct cmdline_value vu = {.type = CMDLINE_TYPE_UINT, |
| 111 | .u64 = 0x123456789ABCDEF0ULL}; |
| 112 | uint64_t u64_val = 0; |
| 113 | TEST_ASSERT(cmdline_extract_u64(&vu, &u64_val) == ERR_OK && |
| 114 | u64_val == 0x123456789ABCDEF0ULL); |
| 115 | |
| 116 | uint32_t u32_val = 0; |
| 117 | struct cmdline_value vu32 = {.type = CMDLINE_TYPE_UINT, .u64 = 0x12345678}; |
| 118 | TEST_ASSERT(cmdline_extract_u32(&vu32, &u32_val) == ERR_OK && |
| 119 | u32_val == 0x12345678); |
| 120 | |
| 121 | struct cmdline_value vi = {.type = CMDLINE_TYPE_INT, .i64 = -42}; |
| 122 | int64_t i64_val = 0; |
| 123 | TEST_ASSERT(cmdline_extract_i64(&vi, &i64_val) == ERR_OK && i64_val == -42); |
| 124 | |
| 125 | struct cmdline_value vb = {.type = CMDLINE_TYPE_BOOL, .b = true}; |
| 126 | bool b_val = false; |
| 127 | TEST_ASSERT(cmdline_extract_bool(&vb, &b_val) == ERR_OK && b_val == true); |
| 128 | |
| 129 | struct cmdline_value vfx = {.type = CMDLINE_TYPE_FX, .fx = FX(0.5)}; |
| 130 | fx32_32_t fx_val = 0; |
| 131 | TEST_ASSERT(cmdline_extract_fx(&vfx, &fx_val) == ERR_OK && |
| 132 | fx_val == FX(0.5)); |
| 133 | |
| 134 | struct cmdline_value vdur = {.type = CMDLINE_TYPE_DURATION, |
| 135 | .duration = SECONDS_TO_NS(2)}; |
| 136 | time_ns_t dur_val = 0; |
| 137 | TEST_ASSERT(cmdline_extract_duration(&vdur, &dur_val) == ERR_OK && |
| 138 | dur_val == SECONDS_TO_NS(2)); |
| 139 | |
| 140 | const char *str_val = NULL; |
| 141 | struct cmdline_value vstr = {.type = CMDLINE_TYPE_STRING, |
| 142 | .data = "my_string" }; |
| 143 | TEST_ASSERT(cmdline_extract_const_string(&vstr, &str_val) == ERR_OK && |
| 144 | strcmp(str_val, "my_string" ) == 0); |
| 145 | |
| 146 | uint64_t gen_u64 = 0; |
| 147 | int64_t gen_i64 = 0; |
| 148 | bool gen_b = false; |
| 149 | const char *gen_str = NULL; |
| 150 | |
| 151 | TEST_ASSERT(CMDLINE_EXTRACT(&vu, gen_u64) == ERR_OK && |
| 152 | gen_u64 == 0x123456789ABCDEF0ULL); |
| 153 | TEST_ASSERT(CMDLINE_EXTRACT(&vi, gen_i64) == ERR_OK && gen_i64 == -42); |
| 154 | TEST_ASSERT(CMDLINE_EXTRACT(&vb, gen_b) == ERR_OK && gen_b == true); |
| 155 | TEST_ASSERT(CMDLINE_EXTRACT(&vstr, gen_str) == ERR_OK && |
| 156 | strcmp(gen_str, "my_string" ) == 0); |
| 157 | |
| 158 | return TEST_SUCCESS; |
| 159 | } |
| 160 | |
| 161 | TEST_DECLARE_UNIT(cmdline, choices_and_mappings) { |
| 162 | const char *const *choices = CMDLINE_CHOICES("alpha" , "beta" , "gamma" ); |
| 163 | TEST_ASSERT(cmdline_has_choice(choices, "alpha" )); |
| 164 | TEST_ASSERT(cmdline_has_choice(choices, "beta" )); |
| 165 | TEST_ASSERT(cmdline_has_choice(choices, "gamma" )); |
| 166 | TEST_ASSERT(!cmdline_has_choice(choices, "delta" )); |
| 167 | TEST_ASSERT(!cmdline_has_choice(choices, "" )); |
| 168 | |
| 169 | const struct cmdline_map *mappings = |
| 170 | CMDLINE_MAPPINGS(CMDLINE_MAP("fast" , 100), CMDLINE_MAP("slow" , 10)); |
| 171 | uint64_t map_val = 0; |
| 172 | TEST_ASSERT(cmdline_lookup_mapping(mappings, "fast" , &map_val) && |
| 173 | map_val == 100); |
| 174 | TEST_ASSERT(cmdline_lookup_mapping(mappings, "slow" , &map_val) && |
| 175 | map_val == 10); |
| 176 | TEST_ASSERT(!cmdline_lookup_mapping(mappings, "medium" , &map_val)); |
| 177 | |
| 178 | return TEST_SUCCESS; |
| 179 | } |
| 180 | |
| 181 | TEST_DECLARE_UNIT(cmdline, flags_table) { |
| 182 | const struct cmdline_flag *flags = |
| 183 | CMDLINE_FLAGS({"read" , BIT(0)}, {"write" , BIT(1)}, {"exec" , BIT(2)}); |
| 184 | uint64_t mask = 0; |
| 185 | const char *err_tok = NULL; |
| 186 | size_t err_len = 0; |
| 187 | |
| 188 | TEST_ASSERT( |
| 189 | cmdline_parse_flags(flags, "read,write" , &mask, &err_tok, &err_len) && |
| 190 | mask == 0x3); |
| 191 | TEST_ASSERT(cmdline_parse_flags(flags, "exec" , &mask, &err_tok, &err_len) && |
| 192 | mask == 0x4); |
| 193 | TEST_ASSERT(cmdline_parse_flags(flags, "read,exec,write" , &mask, &err_tok, |
| 194 | &err_len) && |
| 195 | mask == 0x7); |
| 196 | TEST_ASSERT(!cmdline_parse_flags(flags, "read,invalid_flag" , &mask, |
| 197 | &err_tok, &err_len)); |
| 198 | |
| 199 | return TEST_SUCCESS; |
| 200 | } |
| 201 | |
| 202 | CMDLINE_CHILD_DEFINE(test_root, group_opt_in); |
| 203 | CMDLINE_CHILD_DEFINE(watchdog, master, heartbeat_interval); |
| 204 | |
| 205 | TEST_DECLARE_UNIT(cmdline, runtime_query) { |
| 206 | struct cmdline_entry *e_root = cmdline_lookup(key: "root" ); |
| 207 | TEST_ASSERT_NONNULL(e_root); |
| 208 | TEST_ASSERT_STR_EQ(e_root->name, "root" ); |
| 209 | |
| 210 | struct cmdline_entry *e_none = cmdline_lookup(key: "non_existent_key_123" ); |
| 211 | TEST_ASSERT_NULL(e_none); |
| 212 | |
| 213 | uint64_t fallback_u64 = CMDLINE_GET("non_existent_key_123" , uint64_t, 9999); |
| 214 | TEST_ASSERT_EQ(fallback_u64, 9999); |
| 215 | |
| 216 | uint64_t out_val = 0; |
| 217 | bool found = cmdline_read_or("non_existent_key_123" , out_val, 42); |
| 218 | TEST_ASSERT(!found); |
| 219 | TEST_ASSERT_EQ(out_val, 42); |
| 220 | |
| 221 | bool opt_in = CMDLINE_CHILD_VALUE(test_root, group_opt_in); |
| 222 | TEST_ASSERT_EQ(opt_in, false); |
| 223 | |
| 224 | struct cmdline_entry *e_hb1 = |
| 225 | CMDLINE_CHILD(watchdog, master, heartbeat_interval); |
| 226 | struct cmdline_entry *e_hb2 = |
| 227 | CMDLINE_CHILD(CMDLINE_NODE(watchdog, master), heartbeat_interval); |
| 228 | TEST_ASSERT_NONNULL(e_hb1); |
| 229 | TEST_ASSERT_PTR_EQ(e_hb1, e_hb2); |
| 230 | TEST_ASSERT_STR_EQ(e_hb1->name, "heartbeat_interval" ); |
| 231 | |
| 232 | /* TODO: Move these two a separate test */ |
| 233 | uint64_t reg = 0; |
| 234 | reg = BIT_SET_FIELD(reg, 0x5, 4, 7); |
| 235 | TEST_ASSERT_EQ(BIT_GET_FIELD(reg, 4, 7), 0x5); |
| 236 | |
| 237 | uint64_t full_mask = BIT_MASK(0, 63); |
| 238 | TEST_ASSERT_EQ(full_mask, UINT64_MAX); |
| 239 | |
| 240 | return TEST_SUCCESS; |
| 241 | } |
| 242 | |
| 243 | TEST_DECLARE_UNIT(cmdline, list_parsing) { |
| 244 | struct cmdline_value vlist = cmdline_parse_list(value: "10,20,30" , accepted: 0); |
| 245 | TEST_ASSERT_EQ(vlist.type, CMDLINE_TYPE_LIST); |
| 246 | |
| 247 | struct cmdline_list list = {0}; |
| 248 | TEST_ASSERT_OK(cmdline_extract_list(&vlist, &list)); |
| 249 | TEST_ASSERT_EQ(list.count, 3); |
| 250 | TEST_ASSERT(list.items[0].type == CMDLINE_TYPE_UINT && |
| 251 | list.items[0].u64 == 10); |
| 252 | TEST_ASSERT(list.items[1].type == CMDLINE_TYPE_UINT && |
| 253 | list.items[1].u64 == 20); |
| 254 | TEST_ASSERT(list.items[2].type == CMDLINE_TYPE_UINT && |
| 255 | list.items[2].u64 == 30); |
| 256 | |
| 257 | return TEST_SUCCESS; |
| 258 | } |
| 259 | |