2020-11-09 13:23:58 +09:00
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2013-05-02 23:50:49 +02:00
# include "hashmap.h"
2020-04-29 09:55:28 +02:00
# include "string-util.h"
2021-11-24 12:11:17 +01:00
# include "tests.h"
2013-05-02 23:50:49 +02:00
test-hashmap: add test to compare hashmap_free performance
The point here is to compare speed of hashmap_destroy with free and a different
freeing function, to the implementation details of hashmap_clear can be
evaluated.
Results:
current code:
/* test_hashmap_free (slow, 1048576 entries) */
string_hash_ops test took 2.494499s
custom_free_hash_ops test took 2.640449s
string_hash_ops test took 2.287734s
custom_free_hash_ops test took 2.557632s
string_hash_ops test took 2.299791s
custom_free_hash_ops test took 2.586975s
string_hash_ops test took 2.314099s
custom_free_hash_ops test took 2.589327s
string_hash_ops test took 2.319137s
custom_free_hash_ops test took 2.584038s
code with a patch which restores the "fast path" using:
for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; idx = skip_free_buckets(h, idx + 1))
in the case where both free_key and free_value are either free or NULL:
/* test_hashmap_free (slow, 1048576 entries) */
string_hash_ops test took 2.347013s
custom_free_hash_ops test took 2.585104s
string_hash_ops test took 2.311583s
custom_free_hash_ops test took 2.578388s
string_hash_ops test took 2.283658s
custom_free_hash_ops test took 2.621675s
string_hash_ops test took 2.334675s
custom_free_hash_ops test took 2.601568s
So the test is noisy, but there clearly is no significant difference with the
"fast path" restored. I'm surprised by this, but it shows that the current
"safe" implementation does not cause a performance loss.
When the code is compiled with optimization, those times are significantly
lower (e.g. 1.1s and 1.4s), but again, there is no difference with the "fast
path" restored.
The difference between string_hash_ops and custom_free_hash_ops is the
additional cost of global modification and the extra function call.
2018-12-18 11:35:48 +01:00
unsigned custom_counter = 0 ;
static void custom_destruct ( void * p ) {
custom_counter - - ;
free ( p ) ;
}
DEFINE_HASH_OPS_FULL ( boring_hash_ops , char , string_hash_func , string_compare_func , free , char , free ) ;
DEFINE_HASH_OPS_FULL ( custom_hash_ops , char , string_hash_func , string_compare_func , custom_destruct , char , custom_destruct ) ;
2021-11-24 12:11:17 +01:00
TEST ( ordered_hashmap_next ) {
2015-06-16 15:46:40 +02:00
_cleanup_ordered_hashmap_free_ OrderedHashmap * m = NULL ;
int i ;
assert_se ( m = ordered_hashmap_new ( NULL ) ) ;
for ( i = - 2 ; i < = 2 ; i + + )
assert_se ( ordered_hashmap_put ( m , INT_TO_PTR ( i ) , INT_TO_PTR ( i + 10 ) ) = = 1 ) ;
for ( i = - 2 ; i < = 1 ; i + + )
assert_se ( ordered_hashmap_next ( m , INT_TO_PTR ( i ) ) = = INT_TO_PTR ( i + 11 ) ) ;
assert_se ( ! ordered_hashmap_next ( m , INT_TO_PTR ( 2 ) ) ) ;
assert_se ( ! ordered_hashmap_next ( NULL , INT_TO_PTR ( 1 ) ) ) ;
assert_se ( ! ordered_hashmap_next ( m , INT_TO_PTR ( 3 ) ) ) ;
2014-06-15 22:46:05 +02:00
}
2021-11-24 12:11:17 +01:00
TEST ( uint64_compare_func ) {
2013-09-25 17:52:43 +02:00
const uint64_t a = 0x100 , b = 0x101 ;
assert_se ( uint64_compare_func ( & a , & a ) = = 0 ) ;
assert_se ( uint64_compare_func ( & a , & b ) = = - 1 ) ;
assert_se ( uint64_compare_func ( & b , & a ) = = 1 ) ;
2013-05-02 23:50:49 +02:00
}
2021-11-24 12:11:17 +01:00
TEST ( trivial_compare_func ) {
2013-05-02 23:50:49 +02:00
assert_se ( trivial_compare_func ( INT_TO_PTR ( ' a ' ) , INT_TO_PTR ( ' a ' ) ) = = 0 ) ;
assert_se ( trivial_compare_func ( INT_TO_PTR ( ' a ' ) , INT_TO_PTR ( ' b ' ) ) = = - 1 ) ;
assert_se ( trivial_compare_func ( INT_TO_PTR ( ' b ' ) , INT_TO_PTR ( ' a ' ) ) = = 1 ) ;
}
2021-11-24 12:11:17 +01:00
TEST ( string_compare_func ) {
2015-02-24 16:24:14 +01:00
assert_se ( string_compare_func ( " fred " , " wilma " ) ! = 0 ) ;
2013-05-02 23:50:49 +02:00
assert_se ( string_compare_func ( " fred " , " fred " ) = = 0 ) ;
}
2018-01-27 13:10:39 -08:00
static void compare_cache ( Hashmap * map , IteratedCache * cache ) {
const void * * keys = NULL , * * values = NULL ;
unsigned num , idx ;
void * k , * v ;
assert_se ( iterated_cache_get ( cache , & keys , & values , & num ) = = 0 ) ;
assert_se ( num = = 0 | | keys ) ;
assert_se ( num = = 0 | | values ) ;
idx = 0 ;
2020-09-08 11:58:29 +02:00
HASHMAP_FOREACH_KEY ( v , k , map ) {
2018-01-27 13:10:39 -08:00
assert_se ( v = = values [ idx ] ) ;
assert_se ( k = = keys [ idx ] ) ;
idx + + ;
}
assert_se ( idx = = num ) ;
}
2021-11-24 12:11:17 +01:00
TEST ( iterated_cache ) {
2018-01-27 13:10:39 -08:00
Hashmap * m ;
IteratedCache * c ;
assert_se ( m = hashmap_new ( NULL ) ) ;
assert_se ( c = hashmap_iterated_cache_new ( m ) ) ;
compare_cache ( m , c ) ;
for ( int stage = 0 ; stage < 100 ; stage + + ) {
for ( int i = 0 ; i < 100 ; i + + ) {
int foo = stage * 1000 + i ;
assert_se ( hashmap_put ( m , INT_TO_PTR ( foo ) , INT_TO_PTR ( foo + 777 ) ) = = 1 ) ;
}
compare_cache ( m , c ) ;
if ( ! ( stage % 10 ) ) {
for ( int i = 0 ; i < 100 ; i + + ) {
int foo = stage * 1000 + i ;
assert_se ( hashmap_remove ( m , INT_TO_PTR ( foo ) ) = = INT_TO_PTR ( foo + 777 ) ) ;
}
compare_cache ( m , c ) ;
}
}
hashmap_clear ( m ) ;
compare_cache ( m , c ) ;
assert_se ( hashmap_free ( m ) = = NULL ) ;
assert_se ( iterated_cache_free ( c ) = = NULL ) ;
}
2021-11-24 12:11:17 +01:00
TEST ( hashmap_put_strdup ) {
2020-04-29 09:55:28 +02:00
_cleanup_hashmap_free_ Hashmap * m = NULL ;
char * s ;
/* We don't have ordered_hashmap_put_strdup() yet. If it is added,
* these tests should be moved to test - hashmap - plain . c . */
assert_se ( hashmap_put_strdup ( & m , " foo " , " bar " ) = = 1 ) ;
assert_se ( hashmap_put_strdup ( & m , " foo " , " bar " ) = = 0 ) ;
assert_se ( hashmap_put_strdup ( & m , " foo " , " BAR " ) = = - EEXIST ) ;
assert_se ( hashmap_put_strdup ( & m , " foo " , " bar " ) = = 0 ) ;
assert_se ( hashmap_contains ( m , " foo " ) ) ;
s = hashmap_get ( m , " foo " ) ;
assert_se ( streq ( s , " bar " ) ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , " bar " ) = = 1 ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , " bar " ) = = 0 ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , " BAR " ) = = - EEXIST ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , " bar " ) = = 0 ) ;
assert_se ( hashmap_contains ( m , " xxx " ) ) ;
s = hashmap_get ( m , " xxx " ) ;
assert_se ( streq ( s , " bar " ) ) ;
}
2021-11-24 12:11:17 +01:00
TEST ( hashmap_put_strdup_null ) {
2020-04-29 09:55:28 +02:00
_cleanup_hashmap_free_ Hashmap * m = NULL ;
char * s ;
assert_se ( hashmap_put_strdup ( & m , " foo " , " bar " ) = = 1 ) ;
assert_se ( hashmap_put_strdup ( & m , " foo " , " bar " ) = = 0 ) ;
assert_se ( hashmap_put_strdup ( & m , " foo " , NULL ) = = - EEXIST ) ;
assert_se ( hashmap_put_strdup ( & m , " foo " , " bar " ) = = 0 ) ;
assert_se ( hashmap_contains ( m , " foo " ) ) ;
s = hashmap_get ( m , " foo " ) ;
assert_se ( streq ( s , " bar " ) ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , NULL ) = = 1 ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , " bar " ) = = - EEXIST ) ;
assert_se ( hashmap_put_strdup ( & m , " xxx " , NULL ) = = 0 ) ;
assert_se ( hashmap_contains ( m , " xxx " ) ) ;
s = hashmap_get ( m , " xxx " ) ;
assert_se ( s = = NULL ) ;
}
2021-11-24 12:11:17 +01:00
/* This file tests in test-hashmap-plain.c, and tests in test-hashmap-ordered.c, which is generated
* from test - hashmap - plain . c . Hashmap tests should be added to test - hashmap - plain . c , and here only if
* they don ' t apply to ordered hashmaps . */
2018-02-08 18:31:15 +01:00
2021-11-24 12:11:17 +01:00
/* This variable allows us to assert that the tests from different compilation units were actually run. */
int n_extern_tests_run = 0 ;
2018-12-18 11:33:52 +01:00
tests: rework test macros to not take code as parameters
C macros are nasty. We use them, but we try to be conservative with
them. In particular passing literal, complex code blocks as argument is
icky, because of "," handling of C, and also because it's quite a
challange for most code highlighters and similar. Hence, let's avoid
that. Using macros for genreating functions is OK but if so, the
parameters should be simple words, not full code blocks.
hence, rework DEFINE_CUSTOM_TEST_MAIN() to take a function name instead
of code block as argument.
As side-effect this also fixes a bunch of cases where we might end up
returning a negative value from main().
Some uses of DEFINE_CUSTOM_TEST_MAIN() inserted local variables into the
main() functions, these are replaced by static variables, and their
destructors by the static destructor logic.
This doesn't fix any bugs or so, it's just supposed to make the code
easier to work with and improve it easthetically.
Or in other words: let's use macros where it really makes sense, but
let's not go overboard with it.
(And yes, FOREACH_DIRENT() is another one of those macros that take
code, and I dislike that too and regret I ever added that.)
2022-02-01 12:06:59 +01:00
static int intro ( void ) {
assert_se ( n_extern_tests_run = = 0 ) ;
return EXIT_SUCCESS ;
}
static int outro ( void ) {
/* Ensure hashmap and ordered_hashmap were tested. */
assert_se ( n_extern_tests_run = = 2 ) ;
return EXIT_SUCCESS ;
}
2022-02-02 11:06:41 +09:00
DEFINE_TEST_MAIN_FULL ( LOG_INFO , intro , outro ) ;