git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [GSoC][PATCH v3] t: port helper/test-hashmap.c to unit-tests/t-hashmap.c

From
Christian Couder <christian.couder@gmail.com>
Date
Jul 25, 2024, 12:48 UTC
Message-ID
<CAP8UFD0apMhWA_F4La13=P3Mq433_Kq2qXzkVp9FNC+cWi_U9A@mail.gmail.com>
In-Reply-To
<20240711235159.5320-1-shyamthakkar001@gmail.com>

On Fri, Jul 12, 2024 at 1:52 AM Ghanshyam Thakkar <shyamthakkar001@gmail.com> wrote:

Show 6 quoted lines
>
> helper/test-hashmap.c along with t0011-hashmap.sh test the hashmap.h
> library. Migrate them to the unit testing framework for better
> debugging, runtime performance and concise code.
>
> Along with the migration, make 'add' tests from the shellscript order
Nit: s/shellscript/shell script/
> agnostic in unit tests, since they iterate over entries with the same
> keys and we do not guarantee the order.

The above is not very clear. Maybe rephrasing a bit and adding an example could help.

> The helper/test-hashmap.c is still not removed because it contains a
> performance test meant to be run by the user directly (not used in
> t/perf). And it makes sense for such a utility to be a helper.
Show 42 quoted lines
> diff --git a/t/unit-tests/t-hashmap.c b/t/unit-tests/t-hashmap.c
> new file mode 100644
> index 0000000000..3112b10b33
> --- /dev/null
> +++ b/t/unit-tests/t-hashmap.c
> @@ -0,0 +1,358 @@
> +#include "test-lib.h"
> +#include "hashmap.h"
> +#include "strbuf.h"
> +
> +struct test_entry {
> +       int padding; /* hashmap entry no longer needs to be the first member */
> +       struct hashmap_entry ent;
> +       /* key and value as two \0-terminated strings */
> +       char key[FLEX_ARRAY];
> +};
> +
> +static int test_entry_cmp(const void *cmp_data,
> +                         const struct hashmap_entry *eptr,
> +                         const struct hashmap_entry *entry_or_key,
> +                         const void *keydata)
> +{
> +       const int ignore_case = cmp_data ? *((int *)cmp_data) : 0;
> +       const struct test_entry *e1, *e2;
> +       const char *key = keydata;
> +
> +       e1 = container_of(eptr, const struct test_entry, ent);
> +       e2 = container_of(entry_or_key, const struct test_entry, ent);
> +
> +       if (ignore_case)
> +               return strcasecmp(e1->key, key ? key : e2->key);
> +       else
> +               return strcmp(e1->key, key ? key : e2->key);
> +}
> +
> +static const char *get_value(const struct test_entry *e)
> +{
> +       return e->key + strlen(e->key) + 1;
> +}
> +
> +static struct test_entry *alloc_test_entry(unsigned int ignore_case,
> +                                          const char *key, const char *value)
Nit: `unsigned int ignore_case` is a bool flag argument which we often
put at the end of function arguments, so perhaps:
static struct test_entry *alloc_test_entry(const char *key, const char *value,
                                          unsigned int ignore_case)
