diff --git a/common/devdoc/thandle_ptr_requirements.md b/common/devdoc/thandle_ptr_requirements.md new file mode 100644 index 0000000..abe8cec --- /dev/null +++ b/common/devdoc/thandle_ptr_requirements.md @@ -0,0 +1,91 @@ +# thandle_ptr_requirements + +## Overview + +`thandle_ptr` is a module that builds on `THANDLE` and provides a "move existing pointer under THANDLE" functionality. + +`THANDLE` has as requirement to "only call malloc once". `THANDLE_PTR` retains that requirement. Note: the existing pointer must have been allocated "somehow" before. Even pointers to stack variables can be moved under `THANDLE_PTR` with the regular gotchas that deallocation happens when leaving the scope of the variable. + +`THANDLE_PTR` should be used when the pointer already exists. In all the other situations, where the pointer hasn't been created yet, regular `THANDLE` should be used. + +`THANDLE_PTR` works by declaring under the hood a struct as below which captures the parameters passed to `THANDLE_PTR_CREATE_WITH_MOVE` and... + +```c +typedef struct PTR_STRUCT_TAG_TYPE_NAME(T) +{ + T pointer; + THANDLE_PTR_FREE_FUNC_TYPE_NAME(T) the_free; +} PTR(T); +``` + +... and then declaring a regular `THANDLE` over the above defined structure type. + +In this document and code implementation "T" is a previously defined type that has pointer semantics and is a C identifier. For example "PS" introduced as: `typedef struct S_TAG* PS;` + +## Usage + +Usage of `THANDLE_PTR` is very similar to `THANDLE`. + +In a C header, introduce the following declaration: +```c +THANDLE_PTR_DECLARE(A_S_PTR); +``` + +In a C source file, introduce the following declaration: +```c +THANDLE_PTR_DEFINE(A_S_PTR); +``` + +Then use it like: +```c +THANDLE(PTR(A_S_PTR)) one = THANDLE_PTR_CREATE_WITH_MOVE(A_S_PTR)(a_s, dispose); +``` + +`a_s` is a pointer of type `A_S_PTR`. + +The above code "moves" ("captures") the pointer `a_s` and produces the `THANDLE` `one`. `dispose` is a user function that takes the original `a_s` pointer and frees all resource used by `a_s`. + +`one->pointer` produces the original `a_s` pointer. Other fields of the `one` pointer should not be used (it also captures the `dispose` function for example). + +## Exposed API + +`THANDLE_PTR` introduces several new types and APIs. + +Types are: + +`PTR(T)` - is a structure that contains the field `pointer` which captures the original `pointer` passed to `THANDLE_PTR_CREATE_WITH_MOVE`. + +`THANDLE(PTR(T))` is the `PTR(T)` above with `THANDLE` semantics. + +`THANDLE_PTR_FREE_FUNC_TYPE(T)` is the function pointer type of the `dispose` function. It takes `T` and frees it. `T` is already a pointer. + +In addition all `THANDLE` regular APIs apply to `PTR(T)` type. For example +```c +THANDLE_ASSING(PTR(T))(&some_ptr_t, NULL); +``` + +APIs are: + +### THANDLE_PTR_CREATE_WITH_MOVE(T) +```c +THANDLE(PTR(T)) THANDLE_PTR_CREATE_WITH_MOVE(T)(T pointer, THANDLE_PTR_FREE_FUNC_TYPE_NAME(T) dispose ); +``` + + +`THANDLE_PTR_CREATE_WITH_MOVE(T)` will "move" `pointer` to a newly created `THANDLE(PTR(T))` which then can be accessed by using the field "pointer" of the structure. + + +**SRS_THANDLE_PTR_02_001: [** `THANDLE_PTR_CREATE_WITH_MOVE(T)` shall return what `THANDLE_CREATE_FROM_CONTENT(PTR(T))(THANDLE_PTR_DISPOSE(T))` returns. **]** + + +### THANDLE_PTR_DISPOSE(T) +``` +static void THANDLE_PTR_DISPOSE(T)(PTR(T)* ptr) +``` + +`THANDLE_PTR_DISPOSE` is a static function that calls the original `dispose` passed to `THANDLE_PTR_CREATE_WITH_MOVE(T)`. + +**SRS_THANDLE_PTR_02_002: [** If the original `dispose` is non-`NULL` then `THANDLE_PTR_DISPOSE(T)` shall call `dispose`. **]** + +**SRS_THANDLE_PTR_02_003: [** `THANDLE_PTR_DISPOSE(T)` shall return. **]** + diff --git a/common/inc/c_pal/thandle_ptr.h b/common/inc/c_pal/thandle_ptr.h new file mode 100644 index 0000000..79a0470 --- /dev/null +++ b/common/inc/c_pal/thandle_ptr.h @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef THANDLE_PTR_H +#define THANDLE_PTR_H + +#include "c_pal/thandle.h" +#include "c_pal/thandle_ll.h" // for THANDLE + +#include "macro_utils/macro_utils.h" // for MU_COUNT_ARG_0, MU_DISPATCH_EMP... + +/*all the macros in this file depend on "T". T is "a pointer to a type", so it looks like "COORD_STRUCT*". */ + +/*this introduces a new *name* for a type that is a typedef of struct THANDLE_PTR_STRUCT_TAG_NAME {...}*/ +#define PTR(T) MU_C2(PTR_STRUCT_, T) + +/*this introduces a new *name* for a structure type that contains "T pointer"*/ +#define PTR_STRUCT_TAG_TYPE_NAME(T) MU_C2(PTR(T), _TAG) + +/*this introduces a new *name* for a function pointer type that takes a T and frees it*/ +#define THANDLE_PTR_FREE_FUNC_TYPE_NAME(T) MU_C2(THANDLE_PTR_FREE_FUNC_, T) + +/*this introduces a new *type* which is a pointer to the free type for T*/ +#define THANDLE_PTR_FREE_FUNC_TYPE(T) typedef void (*THANDLE_PTR_FREE_FUNC_TYPE_NAME(T))(T arg); + +/*this introduces a new *type* which is a structure with a field of type T"*/ +#define PTR_STRUCT_TYPE_TYPEDEF(T) \ +typedef struct PTR_STRUCT_TAG_TYPE_NAME(T) \ +{ \ + T pointer; /*original pointer passed to THANDLE_PTR_CREATE_WITH_MOVE*/ \ + THANDLE_PTR_FREE_FUNC_TYPE_NAME(T) dispose; /*original dispose passed to THANDLE_PTR_CREATE_WITH_MOVE*/ \ +} PTR(T); + +/*this introduces one new *name* for a function which is used to capture a T ptr and move it under the THANDLE(PTR(T))*/ +#define THANDLE_PTR_CREATE_WITH_MOVE(T) MU_C2(THANDLE_PTR_CREATE_WITH_MOVE_, T) + +/*this introduces a new *name* for a function that calls the dispose as passed to THANDLE_PTR_CREATE_WITH_MOVE*/ +#define THANDLE_PTR_DISPOSE(T) MU_C2(THANDLE_PTR_DISPOSE_, T) + +/*this introduces the declaration of a function that returns a THANDLE(PTR(T))*/ +#define THANDLE_PTR_DECLARE(T) \ + THANDLE_PTR_FREE_FUNC_TYPE(T); \ + PTR_STRUCT_TYPE_TYPEDEF(T); \ + THANDLE_TYPE_DECLARE(PTR(T)); \ + THANDLE(PTR(T)) THANDLE_PTR_CREATE_WITH_MOVE(T)(T pointer, THANDLE_PTR_FREE_FUNC_TYPE_NAME(T) dispose ); \ + +/*this introduces the definition of the function declared above*/ +#define THANDLE_PTR_DEFINE(T) \ + THANDLE_TYPE_DEFINE(PTR(T)); \ + static void THANDLE_PTR_DISPOSE(T)(PTR(T)* ptr) \ + { \ + /*Codes_SRS_THANDLE_PTR_02_002: [ If the original dispose is non-NULL then THANDLE_PTR_DISPOSE(T) shall call dispose. ]*/ \ + if(ptr->dispose!=NULL) \ + { \ + ptr->dispose(ptr->pointer); \ + } \ + else \ + { \ + /*Codes_SRS_THANDLE_PTR_02_003: [ THANDLE_PTR_DISPOSE(T) shall return. ]*/ \ + /*do nothing*/ \ + } \ + } \ + THANDLE(PTR(T)) THANDLE_PTR_CREATE_WITH_MOVE(T)(T pointer, THANDLE_PTR_FREE_FUNC_TYPE_NAME(T) dispose ) \ + { \ + PTR(T) temp = \ + { \ + .pointer = pointer, /* this is "move" */ \ + .dispose = dispose \ + }; \ + /*Codes_SRS_THANDLE_PTR_02_001: [ THANDLE_PTR_CREATE_WITH_MOVE(T) shall return what THANDLE_CREATE_FROM_CONTENT(PTR(T))(THANDLE_PTR_DISPOSE(T)) returns. ]*/ \ + return THANDLE_CREATE_FROM_CONTENT(PTR(T))(&temp, THANDLE_PTR_DISPOSE(T), NULL); \ + } + +#endif /*THANDLE_PTR_H*/ diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt index bd12e26..b990de6 100644 --- a/common/tests/CMakeLists.txt +++ b/common/tests/CMakeLists.txt @@ -19,6 +19,7 @@ if(${run_unittests}) build_test_folder(s_list_ut) build_test_folder(thandle_2_ut) build_test_folder(thandle_ut) + build_test_folder(thandle_ptr_ut) endif() if(${run_int_tests}) @@ -27,6 +28,7 @@ if(${run_int_tests}) build_test_folder(interlocked_hl_int) build_test_folder(lazy_init_int) build_test_folder(sm_int) + build_test_folder(thandle_ptr_int) endif() diff --git a/common/tests/thandle_2_ut/thandle_2_ut.c b/common/tests/thandle_2_ut/thandle_2_ut.c index dd585f0..7ee2132 100644 --- a/common/tests/thandle_2_ut/thandle_2_ut.c +++ b/common/tests/thandle_2_ut/thandle_2_ut.c @@ -11,7 +11,7 @@ #include "umock_c/umock_c.h" #define ENABLE_MOCKS -#include "c_pal/gballoc_hl.h" +#include "c_pal/gballoc_hl.h" #include "c_pal/gballoc_hl_redirect.h" /*THANDLE needs malloc/malloc_flex/free to exist*/ #include "malloc_mocks.h" #undef ENABLE_MOCKS @@ -236,7 +236,7 @@ TEST_FUNCTION(T_ON_create_from_content_flex_with_malloc_functions_calls_var_mall TEST_FUNCTION(T_OFF_uses_malloc_when_no_function_is_specified_1) { ///arrange - + STRICT_EXPECTED_CALL(malloc(IGNORED_ARG)); //act diff --git a/common/tests/thandle_ptr_int/CMakeLists.txt b/common/tests/thandle_ptr_int/CMakeLists.txt new file mode 100644 index 0000000..4daa86d --- /dev/null +++ b/common/tests/thandle_ptr_int/CMakeLists.txt @@ -0,0 +1,22 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +set(theseTestsName thandle_ptr_int) + +set(${theseTestsName}_test_files + ${theseTestsName}.c +) + +set(${theseTestsName}_c_files + example.c + example_incomplete_type.c +) + +set(${theseTestsName}_h_files + example.h + example_incomplete_type.h + ../../inc/c_pal/thandle_ptr.h +) + +build_test_artifacts(${theseTestsName} "tests/c_pal" ADDITIONAL_LIBS c_pal c_pal_reals) + diff --git a/common/tests/thandle_ptr_int/example.c b/common/tests/thandle_ptr_int/example.c new file mode 100644 index 0000000..6d49a83 --- /dev/null +++ b/common/tests/thandle_ptr_int/example.c @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#include "c_pal/gballoc_hl.h" // IWYU pragma: keep +#include "c_pal/gballoc_hl_redirect.h" // IWYU pragma: keep + +#include "c_pal/thandle_ptr.h" + +#include "example.h" + +THANDLE_PTR_DEFINE(EXAMPLE_COMPLETE_PTR); + +void dispose_example_complete(EXAMPLE_COMPLETE_PTR example_complete) +{ + free(example_complete); /*nothing else*/ +} \ No newline at end of file diff --git a/common/tests/thandle_ptr_int/example.h b/common/tests/thandle_ptr_int/example.h new file mode 100644 index 0000000..5dc72ea --- /dev/null +++ b/common/tests/thandle_ptr_int/example.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef EXAMPLE_H +#define EXAMPLE_H + +#include "macro_utils/macro_utils.h" + +#include "c_pal/thandle_ptr.h" + +/*this is a complete type*/ +typedef struct EXAMPLE_COMPLETE_TAG +{ + int example_complete; +}EXAMPLE_COMPLETE; + +typedef EXAMPLE_COMPLETE* EXAMPLE_COMPLETE_PTR; + +THANDLE_PTR_DECLARE(EXAMPLE_COMPLETE_PTR); + +void dispose_example_complete(EXAMPLE_COMPLETE_PTR example_complete); + +#endif /*EXAMPLE_H*/ diff --git a/common/tests/thandle_ptr_int/example_incomplete_type.c b/common/tests/thandle_ptr_int/example_incomplete_type.c new file mode 100644 index 0000000..0e884c8 --- /dev/null +++ b/common/tests/thandle_ptr_int/example_incomplete_type.c @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#include "macro_utils/macro_utils.h" + +#include "c_pal/gballoc_hl.h" // IWYU pragma: keep +#include "c_pal/gballoc_hl_redirect.h" // IWYU pragma: keep + +#include "c_pal/thandle_ptr.h" + +#include "example_incomplete_type.h" + +struct EXAMPLE_INCOMPLETE +{ + int example_incomplete; +}; + +THANDLE_PTR_DEFINE(EXAMPLE_INCOMPLETE_PTR); + +EXAMPLE_INCOMPLETE_PTR create_example_incomplete(void) +{ + return malloc(sizeof(struct EXAMPLE_INCOMPLETE)); +} + +void dispose_example_incomplete(EXAMPLE_INCOMPLETE_PTR example_incomplete) +{ + free(example_incomplete); /*nothing else*/ +} \ No newline at end of file diff --git a/common/tests/thandle_ptr_int/example_incomplete_type.h b/common/tests/thandle_ptr_int/example_incomplete_type.h new file mode 100644 index 0000000..70a8bcc --- /dev/null +++ b/common/tests/thandle_ptr_int/example_incomplete_type.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef EXAMPLE_INCOMPLETE_TYPE_H +#define EXAMPLE_INCOMPLETE_TYPE_H + +#include "macro_utils/macro_utils.h" + +#include "c_pal/thandle_ptr.h" + +/*EXAMPLE_INCOMPLETE is an incomplete type (opaque)*/ +typedef struct EXAMPLE_INCOMPLETE* EXAMPLE_INCOMPLETE_PTR; + +THANDLE_PTR_DECLARE(EXAMPLE_INCOMPLETE_PTR); + +EXAMPLE_INCOMPLETE_PTR create_example_incomplete(void); + +void dispose_example_incomplete(EXAMPLE_INCOMPLETE_PTR example_incomplete); + +#endif /*EXAMPLE_INCOMPLETE_TYPE_H*/ diff --git a/common/tests/thandle_ptr_int/thandle_ptr_int.c b/common/tests/thandle_ptr_int/thandle_ptr_int.c new file mode 100644 index 0000000..b38de3e --- /dev/null +++ b/common/tests/thandle_ptr_int/thandle_ptr_int.c @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#include "macro_utils/macro_utils.h" // IWYU pragma: keep + +#include "c_logging/logger.h" + +#include "testrunnerswitcher.h" + +#include "c_pal/gballoc_hl.h" // IWYU pragma: keep +#include "c_pal/gballoc_hl_redirect.h" // IWYU pragma: keep +#include "c_pal/thandle.h" +#include "c_pal/string_utils.h" + +#include "c_pal/thandle_ll.h" +#include "c_pal/thandle.h" // IWYU pragma: keep +#include "c_pal/thandle_ptr.h" + +#include "example.h" +#include "example_incomplete_type.h" + +typedef struct A_S_TAG +{ + int a; + char* s; +}A_S; + +typedef A_S* A_S_PTR; + +static void dispose(A_S_PTR a_s) +{ + free(a_s->s); + free(a_s); +} + +THANDLE_PTR_DECLARE(A_S_PTR); +THANDLE_PTR_DEFINE(A_S_PTR); + + +typedef struct A_S_CONST_TAG +{ + const int a; + const char* s; +}A_S_CONST; + +typedef A_S_CONST* A_S_CONST_PTR; + +static void dispose_const(A_S_CONST_PTR a_s) +{ + free((void*)a_s->s); + free(a_s); +} + +THANDLE_PTR_DECLARE(A_S_CONST_PTR); +THANDLE_PTR_DEFINE(A_S_CONST_PTR); + +BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) + +TEST_SUITE_INITIALIZE(it_does_something) +{ + ASSERT_ARE_EQUAL(int, 0, gballoc_hl_init(NULL, NULL)); +} + +TEST_SUITE_CLEANUP(TestClassCleanup) +{ + gballoc_hl_deinit(); +} + +TEST_FUNCTION_INITIALIZE(f) +{ +} + +TEST_FUNCTION_CLEANUP(cleans) +{ +} + +/*the test will +1. allocate on the heap some structure that needs a special "destroy" function +2. move the pointer to the structure into a THANDLE(PTR()) +3. verify that the pointer in the THANDLE is the same as the original pointer +3.1 verify that the pointer has the same qualifiers as the original pointer (in this case fields are not const) +4. THANDLE_ASSIGN(PTR, NULL) so that memory is freed (the THANDLE memory and the original "destroy" +*/ +TEST_FUNCTION(thandle_int_works_with_both_declare_and_define_in_this_file) +{ + ///arrange + LogInfo("1. allocate on the heap some structure that needs a special \"destroy\" function."); + A_S_PTR a_s = malloc(sizeof(A_S)); + ASSERT_IS_NOT_NULL(a_s); + a_s->a = 42; + a_s->s = sprintf_char("%s", "3333333333333333333333_here_some_string_3333333333333333333333"); + ASSERT_IS_NOT_NULL(a_s->s); + + ///act + LogInfo("2. move the pointer to the structure into a THANDLE(PTR())"); + THANDLE(PTR(A_S_PTR)) one = THANDLE_PTR_CREATE_WITH_MOVE(A_S_PTR)(a_s, dispose); + + ///assert + LogInfo("3. verify that the pointer in the THANDLE is the same as the original pointer"); + ASSERT_IS_NOT_NULL(one); + ASSERT_ARE_EQUAL(void_ptr, a_s, one->pointer); + + ///assert + LogInfo("3.1 verify that the pointer has the same qualifiers as the original pointer (in this case fields are not const)"); + one->pointer->a = 3; + one->pointer->s[0] = 'a'; + + ///cleanup + LogInfo("4. THANDLE_ASSIGN(PTR, NULL) so that memory is freed (the THANDLE memory and the original \"destroy\""); + THANDLE_ASSIGN(PTR(A_S_PTR))(&one, NULL); +} + +/*the test will +1. allocate on the heap some structure that needs a special "destroy" function +2. move the pointer to the structure into a THANDLE(PTR()) +3. verify that the pointer in the THANDLE is the same as the original pointer +3.1 verify that the pointer has the same qualifiers as the original pointer (in this case fields are const) +4. THANDLE_ASSIGN(PTR, NULL) so that memory is freed (the THANDLE memory and the original "destroy" +*/ +TEST_FUNCTION(thandle_int_works_with_both_declare_and_define_in_this_file_for_const) +{ + ///arrange + LogInfo("1. allocate on the heap some structure that needs a special \"destroy\" function."); + A_S_CONST_PTR a_s = malloc(sizeof(A_S_CONST)); + ASSERT_IS_NOT_NULL(a_s); + /*cast the const away... just for a tiny momnet*/ + *(int*)& a_s->a = 42; + *(char**)&a_s->s = sprintf_char("%s", "3333333333333333333333_here_some_string_3333333333333333333333"); + ASSERT_IS_NOT_NULL(a_s->s); + + ///act + LogInfo("2. move the pointer to the structure into a THANDLE(PTR())"); + THANDLE(PTR(A_S_CONST_PTR)) one = THANDLE_PTR_CREATE_WITH_MOVE(A_S_CONST_PTR)(a_s, dispose_const); + + ///assert + LogInfo("3. verify that the pointer in the THANDLE is the same as the original pointer"); + ASSERT_IS_NOT_NULL(one); + ASSERT_ARE_EQUAL(void_ptr, a_s, one->pointer); + + ///assert + LogInfo("3.1 verify that the pointer has the same qualifiers as the original pointer (in this case fields are const)"); + /*one->pointer->a = 3;*/ /*error C2166: l-value specifies const object*/ + /*one->pointer->s[0] = 'a';*/ /*error C2166: l-value specifies const object*/ + + /*ENTER C GENERICS - with a shout out to "compatible types..." Note: it seems that "const int" and "int" are "compatible", however, int* and const int* are not.*/ + ASSERT_ARE_EQUAL(int, 1, _Generic(&one->pointer->a, const int *: 1, int*: 2, default : 0)); + ASSERT_ARE_EQUAL(int, 1, _Generic(&(one->pointer->s), const char** : 1, default: 0)); + + ///cleanup + LogInfo("4. THANDLE_ASSIGN(PTR, NULL) so that memory is freed (the THANDLE memory and the original \"destroy\""); + THANDLE_ASSIGN(PTR(A_S_CONST_PTR))(&one, NULL); +} + +TEST_FUNCTION(thandle_int_works_with_both_declare_and_define_in_different_files) +{ + ///arrange + EXAMPLE_COMPLETE_PTR p = malloc(sizeof(EXAMPLE_COMPLETE)); + ASSERT_IS_NOT_NULL(p); + p->example_complete= 2; + + ///act + THANDLE(PTR(EXAMPLE_COMPLETE_PTR)) example_complete = THANDLE_PTR_CREATE_WITH_MOVE(EXAMPLE_COMPLETE_PTR)(p, dispose_example_complete); + + ///assert + ASSERT_IS_NOT_NULL(example_complete); + ASSERT_ARE_EQUAL(void_ptr, p, example_complete->pointer); + ASSERT_ARE_EQUAL(int, 2, example_complete->pointer->example_complete); + + ///cleanup + THANDLE_ASSIGN(PTR(EXAMPLE_COMPLETE_PTR))(&example_complete, NULL); + +} + +TEST_FUNCTION(thandle_int_works_with_incomplete_types) +{ + ///arrange + EXAMPLE_INCOMPLETE_PTR p = create_example_incomplete(); + ASSERT_IS_NOT_NULL(p); + + ///act + THANDLE(PTR(EXAMPLE_INCOMPLETE_PTR)) example_incomplete = THANDLE_PTR_CREATE_WITH_MOVE(EXAMPLE_INCOMPLETE_PTR)(p, dispose_example_incomplete); + + ///assert + ASSERT_IS_NOT_NULL(example_incomplete); + ASSERT_ARE_EQUAL(void_ptr, p, example_incomplete->pointer); + + ///cleanup + THANDLE_ASSIGN(PTR(EXAMPLE_INCOMPLETE_PTR))(&example_incomplete, NULL); + +} + +END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) + diff --git a/common/tests/thandle_ptr_ut/CMakeLists.txt b/common/tests/thandle_ptr_ut/CMakeLists.txt new file mode 100644 index 0000000..b9e037d --- /dev/null +++ b/common/tests/thandle_ptr_ut/CMakeLists.txt @@ -0,0 +1,18 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +set(theseTestsName thandle_ptr_ut) + +set(${theseTestsName}_test_files +${theseTestsName}.c +) + +set(${theseTestsName}_c_files +) + +set(${theseTestsName}_h_files + ../../inc/c_pal/thandle_ptr.h +) + +build_test_artifacts(${theseTestsName} "tests/c_pal" ADDITIONAL_LIBS c_pal c_pal_reals) + diff --git a/common/tests/thandle_ptr_ut/thandle_ptr_ut.c b/common/tests/thandle_ptr_ut/thandle_ptr_ut.c new file mode 100644 index 0000000..0b4e37c --- /dev/null +++ b/common/tests/thandle_ptr_ut/thandle_ptr_ut.c @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +#include "macro_utils/macro_utils.h" // IWYU pragma: keep + +#include "testrunnerswitcher.h" + +#include "c_pal/thandle_ll.h" // for THANDLE, THANDLE_ASSIGN + +#define ENABLE_MOCKS +#include "umock_c/umock_c.h" +#include "c_pal/gballoc_hl.h" +#include "c_pal/gballoc_hl_redirect.h" + +#undef ENABLE_MOCKS + +#include "real_gballoc_hl.h" + +#include "c_pal/thandle_ptr.h" + +MU_DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) + +static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) +{ + ASSERT_FAIL("umock_c reported error :%" PRI_MU_ENUM "", MU_ENUM_VALUE(UMOCK_C_ERROR_CODE, error_code)); +} + + +typedef struct UNDER_TEST_TAG +{ + int a; +}UNDER_TEST; + +typedef UNDER_TEST* UNDER_TEST_PTR; + +THANDLE_PTR_DECLARE(UNDER_TEST_PTR); +THANDLE_PTR_DEFINE(UNDER_TEST_PTR); + + +BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) + +TEST_SUITE_INITIALIZE(it_does_something) +{ + ASSERT_ARE_EQUAL(int, 0, real_gballoc_hl_init(NULL, NULL)); + + umock_c_init(on_umock_c_error); + + REGISTER_GBALLOC_HL_GLOBAL_MOCK_HOOK(); +} + +TEST_SUITE_CLEANUP(TestClassCleanup) +{ + umock_c_deinit(); + + real_gballoc_hl_deinit(); +} + +TEST_FUNCTION_INITIALIZE(f) +{ + umock_c_reset_all_calls(); +} + +TEST_FUNCTION_CLEANUP(cleans) +{ +} + +static void just_call_free(UNDER_TEST_PTR p) +{ + free(p); +} + +/*Tests_SRS_THANDLE_PTR_02_001: [ THANDLE_PTR_CREATE_WITH_MOVE(T) shall return what THANDLE_CREATE_FROM_CONTENT(PTR(T))(THANDLE_PTR_DISPOSE(T)) returns. ]*/ +TEST_FUNCTION(THANDLE_PTR_CREATE_WITH_MOVE_happy_path) +{ + ///arrange + UNDER_TEST_PTR p = real_gballoc_hl_malloc(sizeof(UNDER_TEST)); + ASSERT_IS_NOT_NULL(p); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, IGNORED_ARG)); /*this is THANDLE_CREATE_FROM_CONTENT...*/ + + ///act + THANDLE(PTR(UNDER_TEST_PTR)) t = THANDLE_PTR_CREATE_WITH_MOVE(UNDER_TEST_PTR)(p, just_call_free); + + ///assert + ASSERT_IS_NOT_NULL(t); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///clean + THANDLE_ASSIGN(PTR(UNDER_TEST_PTR))(&t, NULL); + +} + +/*Tests_SRS_THANDLE_PTR_02_001: [ THANDLE_PTR_CREATE_WITH_MOVE(T) shall return what THANDLE_CREATE_FROM_CONTENT(PTR(T))(THANDLE_PTR_DISPOSE(T)) returns. ]*/ +TEST_FUNCTION(THANDLE_PTR_CREATE_WITH_MOVE_unhappy_path) +{ + ///arrange + UNDER_TEST_PTR p = real_gballoc_hl_malloc(sizeof(UNDER_TEST)); + ASSERT_IS_NOT_NULL(p); + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, IGNORED_ARG)) /*this is THANDLE_CREATE_FROM_CONTENT...*/ + .SetReturn(NULL); + + ///act + THANDLE(PTR(UNDER_TEST_PTR)) t = THANDLE_PTR_CREATE_WITH_MOVE(UNDER_TEST_PTR)(p, just_call_free); + + ///assert + ASSERT_IS_NULL(t); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + ///clean + just_call_free(p); +} + +/*Tests_SRS_THANDLE_PTR_02_002: [ If the original dispose is non-NULL then THANDLE_PTR_DISPOSE(T) shall call dispose. ]*/ +TEST_FUNCTION(THANDLE_PTR_DISPOSE_call_dispose) +{ + ///arrange + UNDER_TEST_PTR p = real_gballoc_hl_malloc(sizeof(UNDER_TEST)); + ASSERT_IS_NOT_NULL(p); + + THANDLE(PTR(UNDER_TEST_PTR)) t = THANDLE_PTR_CREATE_WITH_MOVE(UNDER_TEST_PTR)(p, just_call_free); + ASSERT_IS_NOT_NULL(t); + + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(free(p)); /*this is freeing "p"*/ + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); /*this is freeing the THANDLE storage space*/ + + ///act + THANDLE_ASSIGN(PTR(UNDER_TEST_PTR))(&t, NULL); + + ///assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); /*this is "shall return"... */ +} + +/*Tests_SRS_THANDLE_PTR_02_003: [ THANDLE_PTR_DISPOSE(T) shall return. ]*/ +TEST_FUNCTION(THANDLE_PTR_DISPOSE_NULL_does_not_call_anything) +{ + ///arrange + UNDER_TEST local = { .a = 42 }; + UNDER_TEST_PTR p = &local; /*note: no function needed to be called to dispose of local*/ + ASSERT_IS_NOT_NULL(p); + + THANDLE(PTR(UNDER_TEST_PTR)) t = THANDLE_PTR_CREATE_WITH_MOVE(UNDER_TEST_PTR)(p, NULL); + ASSERT_IS_NOT_NULL(t); + + umock_c_reset_all_calls(); + + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); /*this is freeing the THANDLE storage space*/ + + ///act + THANDLE_ASSIGN(PTR(UNDER_TEST_PTR))(&t, NULL); + + ///assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); /*this is "shall return"... */ +} + +END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) +