diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index dca13ec..407e2f7 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -86,10 +86,21 @@ static bool expand_var(char **str, size_t pos, const struct hash_map *vars) size_t r = parse_var_name(*str + pos, &var_name); if (r > 0 && var_name != NULL) { - char *value = get_var_or_env(vars, var_name); - if (value == NULL) - // Undefined variable: expand to empty string - value = ""; + char *value; + char rnd_str[10]; // max 5 digits + null terminator + if (strcmp(var_name, "RANDOM") == 0) + { + short rnd = short_random(); + snprintf(rnd_str, 10, "%d", rnd); + value = rnd_str; + } + else + { + value = get_var_or_env(vars, var_name); + if (value == NULL) + // Undefined variable: expand to empty string + value = ""; + } char *p = insert_into(*str, value, pos, r); free(var_name); diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index 46ac9d1..3d77734 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -55,7 +55,7 @@ struct hash_map *hash_map_init(size_t size) bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, bool *updated) { - if (hash_map == NULL || hash_map->size == 0 || key == NULL) + if (hash_map == NULL || hash_map->size == 0 || key == NULL || value == NULL) return false; size_t h = hash(key); @@ -71,6 +71,7 @@ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, if (strcmp(iter->key, key) == 0) { // update + free(iter->value); iter->value = value; if (updated) *updated = true; @@ -96,16 +97,16 @@ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, return true; } -void hash_map_free(struct hash_map *hash_map) +void hash_map_free(struct hash_map **hash_map) { struct pair_list *l; struct pair_list *prev; - if (hash_map) + if (hash_map != NULL && *hash_map != NULL) { - for (size_t i = 0; i < hash_map->size; i++) + for (size_t i = 0; i < (*hash_map)->size; i++) { - l = hash_map->data[i]; + l = (*hash_map)->data[i]; while (l != NULL) { prev = l; @@ -113,8 +114,8 @@ void hash_map_free(struct hash_map *hash_map) destroy_pair_list(&prev); } } - free(hash_map->data); - free(hash_map); + free((*hash_map)->data); + free(*hash_map); } } diff --git a/src/utils/hash_map/hash_map.h b/src/utils/hash_map/hash_map.h index efdabc2..048a882 100644 --- a/src/utils/hash_map/hash_map.h +++ b/src/utils/hash_map/hash_map.h @@ -19,10 +19,23 @@ struct hash_map struct hash_map *hash_map_init(size_t size); +/** + * @brief Inserts a key-value pair into the hash map. Key and value are expected + * to be on the heap and will be managed by the hash map. It means they are + * consumed by this function. If the key already exists, its value is updated + * and the key is not consumed so it must be freed by the caller. + * + * @param hash_map The hash map. + * @param key The key to insert. + * @param value The value to insert. + * @param updated If not NULL, set to true if the key was already present and + * updated, false if the key was newly inserted. + * @return true on success, false on failure. + */ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, bool *updated); -void hash_map_free(struct hash_map *hash_map); +void hash_map_free(struct hash_map **hash_map); void hash_map_foreach(struct hash_map *hash_map, void (*fn)(const char *, const void *)); diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index 985e085..8d762da 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -2,8 +2,11 @@ #include "vars.h" #include +#include #include #include +#include +#include #include "../hash_map/hash_map.h" @@ -14,6 +17,26 @@ struct hash_map *vars_init(void) return hash_map_init(VARS_INITIAL_SIZE); } +void vars_default(struct hash_map *vars) +{ + set_var_copy(vars, "?", "0"); + pid_t pid = getpid(); + char pid_str[20]; + snprintf(pid_str, sizeof(pid_str), "%d", pid); + set_var_copy(vars, "$", pid_str); +} + +short short_random(void) +{ + static bool seeded = false; + if (!seeded) + { + srand((unsigned)time(NULL)); + seeded = true; + } + return (short)(rand() & 0x7FFF); // force 16 bits positive +} + char *get_var(const struct hash_map *vars, const char *key) { return (char *)hash_map_get(vars, key); @@ -27,14 +50,21 @@ char *get_var_or_env(const struct hash_map *vars, const char *key) return value; } -bool set_var(struct hash_map *vars, const char *key, const char *value) +bool set_var(struct hash_map *vars, const char *key, const char *value, + bool *updated) { - if (key == NULL || value == NULL) - return false; - return hash_map_insert(vars, key, (void *)value, NULL); + return hash_map_insert(vars, key, (void *)value, updated); } bool set_var_copy(struct hash_map *vars, const char *key, const char *value) { - return set_var(vars, strdup(key), strdup(value)); + char *key_copy = strdup(key); + char *value_copy = strdup(value); + bool updated; + bool res = set_var(vars, key_copy, value_copy, &updated); + if (updated || !res) + free(key_copy); + if (!res) + free(value_copy); + return res; } diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h index de1c4c6..85fb5ee 100644 --- a/src/utils/vars/vars.h +++ b/src/utils/vars/vars.h @@ -10,6 +10,16 @@ */ struct hash_map *vars_init(void); +/** + * Set default variables. + */ +void vars_default(struct hash_map *vars); + +/** + * Generate a random short integer (16 bits positive [0-32767]). + */ +short short_random(void); + /** * Get the value of a variable, NULL if not found. */ @@ -26,7 +36,8 @@ char *get_var_or_env(const struct hash_map *vars, const char *key); * the hash_map and need to be on the heap. Returns true on success, false on * failure. */ -bool set_var(struct hash_map *vars, const char *key, const char *value); +bool set_var(struct hash_map *vars, const char *key, const char *value, + bool *updated); /** * Same as set_var, but makes copies of key and value so you don't have to worry diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c index 1e25d7c..fd4295d 100644 --- a/tests/unit/expansion/expand.c +++ b/tests/unit/expansion/expand.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "../../../src/expansion/expansion.h" #include "../../../src/utils/ast/ast.h" @@ -42,7 +43,7 @@ Test(expand, single_quotes_no_expansion) cr_assert_str_eq((char *)command2->command->data, "echo $VAR", "Variable should not expand inside single quotes"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, single_dollar) @@ -61,7 +62,7 @@ Test(expand, single_dollar) cr_assert_str_eq((char *)command2->command->data, "echo $ sign", "Variable should not expand inside single quotes"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, empty_braces_no_expansion) @@ -78,7 +79,7 @@ Test(expand, empty_braces_no_expansion) struct ast_command *command2 = expand(ast_command, vars); cr_assert_null(command2, "Expansion should fail on empty braces"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, basic_expansion) @@ -97,7 +98,7 @@ Test(expand, basic_expansion) cr_assert_str_eq((char *)command2->command->data, "echo expanded", "Variable should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, multiple_expansion) @@ -119,7 +120,7 @@ Test(expand, multiple_expansion) "echo expanded values here", "Multiple variables should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, env_variable) @@ -154,7 +155,7 @@ Test(expand, undefined_variable) cr_assert_str_eq((char *)command2->command->data, "echo ", "Undefined variable should expand to empty string"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, nested_expansion) @@ -174,7 +175,7 @@ Test(expand, nested_expansion) cr_assert_str_eq((char *)command2->command->data, "echo expanded", "Nested variable should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, mixed_quotes_expansion) @@ -196,7 +197,7 @@ Test(expand, mixed_quotes_expansion) "Variable in double quotes should expand, while variable " "in single quotes should not"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, adjacent_variables) @@ -216,5 +217,61 @@ Test(expand, adjacent_variables) cr_assert_str_eq((char *)command2->command->data, "echo helloworld", "Adjacent variables should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); +} + +Test(expand, random) +{ + char str[] = "$RANDOM"; + char *str_heap = strdup(str); + struct list *list = list_append(NULL, str_heap); + struct ast *ast = ast_create_command(list); + struct ast_command *ast_command = ast_get_command(ast); + + struct ast_command *command2 = expand(ast_command, NULL); + cr_assert_not_null(command2, "Expansion returned NULL"); + int rnd = atoi((char *)command2->command->data); + cr_assert(rnd >= 0 && rnd <= 32767, + "RANDOM variable should expand to a value between 0 and 32767"); + ast_free(&ast); +} + +Test(expand, pid) +{ + char str[] = "$$"; + char *str_heap = strdup(str); + struct list *list = list_append(NULL, str_heap); + struct ast *ast = ast_create_command(list); + struct ast_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + vars_default(vars); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + int pid = atoi((char *)command2->command->data); + cr_assert(pid == getpid(), + "$$ variable should expand to the pid of the process"); + ast_free(&ast); + hash_map_free(&vars); +} + +Test(expand, default_last_exit_code) +{ + char str[] = "$?"; + char *str_heap = strdup(str); + struct list *list = list_append(NULL, str_heap); + struct ast *ast = ast_create_command(list); + struct ast_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + vars_default(vars); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + int code = atoi((char *)command2->command->data); + cr_assert(code == 0, + "$? variable should expand to the last exit code (default 0)"); + ast_free(&ast); + hash_map_free(&vars); } diff --git a/tests/unit/utils/hash_map.c b/tests/unit/utils/hash_map.c new file mode 100644 index 0000000..3c3bd04 --- /dev/null +++ b/tests/unit/utils/hash_map.c @@ -0,0 +1,217 @@ +#define _POSIX_C_SOURCE 200809L +#include "../../../src/utils/hash_map/hash_map.h" + +#include +#include +#include +#include + +TestSuite(utils_hash_map); + +Test(utils_hash_map, init_free) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + hash_map_free(&map); +} + +Test(utils_hash_map, insert_basic) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + hash_map_free(&map); +} + +Test(utils_hash_map, insert_multiple) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + hash_map_free(&map); +} + +Test(utils_hash_map, insert_update) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, "key1", strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + + hash_map_free(&map); +} + +Test(utils_hash_map, insert_update_multiple) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, "key1", strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + res = hash_map_insert(map, "key1", strdup("value3"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + + hash_map_free(&map); +} + +Test(utils_hash_map, get_basic) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + char *value = (char *)hash_map_get(map, "key1"); + cr_expect_str_eq(value, "value1"); + + hash_map_free(&map); +} + +Test(utils_hash_map, get_after_update) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + res = hash_map_insert(map, "key1", strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + + char *value = (char *)hash_map_get(map, "key1"); + cr_expect_str_eq(value, "value2"); + + hash_map_free(&map); +} + +Test(utils_hash_map, get_unknown_key) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + char *value = (char *)hash_map_get(map, "unknown_key"); + cr_expect_null(value); + + hash_map_free(&map); +} + +Test(utils_hash_map, delete_key) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + res = hash_map_remove(map, "key1"); + cr_expect_eq(res, true); + + char *value = (char *)hash_map_get(map, "key1"); + cr_expect_null(value); + + hash_map_free(&map); +} + +Test(utils_hash_map, delete_unknown_key) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool res = hash_map_remove(map, "unknown_key"); + cr_expect_eq(res, false); + + hash_map_free(&map); +} + +Test(utils_hash_map, free_nonnull_map) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL); + cr_expect_eq(res, true); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL); + cr_expect_eq(res, true); + + hash_map_free(&map); +} + +Test(utils_hash_map, free_null_map) +{ + hash_map_free(NULL); +} + +static size_t count = 0; +void foreach_fn(const char *key, const void *value) +{ + printf("Key: %s, Value: %s\n", key, (const char *)value); + count++; +} + +Test(utils_hash_map, foreach, .init = cr_redirect_stdout) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL); + cr_expect_eq(res, true); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL); + cr_expect_eq(res, true); + + count = 0; + hash_map_foreach(map, foreach_fn); + fflush(stdout); + cr_expect_eq(count, 2); + cr_expect_stdout_eq_str( + "Key: key2, Value: value2\nKey: key1, Value: value1\n"); + + hash_map_free(&map); +}