Show 14 quoted lines
> +{
> +       size_t klen = strlen(key);
> +       size_t vlen = strlen(value);
> +       unsigned int hash = ignore_case ? strihash(key) : strhash(key);
> +       struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2));
> +
> +       hashmap_entry_init(&entry->ent, hash);
> +       memcpy(entry->key, key, klen + 1);
> +       memcpy(entry->key + klen + 1, value, vlen + 1);
> +       return entry;
> +}
> +
> +static struct test_entry *get_test_entry(struct hashmap *map,
> +                                        unsigned int ignore_case, const char *key)
Here also `unsigned int ignore_case` might want to be the last argument.
Show 23 quoted lines
> +{
> +       return hashmap_get_entry_from_hash(
> +               map, ignore_case ? strihash(key) : strhash(key), key,
> +               struct test_entry, ent);
> +}
> +
> +static int key_val_contains(const char *key_val[][2], char seen[], size_t n,
> +                           struct test_entry *entry)
> +{
> +       for (size_t i = 0; i < n; i++) {
> +               if (!strcmp(entry->key, key_val[i][0]) &&
> +                   !strcmp(get_value(entry), key_val[i][1])) {
> +                       if (seen[i])
> +                               return 2;
> +                       seen[i] = 1;
> +                       return 0;
> +               }
> +       }
> +       return 1;
> +}
> +
> +static void setup(void (*f)(struct hashmap *map, int ignore_case),
> +                 int ignore_case)
Why `int ignore_case` here, instead of `unsigned int ignore_case` above?
It's nice that the `ignore_case` argument is the last argument though.
Show 8 quoted lines
> +{
> +       struct hashmap map = HASHMAP_INIT(test_entry_cmp, &ignore_case);
> +
> +       f(&map, ignore_case);
> +       hashmap_clear_and_free(&map, struct test_entry, ent);
> +}
> +
> +static void t_replace(struct hashmap *map, int ignore_case)

Here also, why `int ignore_case` here, instead of `unsigned int ignore_case` towards the top of this file?

I won't mention it anymore below, but I think it might be better to have `unsigned int ignore_case` everywhere.

Show 19 quoted lines
> +{
> +       struct test_entry *entry;
> +
> +       entry = alloc_test_entry(ignore_case, "key1", "value1");
> +       check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
> +
> +       entry = alloc_test_entry(ignore_case, ignore_case ? "Key1" : "key1",
> +                                "value2");
> +       entry = hashmap_put_entry(map, entry, ent);
> +       if (check(entry != NULL))
> +               check_str(get_value(entry), "value1");
> +       free(entry);
> +
> +       entry = alloc_test_entry(ignore_case, "fooBarFrotz", "value3");
> +       check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
> +
> +       entry = alloc_test_entry(ignore_case,
> +                                ignore_case ? "foobarfrotz" : "fooBarFrotz",
> +                                "value4");

Maybe using something like "FOObarFrotZ" instead of "foobarfrotz" would make it clear that we don't need to downcase in case we ignore case.

Show 18 quoted lines
> +       entry = hashmap_put_entry(map, entry, ent);
> +       if (check(entry != NULL))
> +               check_str(get_value(entry), "value3");
> +       free(entry);
> +}
> +
> +static void t_get(struct hashmap *map, int ignore_case)
> +{
> +       struct test_entry *entry;
> +       const char *key_val[][2] = { { "key1", "value1" },
> +                                    { "key2", "value2" },
> +                                    { "fooBarFrotz", "value3" },
> +                                    { ignore_case ? "key4" : "foobarfrotz", "value4" } };
> +       const char *query[][2] = {
> +               { ignore_case ? "Key1" : "key1", "value1" },
> +               { ignore_case ? "keY2" : "key2", "value2" },
> +               { ignore_case ? "foobarfrotz" : "fooBarFrotz", "value3" }
> +       };
I think adding a test case like:
               { ignore_case ? "FOOBarFrotZ" : "foobarfrotz",
                 ignore_case ? : "value3" : "value4" }

which is a bit similar to what Junio suggested, could help a bit check that things work well especially when not ignoring the case.

Show 29 quoted lines
> +       for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
> +               entry = alloc_test_entry(ignore_case, key_val[i][0], key_val[i][1]);
> +               check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
> +       }
> +
> +       for (size_t i = 0; i < ARRAY_SIZE(query); i++) {
> +               entry = get_test_entry(map, ignore_case, query[i][0]);
> +               if (check(entry != NULL))
> +                       check_str(get_value(entry), query[i][1]);
> +               else
> +                       test_msg("query key: %s", query[i][0]);
> +       }
> +
> +       check_pointer_eq(get_test_entry(map, ignore_case, "notInMap"), NULL);
> +       check_int(map->tablesize, ==, 64);
> +       check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
> +}
> +
> +static void t_add(struct hashmap *map, int ignore_case)
> +{
> +       struct test_entry *entry;
> +       const char *key_val[][2] = {
> +               { "key1", "value1" },
> +               { ignore_case ? "Key1" : "key1", "value2" },
> +               { "fooBarFrotz", "value3" },
> +               { ignore_case ? "Foobarfrotz" : "fooBarFrotz", "value4" }
> +       };
> +       const char *queries[] = { "key1",
> +                                 ignore_case ? "Foobarfrotz" : "fooBarFrotz" };

It is tricky that here `queries` is not defined and doesn't contain the same things as in the other functions above:

const char *queries[][2] = ...

Maybe changing the name to something like `query_keys` would help make people aware that here it contains only keys.

Show 50 quoted lines
> +       char seen[ARRAY_SIZE(key_val)] = { 0 };
> +
> +       for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
> +               entry = alloc_test_entry(ignore_case, key_val[i][0], key_val[i][1]);
> +               hashmap_add(map, &entry->ent);
> +       }
> +
> +       for (size_t i = 0; i < ARRAY_SIZE(queries); i++) {
> +               int count = 0;
> +               entry = hashmap_get_entry_from_hash(map,
> +                       ignore_case ? strihash(queries[i]) :
> +                                     strhash(queries[i]),
> +                       queries[i], struct test_entry, ent);
> +
> +               hashmap_for_each_entry_from(map, entry, ent)
> +               {
> +                       int ret;
> +                       if (!check_int((ret = key_val_contains(
> +                                               key_val, seen,
> +                                               ARRAY_SIZE(key_val), entry)),
> +                                      ==, 0)) {
> +                               switch (ret) {
> +                               case 1:
> +                                       test_msg("found entry was not given in the input\n"
> +                                                "    key: %s\n  value: %s",
> +                                                entry->key, get_value(entry));
> +                                       break;
> +                               case 2:
> +                                       test_msg("duplicate entry detected\n"
> +                                                "    key: %s\n  value: %s",
> +                                                entry->key, get_value(entry));
> +                                       break;
> +                               }
> +                       } else {
> +                               count++;
> +                       }
> +               }
> +               check_int(count, ==, 2);
> +       }
> +
> +       for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
> +               if (!check_int(seen[i], ==, 1))
> +                       test_msg("following key-val pair was not iterated over:\n"
> +                                "    key: %s\n  value: %s",
> +                                key_val[i][0], key_val[i][1]);
> +       }
> +
> +       check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
> +       check_pointer_eq(get_test_entry(map, ignore_case, "notInMap"), NULL);
> +}
Thanks!
Previous: Junio C HamanoNext: A U Thor
Message 12 of 20 in “t: port helper/test-hashmap.c to unit-tests/t-hashmap.c”
  1. Ghanshyam ThakkarJun 28, 2024
  2. [GSoC][PATCH v2] t: port helper/test-hashmap.c to unit-tests/t-hashmap.cGhanshyam Thakkar, Jul 8, 2024
  3. Josh SteadmonJul 9, 2024
  4. Junio C HamanoJul 9, 2024
  5. Ghanshyam ThakkarJul 10, 2024
  6. Ghanshyam ThakkarJul 9, 2024
  7. Phillip WoodJul 10, 2024
  8. Ghanshyam ThakkarJul 11, 2024
  9. [GSoC][PATCH v3] t: port helper/test-hashmap.c to unit-tests/t-hashmap.cGhanshyam Thakkar, Jul 11, 2024
  10. Ghanshyam ThakkarJul 12, 2024
  11. Junio C HamanoJul 12, 2024
  12. Christian CouderJul 25, 2024
  13. t: port helper/test-hashmap.c to unit-tests/t-hashmap.cA U Thor, Jul 30, 2024
  14. Christian CouderJul 31, 2024
  15. Junio C HamanoJul 31, 2024
  16. Ghanshyam ThakkarAug 2, 2024
  17. [GSoC][PATCH v5] t: port helper/test-hashmap.c to unit-tests/t-hashmap.cGhanshyam Thakkar, Aug 3, 2024
  18. Christian CouderAug 7, 2024
  19. Ghanshyam ThakkarAug 7, 2024
  20. Junio C HamanoAug 7, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.