diff --git a/.gitignore b/.gitignore index 892f7465..07797d81 100644 --- a/.gitignore +++ b/.gitignore @@ -210,6 +210,7 @@ typings/** .cmake/** *build/** build_nodejs/** +build_libuv/** # ignore Atom Editor files .atom-build.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 080bcd3a..5267c6f6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,8 +76,8 @@ option(run_as_a_service "Flags that we have the goal of running gateway as a ser option(enable_dotnet_binding "set enable_dotnet_binding to ON to build dotnet binding host binaries (default is OFF)" OFF) option(enable_dotnet_core_binding "set enable_dotnet_core_binding to ON to build dotnet core binding host binaries (default is OFF)" OFF) option(enable_ble_module "set enable_ble_module to OFF to remove ble module from gateway build (default is ON)" ON) -option(enable_native_remote_modules "Build the infrastructure required to support native remote modules" ON) -option(enable_java_remote_modules "Build the infrastructure required to support java remote modules" OFF) +option(enable_native_remote_modules "Build the infrastructure required to support native remote modules (default is ON)" ON) +option(enable_java_remote_modules "Build the infrastructure required to support java remote modules (default is OFF)" OFF) option(use_amqp "set use_amqp to ON if amqp is to be used, set to OFF to not use amqp" ON) option(use_http "set use_http to ON if http is to be used, set to OFF to not use http" ON) option(use_mqtt "set use_mqtt to ON if mqtt is to be used, set to OFF to not use mqtt" ON) diff --git a/bindings/dotnetcore/src/adapters/dotnetcore_utils_linux.cpp b/bindings/dotnetcore/src/adapters/dotnetcore_utils_linux.cpp index b5f245f9..abebe447 100644 --- a/bindings/dotnetcore/src/adapters/dotnetcore_utils_linux.cpp +++ b/bindings/dotnetcore/src/adapters/dotnetcore_utils_linux.cpp @@ -34,7 +34,7 @@ void AddFilesFromDirectoryToTpaList(const std::string& directory, std::string& t // Walk the directory for each extension separately so that we first get files with .ni.dll extension, // then files with .dll extension, etc. - for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++) + for (size_t extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++) { const char* ext = tpaExtensions[extIndex]; int extLength = strlen(ext); @@ -100,11 +100,11 @@ void AddFilesFromDirectoryToTpaList(const std::string& directory, std::string& t tpaList.append(":"); } } - + // Rewind the directory stream to be able to iterate over it for the next extension rewinddir(dir); } - + closedir(dir); } @@ -115,9 +115,9 @@ bool initializeDotNetCoreCLR(coreclr_initialize_ptr coreclrInitialize_ptr, const char executableFullPath[PATH_MAX] = {0}; char the_p_path[PATH_MAX]; - + //Ignoring failures of these calls. coreclrInitialize_ptr will fail if folder is not right. - ssize_t size = readlink("/proc/self/exe", executableFullPath, sizeof(executableFullPath)); + (void)readlink("/proc/self/exe", executableFullPath, sizeof(executableFullPath)); getcwd(the_p_path, 255); strcat(the_p_path, "/"); @@ -168,4 +168,4 @@ bool initializeDotNetCoreCLR(coreclr_initialize_ptr coreclrInitialize_ptr, const } return returnResult; -} \ No newline at end of file +} diff --git a/bindings/dotnetcore/src/dotnetcore.cpp b/bindings/dotnetcore/src/dotnetcore.cpp index e4ef06f8..a4081009 100644 --- a/bindings/dotnetcore/src/dotnetcore.cpp +++ b/bindings/dotnetcore/src/dotnetcore.cpp @@ -311,6 +311,7 @@ static void* DotNetCore_ParseConfigurationFromJson(const char* configuration) void DotNetCore_FreeConfiguration(void* configuration) { + (void)configuration; //Nothing to be freed here. } diff --git a/bindings/dotnetcore/tests/dotnetcore_e2e/dotnetcore_e2e.cpp b/bindings/dotnetcore/tests/dotnetcore_e2e/dotnetcore_e2e.cpp index 1ed05bbf..a62c2c25 100644 --- a/bindings/dotnetcore/tests/dotnetcore_e2e/dotnetcore_e2e.cpp +++ b/bindings/dotnetcore/tests/dotnetcore_e2e/dotnetcore_e2e.cpp @@ -31,21 +31,27 @@ static bool messageReplyReceived = false; static void* E2EModule_ParseConfigurationFromJson(const char* configuration) { - return (void*)0x4242; + (void)configuration; + return (void*)0x4242; } static void E2EModule_FreeConfiguration(void* configuration) { - return; + (void)configuration; + return; } static MODULE_HANDLE E2EModule_Create(BROKER_HANDLE broker, const void* configuration) { + (void)broker; + (void)configuration; return (MODULE_HANDLE)0x4242; } static void E2EModule_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE messageHandle) { + (void)moduleHandle; + (void)messageHandle; if (messageHandle != NULL) { CONSTMAP_HANDLE properties = Message_GetProperties(messageHandle); @@ -66,16 +72,17 @@ static void E2EModule_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE message ConstMap_Destroy(properties); } } + static void E2EModule_Destroy(MODULE_HANDLE module) { - + (void)module; } + static const MODULE_API_1 E2E_APIS_all = { {MODULE_API_VERSION_1}, - - E2EModule_ParseConfigurationFromJson, - E2EModule_FreeConfiguration, + E2EModule_ParseConfigurationFromJson, + E2EModule_FreeConfiguration, E2EModule_Create, E2EModule_Destroy, E2EModule_Receive, @@ -84,21 +91,30 @@ static const MODULE_API_1 E2E_APIS_all = MODULE_EXPORT const MODULE_API* Module_GetApi(const struct MODULE_LOADER_TAG* loader, MODULE_API_VERSION gateway_api_version) { + (void)loader; + (void)gateway_api_version; return reinterpret_cast(&E2E_APIS_all); } MODULE_LIBRARY_HANDLE E2E_Loader_Load(const struct MODULE_LOADER_TAG* loader, const void * config) { + (void)loader; + (void)config; return (MODULE_LIBRARY_HANDLE)&E2E_APIS_all; } void E2E_Loader_Unload(const struct MODULE_LOADER_TAG* loader, MODULE_LIBRARY_HANDLE handle) { + (void)loader; + (void)handle; return; } const MODULE_API* E2E_Loader_GetApi(const struct MODULE_LOADER_TAG* loader, MODULE_LIBRARY_HANDLE handle) { + (void)loader; + (void)handle; + const MODULE_API* result; if (handle != NULL) { @@ -113,12 +129,16 @@ const MODULE_API* E2E_Loader_GetApi(const struct MODULE_LOADER_TAG* loader, MODU void* E2E_Loader_BuildModuleConfiguration(const struct MODULE_LOADER_TAG* loader, const void* entrypoint, const void* module_configuration) { - return NULL; + (void)loader; + (void)entrypoint; + (void)module_configuration; + return NULL; } void E2E_Loader_FreeModuleConfiguration(const struct MODULE_LOADER_TAG* loader, const void* module_configuration) { - + (void)loader; + (void)module_configuration; } // E2E static module loader does not need any JSON parsing. @@ -223,12 +243,6 @@ TEST_FUNCTION(GW_dotnetcore_binding_e2e_Managed2Managed) &receiverEndpoint }; - const DOTNET_CORE_HOST_CONFIG receiverConfig{ - "E2ETestModule", - "E2ETestModule.DotNetE2ETestModule", - "Receiver" - }; - modulesEntryArray[1] = { nameModuleReceiver, loaders[1], @@ -271,7 +285,7 @@ TEST_FUNCTION(GW_dotnetcore_binding_e2e_Managed2Managed) nameProbeModule }; - ///act + ///act GATEWAY_PROPERTIES properties; properties.gateway_modules = VECTOR_create(sizeof(GATEWAY_MODULES_ENTRY)); properties.gateway_links = VECTOR_create(sizeof(GATEWAY_LINK_ENTRY)); diff --git a/bindings/dotnetcore/tests/dotnetcore_ut/dotnetcore_ut.cpp b/bindings/dotnetcore/tests/dotnetcore_ut/dotnetcore_ut.cpp index 9641e7a8..6a11a1b9 100644 --- a/bindings/dotnetcore/tests/dotnetcore_ut/dotnetcore_ut.cpp +++ b/bindings/dotnetcore/tests/dotnetcore_ut/dotnetcore_ut.cpp @@ -73,8 +73,14 @@ static bool calledReceiveMethod = false; static bool calledDestroyMethod = false; static bool calledStartMethod = false; - int DOTNET_CORE_CALLING_CONVENTION fakeGatewayCreateMethod(intptr_t broker, intptr_t module, const char* assemblyName, const char* entryType, const char* gatewayConfiguration) +int DOTNET_CORE_CALLING_CONVENTION fakeGatewayCreateMethod(intptr_t broker, intptr_t module, const char* assemblyName, const char* entryType, const char* gatewayConfiguration) { + (void)broker; + (void)module; + (void)assemblyName; + (void)entryType; + (void)gatewayConfiguration; + calledCreateMethod = true; return __LINE__; @@ -82,16 +88,24 @@ static bool calledStartMethod = false; void DOTNET_CORE_CALLING_CONVENTION fakeGatewayReceiveMethod(unsigned char* buffer, int32_t bufferSize, unsigned int moduleIdManaged) { + (void)buffer; + (void)bufferSize; + (void)moduleIdManaged; + calledReceiveMethod = true; }; void DOTNET_CORE_CALLING_CONVENTION fakeGatewayDestroyMethod(unsigned int moduleIdManaged) { + (void)moduleIdManaged; + calledDestroyMethod = true; }; void DOTNET_CORE_CALLING_CONVENTION fakeGatewayStartMethod(unsigned int moduleIdManaged) { + (void)moduleIdManaged; + calledStartMethod = true; } @@ -103,6 +117,11 @@ int DOTNET_CORE_CALLING_CONVENTION fake_create_delegate(void* hostHandle, const char* entryPointMethodName, void** delegate) { + (void)hostHandle; + (void)domainId; + (void)entryPointAssemblyName; + (void)entryPointTypeName; + int returnStatus; //assign delegates. @@ -124,7 +143,7 @@ int DOTNET_CORE_CALLING_CONVENTION fake_create_delegate(void* hostHandle, } - //Define return status. + //Define return status. if (failCreateDelegate == true && strcmp(entryPointMethodName, "Create") == 0) { returnStatus = -1; @@ -148,9 +167,11 @@ int DOTNET_CORE_CALLING_CONVENTION fake_create_delegate(void* hostHandle, int DOTNET_CORE_CALLING_CONVENTION fake_shutdownclr( - void* hostHandle, + void * hostHandle, unsigned int domainId) { + (void)hostHandle; + (void)domainId; return 0; }; @@ -243,7 +264,6 @@ TYPED_MOCK_CLASS(CDOTNETCOREMocks, CGlobalMock) MOCK_STATIC_METHOD_3(, int32_t, Message_ToByteArray, MESSAGE_HANDLE, messageHandle, unsigned char*, buf, int32_t , size) MOCK_METHOD_END( int32_t, (int32_t)11); - MOCK_STATIC_METHOD_2(, MESSAGE_HANDLE, Message_CreateFromByteArray, const unsigned char*, source, int32_t, size) MOCK_METHOD_END(MESSAGE_HANDLE, (MESSAGE_HANDLE)0x42); @@ -302,7 +322,7 @@ TYPED_MOCK_CLASS(CDOTNETCOREMocks, CGlobalMock) MOCK_STATIC_METHOD_2(, JSON_Object*, json_array_get_object, const JSON_Array*, arr, size_t, index) JSON_Object* object = NULL; - if (arr != NULL && index >= 0) + if (arr != NULL) { object = (JSON_Object*)0x42; } @@ -1391,4 +1411,4 @@ BEGIN_TEST_SUITE(dotnetcore_ut) } -END_TEST_SUITE(dotnetcore_ut) \ No newline at end of file +END_TEST_SUITE(dotnetcore_ut) diff --git a/bindings/java/tests/host_manager_ut/host_manager_ut.c b/bindings/java/tests/host_manager_ut/host_manager_ut.c index 6177e6a7..98aaaf49 100644 --- a/bindings/java/tests/host_manager_ut/host_manager_ut.c +++ b/bindings/java/tests/host_manager_ut/host_manager_ut.c @@ -26,8 +26,10 @@ //Globals //============================================================================= +#ifdef WIN32 + static TEST_MUTEX_HANDLE g_dllByDll; +#endif static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; static bool malloc_will_fail = false; diff --git a/bindings/java/tests/host_ut/host_ut.c b/bindings/java/tests/host_ut/host_ut.c index de42648d..cdd5a39a 100644 --- a/bindings/java/tests/host_ut/host_ut.c +++ b/bindings/java/tests/host_ut/host_ut.c @@ -82,8 +82,10 @@ typedef signed char jbyte; //Globals //============================================================================= +#ifdef WIN32 + static TEST_MUTEX_HANDLE g_dllByDll; +#endif static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; static bool malloc_will_fail = false; diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5f8dd9a4..566676f5 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -23,7 +23,7 @@ if(WIN32) include_directories(${GW_INC}/windows ) elseif(UNIX) # LINUX or APPLE include_directories(${GW_INC}/linux) -endif() +endif() #setting the dynamic_loader file based on OS that it is used if(WIN32) @@ -32,6 +32,31 @@ elseif(UNIX) # LINUX or APPLE set(dynamic_library_c_file ./adapters/dynamic_library_linux.c ./adapters/gb_library_linux.c ) endif() +# If using the appropriate build script based on OS that it is used +if (${enable_native_remote_modules} OR ${enable_java_remote_modules}) + if(WIN32) + if(8 EQUAL CMAKE_SIZEOF_VOID_P) + MESSAGE(STATUS "Building 64-bit LibUV for Windows...") + execute_process ( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/ + COMMAND build_libuv.cmd x64 + ) + else() + MESSAGE(STATUS "Building 32-bit LibUV for Windows...") + execute_process ( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/ + COMMAND build_libuv.cmd + ) + endif() + elseif(UNIX) + MESSAGE(STATUS "Building LibUV for Unix...") + execute_process ( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/ + COMMAND ./build_libuv.sh + ) + endif() +endif() + #setting specific libraries to be loaded based on OS (for example, Linux needs "-ldl", windows does not) if(UNIX) # LINUX or APPLE set(dynamic_loader_library dl) @@ -156,7 +181,7 @@ if(${enable_nodejs_binding}) include_directories(../bindings/nodejs/inc) endif() -if(${enable_native_remote_modules}) +if(${enable_native_remote_modules} OR ${enable_java_remote_modules}) set(gateway_c_sources ${gateway_c_sources} ../proxy/message/src/control_message.c @@ -175,6 +200,8 @@ if(${enable_native_remote_modules}) include_directories( ../proxy/outprocess/inc) include_directories( ../proxy/message/inc) + include_directories(${CMAKE_SOURCE_DIR}/build_libuv/dist/include) + link_directories(${CMAKE_SOURCE_DIR}/build_libuv/dist/lib) endif() set(module_host_sources @@ -207,21 +234,24 @@ add_library(gateway_static STATIC ${gateway_h_sources} ) -target_link_libraries(module_host_static parson nanomsg aziotsharedutil ${dynamic_loader_library}) - -if(WIN32) - target_link_libraries(gateway parson nanomsg aziotsharedutil ${dynamic_loader_library}) - - target_link_libraries(gateway_static parson nanomsg aziotsharedutil ${dynamic_loader_library}) -else() - target_link_libraries(gateway parson nanomsg aziotsharedutil ${dynamic_loader_library}) - target_link_libraries(gateway ${NN_REQUIRED_LIBRARIES}) - target_link_libraries(gateway m) +if(${enable_native_remote_modules} OR ${enable_java_remote_modules}) + if(WIN32) + target_link_libraries(gateway ${CMAKE_SOURCE_DIR}/build_libuv/dist/lib/libuv.lib Psapi.lib Iphlpapi.lib Userenv.lib) + target_link_libraries(gateway_static ${CMAKE_SOURCE_DIR}/build_libuv/dist/lib/libuv.lib Psapi.lib Iphlpapi.lib Userenv.lib) + target_link_libraries(module_host_static ${CMAKE_SOURCE_DIR}/build_libuv/dist/lib/libuv.lib Psapi.lib Iphlpapi.lib Userenv.lib) + else() + target_link_libraries(gateway ${CMAKE_SOURCE_DIR}/build_libuv/dist/lib/libuv.a) + target_link_libraries(gateway_static ${CMAKE_SOURCE_DIR}/build_libuv/dist/lib/libuv.a) + target_link_libraries(module_host_static ${CMAKE_SOURCE_DIR}/build_libuv/dist/lib/libuv.a) + endif() +endif() - target_link_libraries(gateway_static parson nanomsg aziotsharedutil ${dynamic_loader_library}) - target_link_libraries(gateway_static ${NN_REQUIRED_LIBRARIES}) - target_link_libraries(gateway_static m) +target_link_libraries(gateway parson nanomsg aziotsharedutil ${dynamic_loader_library}) +target_link_libraries(gateway_static parson nanomsg aziotsharedutil ${dynamic_loader_library}) +target_link_libraries(module_host_static parson nanomsg aziotsharedutil ${dynamic_loader_library}) +if(NOT WIN32) + target_link_libraries(gateway m ${NN_REQUIRED_LIBRARIES}) target_link_libraries(module_host_static m ${NN_REQUIRED_LIBRARIES}) endif() @@ -253,7 +283,7 @@ if(${run_unittests}) endif() ############################################################# -########################INSTALL STUFF######################## +####################### INSTALL STUFF ####################### ############################################################# #installs the gateway libs and headers diff --git a/core/devdoc/gateway_requirements.md b/core/devdoc/gateway_requirements.md index 1c531c2d..6b5eca30 100644 --- a/core/devdoc/gateway_requirements.md +++ b/core/devdoc/gateway_requirements.md @@ -115,6 +115,8 @@ Gateway_Create creates a new gateway using information from the `GATEWAY_PROPERT **SRS_GATEWAY_17_017: [** This function shall destroy the default module loaders upon any failure. **]** +**SRS_GATEWAY_27_027: [** *Launch* - This function shall join any spawned threads upon any failure. **]** + **SRS_GATEWAY_14_003: [** This function shall create a new `BROKER_HANDLE` for the gateway representing this gateway's message broker. **]** **SRS_GATEWAY_14_004: [** This function shall return `NULL` if a `BROKER_HANDLE` cannot be created. **]** @@ -184,6 +186,8 @@ Gateway_Destroy destroys a gateway represented by the `gw` parameter. **SRS_GATEWAY_14_037: [** If `GATEWAY_HANDLE_DATA`'s message broker cannot remove a module, the function shall log the error and continue removing modules from the `GATEWAY_HANDLE`. **]** +**SRS_GATEWAY_27_040: [** *Launch* - `Gateway_Destroy` shall join any spawned threads. **]** + **SRS_GATEWAY_14_006: [** The function shall destroy the `GATEWAY_HANDLE_DATA`'s `broker` `BROKER_HANDLE`. **]** **SRS_GATEWAY_17_019: [** The function shall destroy the module loader list. **]** diff --git a/core/devdoc/outprocess_loader_requirements.md b/core/devdoc/outprocess_loader_requirements.md deleted file mode 100644 index 39f6d000..00000000 --- a/core/devdoc/outprocess_loader_requirements.md +++ /dev/null @@ -1,204 +0,0 @@ -Out of Process Module Loader Requirements -========================================= - -Overview --------- - -The out of process (outprocess) module loader implements loading of gateway modules that are proxies for modules hosted out of process. - -## References -[Module loader design](./module_loaders.md) -[Out of Process Module HLD](./outprocess_hld.md) -[Out of Process Module requirements](./outprocess_module_requirements.md) - -## Exposed API -```C - -#define OUTPROCESS_LOADER_ACTIVATION_TYPE_VALUES \ - OUTPROCESS_LOADER_ACTIVATION_NONE - -/** - * @brief Enumeration listing all supported module loaders - */ -DEFINE_ENUM(OUTPROCESS_LOADER_ACTIVATION_TYPE, OUTPROCESS_LOADER_ACTIVATION_TYPE_VALUES); - -/** @brief Structure to load an out of process proxy module */ -typedef struct OUTPROCESS_LOADER_ENTRYPOINT_TAG -{ - /** - * @brief Tells the module and loader how much control the gateway - * has over the module host process. - */ - OUTPROCESS_LOADER_ACTIVATION_TYPE activation_type; - /** @brief The URI for the module host control channel.*/ - STRING_HANDLE control_id; - /** @brief The URI for the gateway message channel.*/ - STRING_HANDLE message_id; - /** @brief controls timeout for ipc retries. */ - unsigned int default_wait; -} OUTPROCESS_LOADER_ENTRYPOINT; - -/** @brief The API for the out of process proxy module loader. */ -MOCKABLE_FUNCTION(, GATEWAY_EXPORT const MODULE_LOADER*, OutprocessLoader_Get); - -``` - -OutprocessModuleLoader_Load ---------------------------- -```C -MODULE_LIBRARY_HANDLE OutprocessModuleLoader_Load(const MODULE_LOADER* loader, const void* entrypoint) -``` - -Loads the outprocess module according to the `entrypoint` given. The outprocess module is statically linked in the gateway library, so there is no shared library to load. - -**SRS_OUTPROCESS_LOADER_17_001: [** If `loader` or `entrypoint` are `NULL`, then this function shall return `NULL`. **]** - -**SRS_OUTPROCESS_LOADER_17_042: [** If the loader type is not `OUTPROCESS`, then this function shall return `NULL`. **]** - -**SRS_OUTPROCESS_LOADER_17_002: [** If the entrypoint's `control_id` is `NULL`, then this function shall return `NULL`. **]** -At Load time, it is permitted for `message_id` to be `NULL`. - -**SRS_OUTPROCESS_LOADER_17_003: [** If the entrypoint's `activation_type` is not NONE, then this function shall return `NULL`. **]** - -**SRS_OUTPROCESS_LOADER_17_004: [** The loader shall allocate memory for the loader handle. **]** - -**SRS_OUTPROCESS_LOADER_17_006: [** The loader shall store a pointer to the `MODULE_API` in the loader handle. **]** - -**SRS_OUTPROCESS_LOADER_17_007: [** Upon success, this function shall return a valid pointer to the loader handle. **]** - -**SRS_OUTPROCESS_LOADER_17_008: [** If any call in this function fails, this function shall return `NULL`. **]** - -OutprocessModuleLoader_GetModuleApi ------------------------------------ -```C -extern const MODULE_API* `OutprocessModuleLoader_GetModuleApi`(const MODULE_LOADER* loader, MODULE_LIBRARY_HANDLE moduleLibraryHandle); -``` - -The out of process module is linked with the outprocess loader. - -**SRS_OUTPROCESS_LOADER_17_009: [** This function shall return a valid pointer to the `MODULE_API` MODULE_API. **]** - -OutprocessModuleLoader_Unload ------------------------------ -```C -void OutprocessModuleLoader_Unload(const MODULE_LOADER* loader, MODULE_LIBRARY_HANDLE moduleLibraryHandle); -``` - -**SRS_OUTPROCESS_LOADER_17_010: [** This function shall do nothing if `moduleLibraryHandle` is `NULL`. **]** - -**SRS_OUTPROCESS_LOADER_17_011: [** This function shall release all resources created by this loader. **]** - -OutprocessModuleLoader_ParseEntrypointFromJson ----------------------------------------------- -```C -void* OutprocessModuleLoader_ParseEntrypointFromJson(const struct MODULE_LOADER_TAG* loader, const JSON_Value* json); -``` - -Parses entrypoint JSON as it applies to out of process modules and returns a -pointer to the parsed data. - -**SRS_OUTPROCESS_LOADER_17_012: [** This function shall return `NULL` if `json` is `NULL`. **]** - -**SRS_OUTPROCESS_LOADER_17_013: [** This function shall return `NULL` if `activation.type` is not present in `json`. **]** - -**SRS_OUTPROCESS_LOADER_17_014: [** This function shall return `NULL` if `activation.type` is not `NONE`. **]** - - -**SRS_OUTPROCESS_LOADER_17_041: [** This function shall return `NULL` if `control.id` is not present in `json`. **]** - -**SRS_OUTPROCESS_LOADER_17_016: [** This function shall allocate a `OUTPROCESS_LOADER_ENTRYPOINT` structure. **]** - -**SRS_OUTPROCESS_LOADER_17_043: [** This function shall read the `timeout` value. **]** - -**SRS_OUTPROCESS_LOADER_17_044: [** If `timeout` is set, the `default_wait` shall be set to this value, else it will be set to a default of 1000 ms. **]** - -This timeout controls how long a module will wait before retrying to connect to remote module on startup. If remote module is expected to take a long time to start, setting this will reduce the number of retires before success. - -**SRS_OUTPROCESS_LOADER_17_017: [** This function shall assign the entrypoint activation_type to NONE. **]** - -**SRS_OUTPROCESS_LOADER_17_018: [** This function shall assign the entrypoint `control_id` to the string value of "ipc://" + "control.id" in `json`. **]** - -**SRS_OUTPROCESS_LOADER_17_019: [** This function shall assign the entrypoint `message_id` to the string value of "ipc://" + "message.id" in `json`, `NULL` if not present. **]** - -**SRS_OUTPROCESS_LOADER_17_021: [** This function shall return `NULL` if any calls fails. **]** - -**SRS_OUTPROCESS_LOADER_17_022: [** This function shall return a valid pointer to an `OUTPROCESS_LOADER_ENTRYPOINT` on success. **]** - -OutprocessModuleLoader_FreeEntrypoint -------------------------------------- -```C -void OutprocessModuleLoader_FreeEntrypoint(const struct MODULE_LOADER_TAG* loader, void* entrypoint) -``` - -**SRS_OUTPROCESS_LOADER_17_023: [** This function shall release all resources allocated by `OutprocessModuleLoader_ParseEntrypointFromJson`. **]** - -OutprocessModuleLoader_ParseConfigurationFromJson -------------------------------------------------- -```C -MODULE_LOADER_BASE_CONFIGURATION* OutprocessModuleLoader_ParseConfigurationFromJson(const struct MODULE_LOADER_TAG* loader, const JSON_Value* json); -``` - -**SRS_OUTPROCESS_LOADER_17_024: [** The out of process loader does not have any configuration. So this method shall return `NULL`. **]** - - - -OutprocessModuleLoader_FreeConfiguration ----------------------------------------- -```C -void OutprocessModuleLoader_FreeConfiguration(const struct MODULE_LOADER_TAG* loader, MODULE_LOADER_BASE_CONFIGURATION* configuration); -``` - -The out of process loader does not have any configuration. So there is nothing to free here. - -**SRS_OUTPROCESS_LOADER_17_025: [** This function shall move along, nothing to free here. **]** - - -OutprocessModuleLoader_BuildModuleConfiguration ------------------------------------------------ -```C -void* OutprocessModuleLoader_BuildModuleConfiguration( - const MODULE_LOADER* loader, - const void* entrypoint, - const void* module_configuration -); -``` - -**SRS_OUTPROCESS_LOADER_17_026: [** This function shall return `NULL` if `entrypoint`, `control_id`, or `module_configuration` is `NULL`. **]** Note: If there is no module configuration, this function expects the configuration will be the string "null". - -**SRS_OUTPROCESS_LOADER_17_027: [** This function shall allocate a `OUTPROCESS_MODULE_CONFIG` structure. **]** - -**SRS_OUTPROCESS_LOADER_17_029: [** If the entrypoint's `message_id` is `NULL`, then the loader shall construct an IPC uri. **]** - -**SRS_OUTPROCESS_LOADER_17_030: [** The loader shall create a unique id, if needed for URI constrution. **]** - -**SRS_OUTPROCESS_LOADER_17_032: [** The message uri shall be composed of "ipc://" + unique id. **]** - -**SRS_OUTPROCESS_LOADER_17_033: [** This function shall allocate and copy each string in `OUTPROCESS_LOADER_ENTRYPOINT` and assign them to the corresponding fields in `OUTPROCESS_MODULE_CONFIG`. **]** - -**SRS_OUTPROCESS_LOADER_17_034: [** This function shall allocate and copy the `module_configuration` string and assign it the `OUTPROCESS_MODULE_CONFIG::outprocess_module_args` field. **]** - -**SRS_OUTPROCESS_LOADER_17_035: [** Upon success, this function shall return a valid pointer to an `OUTPROCESS_MODULE_CONFIG` structure. **]** - -**SRS_OUTPROCESS_LOADER_17_036: [** If any call fails, this function shall return `NULL`. **]** - - -OutprocessModuleLoader_FreeModuleConfiguration ----------------------------------------------- -```C -void OutprocessModuleLoader_FreeModuleConfiguration(const struct MODULE_LOADER_TAG* loader, const void* module_configuration); -``` - -**SRS_OUTPROCESS_LOADER_17_037: [** This function shall release all memory allocated by `OutprocessModuleLoader_BuildModuleConfiguration`. **]** - - -OutprocessModuleLoader_Get --------------------------- -```C -const MODULE_LOADER* OutprocessModuleLoader_Get(void); -``` - -**SRS_OUTPROCESS_LOADER_17_038: [** `OutprocessModuleLoader_Get` shall return a non-`NULL` pointer to a `MODULE_LOADER` struct. **]** - -**SRS_OUTPROCESS_LOADER_17_039: [** `MODULE_LOADER::type` shall be `OUTPROCESS`. **]** - -**SRS_OUTPROCESS_LOADER_17_040: [** `MODULE_LOADER::name` shall be the string 'outprocess'. **]** diff --git a/core/src/control_message.c b/core/src/control_message.c deleted file mode 100755 index 93d7431d..00000000 --- a/core/src/control_message.c +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - - -#include "control_message.h" - -#include - -#include "azure_c_shared_utility/gballoc.h" -#include "azure_c_shared_utility/xlogging.h" - -#include "message.h" - -DEFINE_ENUM_STRINGS(CONTROL_MESSAGE_TYPE, CONTROL_MESSAGE_TYPE_VALUES); - -#define FIRST_MESSAGE_BYTE 0xA1 /*0xA1 comes from (A)zure (I)oT*/ -#define SECOND_MESSAGE_BYTE 0x6C /*0x6C comes from (G)ateway (C)ontrol */ -#define BASE_MESSAGE_SIZE 8 -#define BASE_CREATE_SIZE (BASE_MESSAGE_SIZE+10) -#define BASE_CREATE_REPLY_SIZE (BASE_MESSAGE_SIZE+1) - -static int parse_uint32_t(const unsigned char* source, size_t sourceSize, size_t position, int32_t *parsed, uint32_t* value) -{ - int result; - if (position + 4 > sourceSize) - { - /*Codes_SRS_CONTROL_MESSAGE_17_018: [ Reading past the end of the byte array shall cause this function to fail and return NULL. ]*/ - LogError("unable to parse a int32_t because it would go past the end of the source"); - result = __LINE__; - } - else - { - *parsed = 4; - *value = - (source[position + 0] << 24) | - (source[position + 1] << 16) | - (source[position + 2] << 8) | - (source[position + 3]); - result = 0; - } - return result; -} - -static int parse_memory_chunk(const unsigned char* source, size_t sourceSize, size_t position, int32_t *parsed, uint32_t *size, unsigned char** value) -{ - int result; - uint32_t chunk_size; - int32_t current_parsed; - result = parse_uint32_t(source, sourceSize, position, ¤t_parsed, &chunk_size); - if (result == 0) - { - position += current_parsed; - if (position + chunk_size > sourceSize) - { - /*Codes_SRS_CONTROL_MESSAGE_17_018: [ Reading past the end of the byte array shall cause this function to fail and return NULL. ]*/ - LogError("unable to extract memory because it would go past end of source"); - result = __LINE__; - } - else - { - if (chunk_size == 0) - { - *value = NULL; - *parsed = current_parsed; - *size = chunk_size; - result = 0; - } - else - { - /* allocate 1 more for null-termination */ - *value = (unsigned char *)malloc(chunk_size); - if (*value == NULL) - { - LogError("unable to allocate memory chunk"); - result = __LINE__; - } - else - { - memcpy(*value, source + position, chunk_size); - *parsed = current_parsed + chunk_size; - *size = chunk_size; - result = 0; - } - } - } - } - return result; -} - -static void init_create_message_contents(CONTROL_MESSAGE_MODULE_CREATE * create_msg) -{ - create_msg->gateway_message_version = 0x00; - create_msg->uri.uri_type = CONTROL_MESSAGE_TYPE_ERROR; - create_msg->uri.uri_size = 0; - create_msg->uri.uri = NULL; - create_msg->args_size = 0; - create_msg->args = NULL; -} - -static void free_create_message_contents(CONTROL_MESSAGE_MODULE_CREATE * create_msg) -{ - if (create_msg->uri.uri != NULL) - { - free(create_msg->uri.uri); - } - if (create_msg->args != NULL) - { - free(create_msg->args); - } -} - -int parse_create_message(const unsigned char* source, size_t sourceSize, size_t position, int32_t *parsed, CONTROL_MESSAGE_MODULE_CREATE * create_msg) -{ - (void)parsed; - int result; - int32_t current_parsed; /*reused in all parsings*/ - - init_create_message_contents(create_msg); /* initialize to NULL for easy cleanup */ - /* - * Parse the number of uris - * We already know we won't extend past the end of the array - - * it was checked before entering this function - */ - - if (position + 2 > sourceSize) - { - LogError("couldn't parse message uri"); - result = __LINE__; - } - else - { - create_msg->gateway_message_version = (uint8_t)source[position++]; - /*Codes_SRS_CONTROL_MESSAGE_17_012: [ This function shall read the uri_type, uri_size, and the uri. ]*/ - create_msg->uri.uri_type = (uint8_t)source[position++]; - - /*Codes_SRS_CONTROL_MESSAGE_17_013: [ This function shall allocate uri_size bytes for the url. ]*/ - if (parse_memory_chunk(source, - sourceSize, - position, - ¤t_parsed, - &(create_msg->uri.uri_size), - (unsigned char**)(&(create_msg->uri.uri))) != 0) - { - LogError("unable to parse a uri"); - result = __LINE__; - } - else - { - position += current_parsed; - result = 0; - } - } - if (result != 0) - { - /* Did not come out of parsing the URLs OK */ - /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ - free_create_message_contents(create_msg); - } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_014: [ This function shall read the args_size and args from the byte stream. ]*/ - /*Codes_SRS_CONTROL_MESSAGE_17_015: [ This function shall allocate args_size + 1 bytes for the args and will null terminate the memory block. ]*/ - if (parse_memory_chunk(source, - sourceSize, - position, - ¤t_parsed, - &(create_msg->args_size), - (unsigned char**)(&(create_msg->args))) != 0) - { - LogError("unable to parse a module args"); - result = __LINE__; - /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ - free_create_message_contents(create_msg); - } - else - { - result = 0; - } - } - return result; -} - -CONTROL_MESSAGE * ControlMessage_CreateFromByteArray(const unsigned char* source, size_t size) -{ - CONTROL_MESSAGE * result; - /*Codes_SRS_CONTROL_MESSAGE_17_001: [ If source is NULL, then this function shall return NULL. ]*/ - /*Codes_SRS_CONTROL_MESSAGE_17_002: [ If size is smaller than 8, then this function shall return NULL. ]*/ - if ( - (source == NULL) || - (size < BASE_MESSAGE_SIZE) - ) - { - LogError("invalid parameter source=[%p] size=%z" , source, size); - result = NULL; - } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_003: [ If the first two bytes of source are not 0xA1 0x6C then this function shall fail and return NULL. ]*/ - /*Codes_SRS_CONTROL_MESSAGE_17_004: [ If the version is not equal to CONTROL_MESSAGE_VERSION_CURRENT, then this function shall return NULL. ]*/ - if ( - (source[0] != FIRST_MESSAGE_BYTE) || - (source[1] != SECOND_MESSAGE_BYTE) || - ((uint8_t)source[2] != CONTROL_MESSAGE_VERSION_CURRENT) - ) - { - LogError("byte array is not a control message serialization"); - result = NULL; - } - else - { - size_t currentPosition = 2; /*current position is always the first character that "we are about to look at"*/ - int32_t parsed; /*reused in all parsings*/ - /*Codes_SRS_CONTROL_MESSAGE_17_005: [ This function shall read the version, type and size from the byte stream. ]*/ - uint8_t messageVersion = (uint8_t)source[currentPosition++]; - uint8_t messageType = (uint8_t)source[currentPosition++]; - uint32_t messageSize; - /* we already know buffer is at least BASE_MESSAGE_SIZE - this will always return OK */ - (void)parse_uint32_t(source, size, currentPosition, &parsed, &messageSize); - currentPosition += parsed; - /*Codes_SRS_CONTROL_MESSAGE_17_006: [ If the size embedded in the message is not the same as size parameter then this function shall fail and return NULL. ]*/ - if (messageSize != size) - { - LogError("message size is inconsistent"); - result = NULL; - } - else - { - if (messageType == CONTROL_MESSAGE_TYPE_MODULE_CREATE) - { - /*Codes_SRS_CONTROL_MESSAGE_17_009: [ If the total message size is not at least 20 bytes, then this function shall return NULL ]*/ - if (size < BASE_CREATE_SIZE) - { - result = NULL; - } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_008: [ This function shall allocate a CONTROL_MESSAGE_MODULE_CREATE structure. ]*/ - result = (CONTROL_MESSAGE *)malloc(sizeof(CONTROL_MESSAGE_MODULE_CREATE)); - if (result != NULL) - { - /*Codes_SRS_CONTROL_MESSAGE_17_024: [ Upon valid reading of the byte stream, this function shall assign the message version and type into the CONTROL_MESSAGE base structure. ]*/ - result->version = messageVersion; - result->type = messageType; - if (parse_create_message( - source, - size, - currentPosition, - &parsed, - (CONTROL_MESSAGE_MODULE_CREATE*)result) != 0) - { - /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ - free(result); - result = NULL; - } - } - } - } - else if (messageType == CONTROL_MESSAGE_TYPE_MODULE_REPLY) - { - /*Codes_SRS_CONTROL_MESSAGE_17_020: [ If the total message size is not at least 9 bytes, then this function shall fail and return NULL. ]*/ - if (size < BASE_CREATE_REPLY_SIZE) - { - result = NULL; - } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_019: [ This function shall allocate a CONTROL_MESSAGE_MODULE_REPLY structure. ]*/ - result = (CONTROL_MESSAGE *)malloc(sizeof(CONTROL_MESSAGE_MODULE_REPLY)); - if (result != NULL) - { - /*Codes_SRS_CONTROL_MESSAGE_17_024: [ Upon valid reading of the byte stream, this function shall assign the message version and type into the CONTROL_MESSAGE base structure. ]*/ - result->version = messageVersion; - result->type = messageType; - /*Codes_SRS_CONTROL_MESSAGE_17_021: [ This function shall read the status from the byte stream. ]*/ - ((CONTROL_MESSAGE_MODULE_REPLY*)result)->status = - (uint8_t)source[currentPosition]; - } - } - } - else if ( - (messageType == CONTROL_MESSAGE_TYPE_MODULE_START) || - (messageType == CONTROL_MESSAGE_TYPE_MODULE_DESTROY) - ) - { - /*Codes_SRS_CONTROL_MESSAGE_17_023: [ This function shall allocate a CONTROL_MESSAGE structure. ]*/ - result = (CONTROL_MESSAGE *)malloc(sizeof(CONTROL_MESSAGE)); - if (result != NULL) - { - /*Codes_SRS_CONTROL_MESSAGE_17_024: [ Upon valid reading of the byte stream, this function shall assign the message version and type into the CONTROL_MESSAGE base structure. ]*/ - result->version = messageVersion; - result->type = messageType; - } - } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_007: [ This function shall return NULL if the type is not a valid enum value of CONTROL_MESSAGE_TYPE or CONTROL_MESSAGE_TYPE_ERROR. ]*/ - LogError("message is an unrecognized type: %s", ENUM_TO_STRING(CONTROL_MESSAGE_TYPE, messageType)); - result = NULL; - } - } - } - } - /*Codes_SRS_CONTROL_MESSAGE_17_025: [ Upon success, this function shall return a valid pointer to the CONTROL_MESSAGE base. ]*/ - /*Codes_SRS_CONTROL_MESSAGE_17_036: [ This function shall return NULL upon failure. ]*/ - return result; -} - -void ControlMessage_Destroy(CONTROL_MESSAGE * message) -{ - /*Codes_SRS_CONTROL_MESSAGE_17_026: [ If message is NULL this function shall do nothing. ]*/ - if (message != NULL) - { - /*Codes_SRS_CONTROL_MESSAGE_17_027: [ This function shall release all memory allocated in this structure. ]*/ - if (message->type == CONTROL_MESSAGE_TYPE_MODULE_CREATE) - { - /*Codes_SRS_CONTROL_MESSAGE_17_028: [ For a CONTROL_MESSAGE_MODULE_CREATE message type, all memory allocated shall be defined as all the memory allocated by ControlMessage_CreateFromByteArray. ]*/ - free_create_message_contents((CONTROL_MESSAGE_MODULE_CREATE *)message); - } - /*Codes_SRS_CONTROL_MESSAGE_17_030: [ This function shall release message for all message types. ]*/ - free(message); - } -} - -static int32_t create_message_get_size(CONTROL_MESSAGE * message) -{ - int32_t result; - - CONTROL_MESSAGE_MODULE_CREATE * create_msg = (CONTROL_MESSAGE_MODULE_CREATE*)message; - result = - 1 /* gateway_message_version */ - + 1 /* uri_type */ - + 4 /* uri_size */ - + 4; /* args_size */ - if (create_msg->uri.uri != NULL) - { - result += - (int32_t)strlen(create_msg->uri.uri) - + 1; /* for null char */ - } - if (create_msg->args != NULL) - { - result += - (int32_t)strlen(create_msg->args) - + 1; /* for null char */ - } - - return result; -} - -static void create_message_serialize(CONTROL_MESSAGE_MODULE_CREATE * create_msg, unsigned char* buf, size_t currentPosition) -{ - buf[currentPosition++] = (create_msg->gateway_message_version); - buf[currentPosition++] = create_msg->uri.uri_type; - buf[currentPosition++] = (create_msg->uri.uri_size) >> 24; - buf[currentPosition++] = ((create_msg->uri.uri_size) >> 16) & 0xFF; - buf[currentPosition++] = ((create_msg->uri.uri_size) >> 8) & 0xFF; - buf[currentPosition++] = (create_msg->uri.uri_size) & 0xFF; - if (create_msg->uri.uri_size > 0) - { - memcpy(buf + currentPosition, create_msg->uri.uri, create_msg->uri.uri_size); - currentPosition += create_msg->uri.uri_size; - } - buf[currentPosition++] = (create_msg->args_size) >> 24; - buf[currentPosition++] = ((create_msg->args_size) >> 16) & 0xFF; - buf[currentPosition++] = ((create_msg->args_size) >> 8) & 0xFF; - buf[currentPosition++] = (create_msg->args_size) & 0xFF; - if (create_msg->args_size > 0) - { - memcpy(buf + currentPosition, create_msg->args, create_msg->args_size); - currentPosition += create_msg->args_size; - } -} - - -int32_t ControlMessage_ToByteArray(CONTROL_MESSAGE * message, unsigned char* buf, int32_t size) -{ - int32_t result; - if (message == NULL) - { - /*Codes_SRS_CONTROL_MESSAGE_17_031: [ If message is NULL, then this function shall return -1. ]*/ - LogError("invalid (NULL) message parameter detected buffer=[%p], size=[%d]", message, size); - result = -1; - } - else if ( - (buf == NULL) && - (size != 0) - ) - { - /*Codes_SRS_CONTROL_MESSAGE_17_032: [ If buf is NULL, but size is not zero, then this function shall return -1. ]*/ - LogError("Null buffer sent with a specific size buffer=[%p], size=[%d]", message, size); - result = -1; - } - else - { - //MESSAGE_HANDLE_DATA* messageData = (MESSAGE_HANDLE_DATA*)message; - /*Codes_SRS_CONTROL_MESSAGE_17_033: [ This function shall populate the memory with values as indicated in control messages in out process modules. ]*/ - size_t byteArraySize = - + 2 /* header */ - + 1 /* message type*/ - + 1 /* message version */ - + 4 /* total size */ - + 0 /*an unknown at this moment number of bytes for message content*/ - ; - if (message->type == CONTROL_MESSAGE_TYPE_MODULE_CREATE) - { - byteArraySize += create_message_get_size(message); - result = 0; - } - else if (message->type == CONTROL_MESSAGE_TYPE_MODULE_REPLY) - { - result = 0; - byteArraySize += 1; /* status */ - } - else if ( - (message->type == CONTROL_MESSAGE_TYPE_MODULE_START) || - (message->type == CONTROL_MESSAGE_TYPE_MODULE_DESTROY) - ) - { - /* no additional fields */ - result =0; - } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_034: [ If any of the above steps fails then this function shall fail and return -1. ]*/ - LogError("message is an unrecognized type: %s", ENUM_TO_STRING(CONTROL_MESSAGE_TYPE, message->type)); - result = -1; - } - - if (result >=0 ) - { - if (size == 0) - { - result = byteArraySize; - } - else if (byteArraySize > (size_t)size) - { - /*Codes_SRS_CONTROL_MESSAGE_17_034: [ If any of the above steps fails then this function shall fail and return -1. ]*/ - LogError("message is %u bytes, won't fit in buffer of %u bytes", byteArraySize, size); - result = -1; - } - else - { - size_t currentPosition; /*always points to the byte we are about to write*/ - /*a header formed of the following hex characters in this order: 0xA1 0x60*/ - buf[0] = FIRST_MESSAGE_BYTE; - buf[1] = SECOND_MESSAGE_BYTE; - /*Codes_SRS_CONTROL_MESSAGE_17_037: [ This function shall read the gateway_message_version. ]*/ - buf[2] = message->version; - buf[3] = (uint8_t)message->type; - - /*4 bytes in MSB order representing the total size of the byte array. */ - buf[4] = byteArraySize >> 24; - buf[5] = (byteArraySize >> 16) & 0xFF; - buf[6] = (byteArraySize >> 8) & 0xFF; - buf[7] = (byteArraySize) & 0xFF; - - // - currentPosition = 8; - if (message->type == CONTROL_MESSAGE_TYPE_MODULE_CREATE) - { - create_message_serialize((CONTROL_MESSAGE_MODULE_CREATE *)message, buf, currentPosition); - } - else if (message->type == CONTROL_MESSAGE_TYPE_MODULE_REPLY) - { - CONTROL_MESSAGE_MODULE_REPLY * reply_msg = - (CONTROL_MESSAGE_MODULE_REPLY*)message; - buf[currentPosition++] = (reply_msg->status); - } - /*Codes_SRS_CONTROL_MESSAGE_17_035: [ Upon success this function shall return the byte array size.*/ - result = byteArraySize; - } - } - } - return result; -} - diff --git a/core/src/gateway.c b/core/src/gateway.c index c6f785ff..7cb44943 100644 --- a/core/src/gateway.c +++ b/core/src/gateway.c @@ -161,6 +161,7 @@ GATEWAY_HANDLE Gateway_Create(const GATEWAY_PROPERTIES* properties) result = gateway_create_internal(properties, false); if (result == NULL) { + /* Codes_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ] */ /* Codes_SRS_GATEWAY_17_017: [ This function shall destroy the default module loaders upon any failure. ]*/ ModuleLoader_Destroy(); } diff --git a/core/src/gateway_internal.c b/core/src/gateway_internal.c index aba66b58..3369dba3 100644 --- a/core/src/gateway_internal.c +++ b/core/src/gateway_internal.c @@ -12,6 +12,9 @@ #include "experimental/event_system.h" #include "broker.h" #include "module_access.h" +#ifdef OUTPROCESS_ENABLED + #include "module_loaders/outprocess_loader.h" +#endif #include "gateway_internal.h" @@ -310,6 +313,10 @@ void gateway_destroy_internal(GATEWAY_HANDLE gw) } VECTOR_destroy(gateway_handle->modules); +#ifdef OUTPROCESS_ENABLED + /*Codes_SRS_GATEWAY_27_040: [ Launch - `Gateway_Destroy` shall join any spawned threads. ]*/ + OutprocessLoader_JoinChildProcesses(); +#endif } if (gateway_handle->broker != NULL) diff --git a/core/src/module_loaders/dotnet_core_loader.c b/core/src/module_loaders/dotnet_core_loader.c index e9f27557..01b5861e 100644 --- a/core/src/module_loaders/dotnet_core_loader.c +++ b/core/src/module_loaders/dotnet_core_loader.c @@ -30,8 +30,6 @@ static DOTNET_CORE_LOADER_CONFIGURATION* create_default_config(); static int set_default_paths(DOTNET_CORE_CLR_OPTIONS* options); -static DYNAMIC_LIBRARY_HANDLE hCoreCLRModule; - static DYNAMIC_LIBRARY_HANDLE DotnetCoreModuleLoader_LoadBindingModule(const MODULE_LOADER* loader) { // find the binding path from the loader configuration; if there isn't one, use @@ -685,4 +683,4 @@ static DOTNET_CORE_LOADER_CONFIGURATION* create_default_config() } } return result; -} \ No newline at end of file +} diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index 6337c952..459e9ce1 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -28,7 +28,6 @@ if(${enable_nodejs_binding}) endif() if (${enable_native_remote_modules}) - add_subdirectory(control_msg_ut) add_subdirectory(outprocess_loader_ut) add_subdirectory(outprocess_module_ut) endif() diff --git a/core/tests/dotnet_core_loader_ut/dotnet_core_loader_ut.c b/core/tests/dotnet_core_loader_ut/dotnet_core_loader_ut.c index 1d211d91..75b3ee5a 100644 --- a/core/tests/dotnet_core_loader_ut/dotnet_core_loader_ut.c +++ b/core/tests/dotnet_core_loader_ut/dotnet_core_loader_ut.c @@ -116,13 +116,16 @@ MOCKABLE_FUNCTION(, JSON_Value_Type, json_value_get_type, const JSON_Value*, val //Globals //============================================================================= +#ifdef WIN32 + static TEST_MUTEX_HANDLE g_dllByDll; +#endif static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; static const MODULE_LOADER* g_loader; static MODULE_LOADER_BASE_CONFIGURATION* g_config; void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { + (void)error_code; ASSERT_FAIL("umock_c reported error"); } @@ -173,7 +176,7 @@ MOCK_FUNCTION_END(arr) MOCK_FUNCTION_WITH_CODE(, JSON_Value*, json_array_get_value, const JSON_Array*, arr, size_t, index) JSON_Value* val = NULL; - if (arr != NULL && index >= 0) + if (arr != NULL) { val = (JSON_Value*)0x42; } @@ -190,9 +193,6 @@ MOCK_FUNCTION_END(val) #undef ENABLE_MOCKS -TEST_DEFINE_ENUM_TYPE(MODULE_LOADER_RESULT, MODULE_LOADER_RESULT_VALUES); -IMPLEMENT_UMOCK_C_ENUM_TYPE(MODULE_LOADER_RESULT, MODULE_LOADER_RESULT_VALUES); - TEST_DEFINE_ENUM_TYPE(MODULE_LOADER_TYPE, MODULE_LOADER_TYPE_VALUES); IMPLEMENT_UMOCK_C_ENUM_TYPE(MODULE_LOADER_TYPE, MODULE_LOADER_TYPE_VALUES); diff --git a/core/tests/dynamic_loader_ut/dynamic_loader_ut.c b/core/tests/dynamic_loader_ut/dynamic_loader_ut.c index 9dfce269..9aadb142 100644 --- a/core/tests/dynamic_loader_ut/dynamic_loader_ut.c +++ b/core/tests/dynamic_loader_ut/dynamic_loader_ut.c @@ -87,8 +87,10 @@ MOCKABLE_FUNCTION(, JSON_Value_Type, json_value_get_type, const JSON_Value*, val //Globals //============================================================================= -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { diff --git a/core/tests/gateway_createfromjson_ut/gateway_createfromjson_ut.cpp b/core/tests/gateway_createfromjson_ut/gateway_createfromjson_ut.cpp index efabd096..1c6c72b1 100644 --- a/core/tests/gateway_createfromjson_ut/gateway_createfromjson_ut.cpp +++ b/core/tests/gateway_createfromjson_ut/gateway_createfromjson_ut.cpp @@ -16,6 +16,9 @@ #include #include "azure_c_shared_utility/vector_types_internal.h" +#ifdef OUTPROCESS_ENABLED + #include "module_loaders/outprocess_loader.h" +#endif #define DUMMY_JSON_PATH "x.json" #define MISCONFIG_JSON_PATH "invalid_json.json" @@ -270,6 +273,11 @@ TYPED_MOCK_CLASS(CGatewayMocks, CGlobalMock) MOCK_STATIC_METHOD_1(, MODULE_LOADER*, ModuleLoader_FindByName, const char*, name) MOCK_METHOD_END(MODULE_LOADER*, &dummyModuleLoader); + MOCK_STATIC_METHOD_0(, void, OutprocessLoader_JoinChildProcesses); + MOCK_VOID_METHOD_END(); + + MOCK_STATIC_METHOD_0(, int, OutprocessLoader_SpawnChildProcesses); + MOCK_METHOD_END(int, 0); /*EventSystem Mocks*/ MOCK_STATIC_METHOD_0(, EVENTSYSTEM_HANDLE, EventSystem_Init) @@ -401,7 +409,8 @@ DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayMocks, , MODULE_LOADER_RESULT, ModuleLoader DECLARE_GLOBAL_MOCK_METHOD_1(CGatewayMocks, , MODULE_LOADER_RESULT, ModuleLoader_InitializeFromJson, const JSON_Value*, loaders); DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayMocks, , void, ModuleLoader_Destroy); DECLARE_GLOBAL_MOCK_METHOD_1(CGatewayMocks, , MODULE_LOADER*, ModuleLoader_FindByName, const char*, name); - +DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayMocks, , void, OutprocessLoader_JoinChildProcesses); +DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayMocks, , int, OutprocessLoader_SpawnChildProcesses); DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayMocks, , EVENTSYSTEM_HANDLE, EventSystem_Init); DECLARE_GLOBAL_MOCK_METHOD_4(CGatewayMocks, , void, EventSystem_AddEventCallback, EVENTSYSTEM_HANDLE, event_system, GATEWAY_EVENT, event_type, GATEWAY_CALLBACK, callback, void*, user_param); @@ -1050,6 +1059,9 @@ TEST_FUNCTION(Gateway_Create_Start_fails_returns_null) .IgnoreArgument(1) .IgnoreArgument(2); STRICT_EXPECTED_CALL(mocks, ModuleLoader_Destroy()); +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif //Act GATEWAY_HANDLE gateway = Gateway_CreateFromJson(VALID_JSON_PATH); @@ -1684,6 +1696,9 @@ TEST_FUNCTION(Gateway_CreateFromJson_fails_with_no_entry_point) STRICT_EXPECTED_CALL(mocks, json_value_free(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, ModuleLoader_Destroy()); +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif //Act diff --git a/core/tests/gateway_ut/gateway_ut.cpp b/core/tests/gateway_ut/gateway_ut.cpp index ce2bb836..2f6984ac 100644 --- a/core/tests/gateway_ut/gateway_ut.cpp +++ b/core/tests/gateway_ut/gateway_ut.cpp @@ -15,7 +15,9 @@ #include "module_loader.h" #include "azure_c_shared_utility/vector_types_internal.h" - +#ifdef OUTPROCESS_ENABLED + #include "module_loaders/outprocess_loader.h" +#endif #define DUMMY_LIBRARY_PATH "x.dll" @@ -217,6 +219,12 @@ TYPED_MOCK_CLASS(CGatewayLLMocks, CGlobalMock) MOCK_STATIC_METHOD_0(, void, ModuleLoader_Destroy); MOCK_VOID_METHOD_END(); + MOCK_STATIC_METHOD_0(, void, OutprocessLoader_JoinChildProcesses); + MOCK_VOID_METHOD_END(); + + MOCK_STATIC_METHOD_0(, int, OutprocessLoader_SpawnChildProcesses); + MOCK_METHOD_END(int, 0); + MOCK_STATIC_METHOD_0(, EVENTSYSTEM_HANDLE, EventSystem_Init) MOCK_METHOD_END(EVENTSYSTEM_HANDLE, (EVENTSYSTEM_HANDLE)BASEIMPLEMENTATION::gballoc_malloc(1)); @@ -351,6 +359,8 @@ DECLARE_GLOBAL_MOCK_METHOD_3(CGatewayLLMocks, , void*, DynamicModuleLoader_Build DECLARE_GLOBAL_MOCK_METHOD_2(CGatewayLLMocks, , void, DynamicModuleLoader_FreeModuleConfiguration, const struct MODULE_LOADER_TAG*, loader, const void*, module_configuration); DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayLLMocks, , MODULE_LOADER_RESULT, ModuleLoader_Initialize); DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayLLMocks, , void, ModuleLoader_Destroy); +DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayLLMocks, , void, OutprocessLoader_JoinChildProcesses); +DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayLLMocks, , int, OutprocessLoader_SpawnChildProcesses); DECLARE_GLOBAL_MOCK_METHOD_0(CGatewayLLMocks, , EVENTSYSTEM_HANDLE, EventSystem_Init); DECLARE_GLOBAL_MOCK_METHOD_4(CGatewayLLMocks, , void, EventSystem_AddEventCallback, EVENTSYSTEM_HANDLE, event_system, GATEWAY_EVENT, event_type, GATEWAY_CALLBACK, callback, void*, user_param); @@ -562,6 +572,7 @@ TEST_FUNCTION(Gateway_Create_Creates_Handle_Success) /*Tests_SRS_GATEWAY_14_011: [ If gw, entry, or GATEWAY_MODULES_ENTRY's loader_configuration or loader_api is NULL the function shall return NULL. ]*/ /*Tests_SRS_GATEWAY_17_017: [ This function shall destroy the default module loaders upon any failure. ]*/ +/*Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ]*/ TEST_FUNCTION(Gateway_Create_returns_null_on_bad_module_api_entry) { //Arrange @@ -613,6 +624,9 @@ TEST_FUNCTION(Gateway_Create_returns_null_on_bad_module_api_entry) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -738,6 +752,7 @@ TEST_FUNCTION(Gateway_Create_VECTOR_Create_Fails) } /*Codes_SRS_GATEWAY_14_002: [ This function shall return NULL upon any failure. ] */ +/*Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ]*/ TEST_FUNCTION(Gateway_Create_VECTOR_Create2_Fails) { //Arrange @@ -762,6 +777,9 @@ TEST_FUNCTION(Gateway_Create_VECTOR_Create2_Fails) STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -779,6 +797,7 @@ TEST_FUNCTION(Gateway_Create_VECTOR_Create2_Fails) } /*Codes_SRS_GATEWAY_14_002: [ This function shall return NULL upon any failure. ] */ +/*Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ]*/ TEST_FUNCTION(Gateway_Create_VECTOR_push_back_Fails_To_Add_All_Modules_In_Props) { //Arrange @@ -912,6 +931,9 @@ TEST_FUNCTION(Gateway_Create_VECTOR_push_back_Fails_To_Add_All_Modules_In_Props) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -929,6 +951,7 @@ TEST_FUNCTION(Gateway_Create_VECTOR_push_back_Fails_To_Add_All_Modules_In_Props) } /*Tests_SRS_GATEWAY_14_036: [ If any MODULE_HANDLE is unable to be created from a GATEWAY_MODULES_ENTRY the GATEWAY_HANDLE will be destroyed. ]*/ +/*Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ]*/ TEST_FUNCTION(Gateway_Create_Broker_AddModule_Fails_To_Add_All_Modules_In_Props) { //Arrange @@ -1050,6 +1073,9 @@ TEST_FUNCTION(Gateway_Create_Broker_AddModule_Fails_To_Add_All_Modules_In_Props) .IgnoreArgument(1); //Modules STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); //Links +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -1069,6 +1095,7 @@ TEST_FUNCTION(Gateway_Create_Broker_AddModule_Fails_To_Add_All_Modules_In_Props) /*Tests_SRS_GATEWAY_04_004: [If a module with the same module_name already exists, this function shall fail and the GATEWAY_HANDLE will be destroyed.]*/ /*Tests_SRS_GATEWAY_17_020: [ The function shall clean up any constructed resources. ]*/ +/*Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ]*/ TEST_FUNCTION(Gateway_Create_AddModule_WithDuplicatedModuleName_Fails) { //Arrange @@ -1165,6 +1192,9 @@ TEST_FUNCTION(Gateway_Create_AddModule_WithDuplicatedModuleName_Fails) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -1443,6 +1473,7 @@ TEST_FUNCTION(Gateway_Create_Adds_All_Modules_And_All_Links_In_Props_Success) } /*Tests_SRS_GATEWAY_04_003: [If any GATEWAY_LINK_ENTRY is unable to be added to the broker the GATEWAY_HANDLE will be destroyed.]*/ +/*Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ]*/ TEST_FUNCTION(Gateway_Create_Adds_All_Modules_And_Links_fromNonExistingModule_Fail) { //Arrange @@ -1541,6 +1572,9 @@ TEST_FUNCTION(Gateway_Create_Adds_All_Modules_And_Links_fromNonExistingModule_Fa .IgnoreArgument(1); //Modules STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); //Links +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -1573,6 +1607,7 @@ TEST_FUNCTION(Gateway_Destroy_destroys_loader_If_NULL) } /*Tests_SRS_GATEWAY_14_037: [ If GATEWAY_HANDLE_DATA's message broker cannot remove a module, the function shall log the error and continue removing modules from the GATEWAY_HANDLE. ]*/ +/* Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ] */ TEST_FUNCTION(Gateway_Destroy_Continues_Unloading_If_Broker_RemoveModule_Fails) { //Arrange @@ -1649,6 +1684,9 @@ TEST_FUNCTION(Gateway_Destroy_Continues_Unloading_If_Broker_RemoveModule_Fails) .IgnoreArgument(1); //Modules STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); //links +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -1666,6 +1704,7 @@ TEST_FUNCTION(Gateway_Destroy_Continues_Unloading_If_Broker_RemoveModule_Fails) /*Tests_SRS_GATEWAY_14_006: [ The function shall destroy the GATEWAY_HANDLE_DATA's broker BROKER_HANDLE. ]*/ /*Tests_SRS_GATEWAY_04_014: [ The function shall remove each link in GATEWAY_HANDLE_DATA's links vector and destroy GATEWAY_HANDLE_DATA's link. ]*/ /*Tests_SRS_GATEWAY_17_019: [ The function shall destroy the module loader list. ]*/ +/*Tests_SRS_GATEWAY_27_040: [ Launch - `Gateway_Destroy` shall join any spawned threads. ]*/ TEST_FUNCTION(Gateway_Destroy_Removes_All_Modules_And_Destroys_Vector_Success) { //Arrange @@ -1759,6 +1798,9 @@ TEST_FUNCTION(Gateway_Destroy_Removes_All_Modules_And_Destroys_Vector_Success) .IgnoreArgument(1); //Modules. STRICT_EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); //Links +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif STRICT_EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)) .IgnoreArgument(1); STRICT_EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)) @@ -2759,6 +2801,7 @@ TEST_FUNCTION(Gateway_Event_System_Create_And_Report) } /* Tests_SRS_GATEWAY_26_002: [ If Event System module fails to be initialized the gateway module shall be destroyed and NULL returned with no events reported. ] */ +/* Tests_SRS_GATEWAY_27_027: [ Launch - This function shall join any spawned threads upon any failure. ] */ TEST_FUNCTION(Gateway_Event_System_Create_Fail) { // Arrange @@ -2780,6 +2823,9 @@ TEST_FUNCTION(Gateway_Event_System_Create_Fail) EXPECTED_CALL(mocks, VECTOR_size(IGNORED_PTR_ARG)); //For Links. EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)); //Modules EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)); //Links +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)); EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)); @@ -2793,6 +2839,7 @@ TEST_FUNCTION(Gateway_Event_System_Create_Fail) /* Tests_SRS_GATEWAY_26_003: [ If the Event System module is initialized, this function shall report GATEWAY_DESTROYED event. ] */ /* Tests_SRS_GATEWAY_26_004: [ This function shall destroy the attached Event System. ] */ +/* Tests_SRS_GATEWAY_27_040: [ Launch - This function shall join any spawned threads. ] */ TEST_FUNCTION(Gateway_Event_System_Report_Destroy) { // Arrange @@ -2807,6 +2854,9 @@ TEST_FUNCTION(Gateway_Event_System_Report_Destroy) EXPECTED_CALL(mocks, VECTOR_size(IGNORED_PTR_ARG)); //For Links EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)); //Modules EXPECTED_CALL(mocks, VECTOR_destroy(IGNORED_PTR_ARG)); //Links +#ifdef OUTPROCESS_ENABLED + EXPECTED_CALL(mocks, OutprocessLoader_JoinChildProcesses()); +#endif EXPECTED_CALL(mocks, Broker_Destroy(IGNORED_PTR_ARG)); EXPECTED_CALL(mocks, gballoc_free(IGNORED_PTR_ARG)); EXPECTED_CALL(mocks, ModuleLoader_Destroy()); diff --git a/core/tests/gwmessage_ut/gwmessage_ut.c b/core/tests/gwmessage_ut/gwmessage_ut.c index 053b9b55..0a13f214 100644 --- a/core/tests/gwmessage_ut/gwmessage_ut.c +++ b/core/tests/gwmessage_ut/gwmessage_ut.c @@ -15,8 +15,10 @@ #include "azure_c_shared_utility/map.h" #undef ENABLE_MOCKS -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; #include "message.h" diff --git a/core/tests/java_loader_ut/java_loader_ut.c b/core/tests/java_loader_ut/java_loader_ut.c index 64f57297..6b1eab1f 100644 --- a/core/tests/java_loader_ut/java_loader_ut.c +++ b/core/tests/java_loader_ut/java_loader_ut.c @@ -71,8 +71,10 @@ static pfModuleLoader_FreeModuleConfiguration JavaModuleLoader_FreeModuleConfigu //Globals //============================================================================= +#ifdef WIN32 + static TEST_MUTEX_HANDLE g_dllByDll; +#endif static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; static const MODULE_LOADER* g_loader; static MODULE_LOADER_BASE_CONFIGURATION* g_config; @@ -2350,4 +2352,4 @@ TEST_FUNCTION(JavaLoader_Get_failure_tests) umock_c_negative_tests_deinit(); } -END_TEST_SUITE(JavaLoader_UnitTests) \ No newline at end of file +END_TEST_SUITE(JavaLoader_UnitTests) diff --git a/core/tests/message_q_ut/message_q_ut.c b/core/tests/message_q_ut/message_q_ut.c index a34a6b03..2e266536 100644 --- a/core/tests/message_q_ut/message_q_ut.c +++ b/core/tests/message_q_ut/message_q_ut.c @@ -122,8 +122,10 @@ void real_DList_InsertHeadList(PDLIST_ENTRY listHead, PDLIST_ENTRY entry) //Globals //============================================================================= -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { diff --git a/core/tests/module_loader_ut/module_loader_ut.c b/core/tests/module_loader_ut/module_loader_ut.c index fa5bc18f..eb39c488 100644 --- a/core/tests/module_loader_ut/module_loader_ut.c +++ b/core/tests/module_loader_ut/module_loader_ut.c @@ -94,8 +94,10 @@ static const size_t LOADERS_COUNT = sizeof(g_enabled_loaders) / sizeof(g_enabled //Globals //============================================================================= -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { diff --git a/core/tests/node_loader_ut/node_loader_ut.c b/core/tests/node_loader_ut/node_loader_ut.c index 98a4f72b..5b5a0baa 100644 --- a/core/tests/node_loader_ut/node_loader_ut.c +++ b/core/tests/node_loader_ut/node_loader_ut.c @@ -93,8 +93,10 @@ MOCKABLE_FUNCTION(, JSON_Value_Type, json_value_get_type, const JSON_Value*, val //Globals //============================================================================= +#ifdef WIN32 + static TEST_MUTEX_HANDLE g_dllByDll; +#endif static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { diff --git a/core/tests/outprocess_loader_ut/outprocess_loader_ut.c b/core/tests/outprocess_loader_ut/outprocess_loader_ut.c index 71a17807..654f6621 100644 --- a/core/tests/outprocess_loader_ut/outprocess_loader_ut.c +++ b/core/tests/outprocess_loader_ut/outprocess_loader_ut.c @@ -1,10 +1,37 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#include #include #include #include +#include +#define disableNegativeTest(x) (negative_tests_to_skip |= ((uint64_t)1 << (x))) +#define disableNegativeTestsBeforeIndex(x) for (size_t i = (x) ; i > 0 ; disableNegativeTest(--i)) +#define enableNegativeTest(x) (negative_tests_to_skip &= ~((uint64_t)1 << (x))) +#define skipNegativeTest(x) (negative_tests_to_skip & ((uint64_t)1 << (x))) + +#define MOCK_UV_LOOP (uv_loop_t *)0x09171979 +#define MOCK_UV_PROCESS_VECTOR (VECTOR_HANDLE)0x19790917 + +static size_t negative_test_index; +static uint64_t negative_tests_to_skip; + +#define GATEWAY_EXPORT_H +#define GATEWAY_EXPORT + +#include "testrunnerswitcher.h" +#include "umock_c.h" +#include "umock_c_negative_tests.h" +#include "umocktypes_charptr.h" +#include "umocktypes_bool.h" +#include "umocktypes_stdint.h" +#include "real_strings.h" + +static void ** global_ptrs = NULL; +static size_t global_malloc_count = 0; +static bool global_memory = false; static bool malloc_will_fail = false; static size_t malloc_fail_count = 0; static size_t malloc_count = 0; @@ -21,6 +48,12 @@ void* my_gballoc_malloc(size_t size) else { result = malloc(size); + if (global_memory) { + ++global_malloc_count; + global_ptrs = (void **)realloc(global_ptrs, (sizeof(void *) * global_malloc_count)); + ASSERT_IS_NOT_NULL(global_ptrs); + global_ptrs[(global_malloc_count - 1)] = result; + } } return result; @@ -28,38 +61,52 @@ void* my_gballoc_malloc(size_t size) void my_gballoc_free(void* ptr) { + if (global_memory) { + --global_malloc_count; + ptr = global_ptrs[global_malloc_count]; + if (!global_malloc_count) { + free(global_ptrs); + global_ptrs = NULL; + } + } + free(ptr); } -#define GATEWAY_EXPORT_H -#define GATEWAY_EXPORT - -#include "testrunnerswitcher.h" -#include "umock_c.h" -#include "umock_c_negative_tests.h" -#include "umocktypes_charptr.h" -#include "umocktypes_bool.h" -#include "umocktypes_stdint.h" - -#include "real_strings.h" #define ENABLE_MOCKS - - -#include "azure_c_shared_utility/strings.h" #include "azure_c_shared_utility/gballoc.h" -#include "azure_c_shared_utility/urlencode.h" +#include "azure_c_shared_utility/strings.h" +#include "azure_c_shared_utility/threadapi.h" +#include "azure_c_shared_utility/tickcounter.h" #include "azure_c_shared_utility/uniqueid.h" +#include "azure_c_shared_utility/urlencode.h" +#include "azure_c_shared_utility/vector.h" #include "parson.h" +#include "uv.h" #include "module_loader.h" #include "control_message.h" - #undef ENABLE_MOCKS #include "module_loaders/outprocess_loader.h" #include "module_loaders/outprocess_module.h" +static const THREAD_HANDLE MOCK_THREAD_HANDLE = (THREAD_HANDLE *)0x19791709; + +#ifdef __cplusplus + extern "C" { +#endif + +int launch_child_process_from_entrypoint(OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry); +int spawn_child_processes(void * context); +int update_entrypoint_with_launch_object(OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry, const JSON_Object * launch_object); +int validate_launch_arguments(const JSON_Object * launch_object); + +#ifdef __cplusplus + } +#endif + static pfModuleLoader_Load OutprocessModuleLoader_Load = NULL; static pfModuleLoader_Unload OutprocessModuleLoader_Unload = NULL; static pfModuleLoader_GetApi OutprocessModuleLoader_GetModuleApi = NULL; @@ -70,20 +117,41 @@ static pfModuleLoader_FreeConfiguration OutprocessModuleLoader_FreeConfiguration static pfModuleLoader_BuildModuleConfiguration OutprocessModuleLoader_BuildModuleConfiguration = NULL; static pfModuleLoader_FreeModuleConfiguration OutprocessModuleLoader_FreeModuleConfiguration = NULL; +// ** Mocking parson.h MOCKABLE_FUNCTION(, void, json_free_serialized_string, char*, string); +MOCKABLE_FUNCTION(, size_t, json_array_get_count, const JSON_Array*, array); +MOCKABLE_FUNCTION(, const char*, json_array_get_string, const JSON_Array*, array, size_t, size); MOCKABLE_FUNCTION(, char*, json_serialize_to_string, const JSON_Value*, value); +MOCKABLE_FUNCTION(, JSON_Array*, json_object_get_array, const JSON_Object*, object, const char*, name); +MOCKABLE_FUNCTION(, JSON_Object*, json_object_get_object, const JSON_Object*, object, const char*, name); MOCKABLE_FUNCTION(, JSON_Value*, json_object_get_value, const JSON_Object*, object, const char*, name); MOCKABLE_FUNCTION(, const char*, json_object_get_string, const JSON_Object*, object, const char*, name); MOCKABLE_FUNCTION(, double, json_object_get_number, const JSON_Object*, object, const char*, name); MOCKABLE_FUNCTION(, JSON_Object*, json_value_get_object, const JSON_Value *, value); MOCKABLE_FUNCTION(, JSON_Value_Type, json_value_get_type, const JSON_Value*, value); +// ** Mocking uv.h (Process handle) +MOCK_FUNCTION_WITH_CODE(, int, uv_process_kill, uv_process_t *, handle, int, signum) +MOCK_FUNCTION_END(0); +MOCK_FUNCTION_WITH_CODE(, int, uv_spawn, uv_loop_t *, loop, uv_process_t *, handle, const uv_process_options_t *, options) +MOCK_FUNCTION_END(0); + +// ** Mocking uv.h (Event loop) +MOCK_FUNCTION_WITH_CODE(, uv_loop_t *, uv_default_loop); +MOCK_FUNCTION_END(MOCK_UV_LOOP); +MOCK_FUNCTION_WITH_CODE(, int, uv_loop_alive, const uv_loop_t *, loop); +MOCK_FUNCTION_END(0); +MOCK_FUNCTION_WITH_CODE(, int, uv_run, uv_loop_t *, loop, uv_run_mode, mode); +MOCK_FUNCTION_END(0); + //============================================================================= //Globals //============================================================================= -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; const MODULE_API_1 Outprocess_Module_API_all = { { MODULE_API_VERSION_1 }, @@ -107,6 +175,18 @@ MOCK_FUNCTION_END(val) //parson mocks +MOCK_FUNCTION_WITH_CODE(, size_t, json_array_get_count, const JSON_Array*, array) +MOCK_FUNCTION_END(0) + +MOCK_FUNCTION_WITH_CODE(, const char*, json_array_get_string, const JSON_Array*, array, size_t, size) +MOCK_FUNCTION_END(NULL) + +MOCK_FUNCTION_WITH_CODE(, JSON_Array*, json_object_get_array, const JSON_Object*, object, const char*, name) +MOCK_FUNCTION_END(NULL) + +MOCK_FUNCTION_WITH_CODE(, JSON_Object*, json_object_get_object, const JSON_Object*, object, const char*, name) +MOCK_FUNCTION_END(NULL) + MOCK_FUNCTION_WITH_CODE(, JSON_Object*, json_value_get_object, const JSON_Value*, value) JSON_Object* obj = NULL; if (value != NULL) @@ -161,6 +241,241 @@ MOCK_FUNCTION_END(newString) TEST_DEFINE_ENUM_TYPE(MODULE_LOADER_TYPE, MODULE_LOADER_TYPE_VALUES); +static inline +void +expected_calls_OutprocessLoader_JoinChildProcesses ( + size_t process_count_, + bool children_have_exited_, + size_t grace_period_ms_, + size_t children_close_ms_ +) { + static const TICK_COUNTER_HANDLE MOCK_TICKCOUNTER = (TICK_COUNTER_HANDLE)0x19790917; + static uv_process_t * MOCK_UV_PROCESS = (uv_process_t *)0x17091979; + + tickcounter_ms_t injected_ms = 0; + bool timed_out; + + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(VECTOR_size(MOCK_UV_PROCESS_VECTOR)) + .SetReturn(process_count_); + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(uv_default_loop()) + .SetReturn(MOCK_UV_LOOP); + disableNegativeTest(negative_test_index++); + if (children_have_exited_) { + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(0); + } else { + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(__LINE__); + enableNegativeTest(negative_test_index++); + EXPECTED_CALL(tickcounter_create()) + .SetFailReturn(NULL) + .SetReturn(MOCK_TICKCOUNTER); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(tickcounter_get_current_ms(MOCK_TICKCOUNTER, IGNORED_PTR_ARG)) + .CopyOutArgumentBuffer(2, &injected_ms, sizeof(tickcounter_ms_t)) + .IgnoreArgument(2) + .SetFailReturn(__LINE__) + .SetReturn(0); + + for (timed_out = true; injected_ms <= grace_period_ms_; injected_ms += 100) { + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(tickcounter_get_current_ms(MOCK_TICKCOUNTER, IGNORED_PTR_ARG)) + .CopyOutArgumentBuffer(2, &injected_ms, sizeof(tickcounter_ms_t)) + .IgnoreArgument(2) + .SetFailReturn(__LINE__) + .SetReturn(0); + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(uv_default_loop()) + .SetReturn(MOCK_UV_LOOP); + disableNegativeTest(negative_test_index++); + if (children_close_ms_ <= injected_ms) { + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(0); + timed_out = false; + break; + } else { + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(__LINE__); + } + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(ThreadAPI_Sleep(100)); + } + + if (timed_out) { + for (size_t i = 0; i < process_count_; ++i) { + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(uv_process_kill(MOCK_UV_PROCESS, SIGTERM)) + .SetFailReturn(__LINE__) + .SetReturn(0); + } + } + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(tickcounter_destroy(MOCK_TICKCOUNTER)); + } + + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(ThreadAPI_Join(MOCK_THREAD_HANDLE, IGNORED_PTR_ARG)) + .IgnoreArgument(2) + .SetFailReturn(THREADAPI_ERROR) + .SetReturn(THREADAPI_OK); + for (size_t i = 0; i < process_count_; ++i) { + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(gballoc_free(MOCK_UV_PROCESS)); + } + disableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(VECTOR_destroy(MOCK_UV_PROCESS_VECTOR)); +} + +static inline +void +expected_calls_OutprocessLoader_SpawnChildProcesses ( + bool first_call_ +) { + if (first_call_) { + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(ThreadAPI_Create(IGNORED_PTR_ARG, IGNORED_PTR_ARG, NULL)) + .CopyOutArgumentBuffer(1, &MOCK_THREAD_HANDLE, sizeof(THREAD_HANDLE)) + .IgnoreArgument(1) + .IgnoreArgument(2) + .SetFailReturn(THREADAPI_ERROR) + .SetReturn(THREADAPI_OK); + } +} + +static inline +void +expected_calls_launch_child_process_from_entrypoint ( + bool first_call_ +) { + static uv_process_t * MOCK_UV_PROCESS = (uv_process_t *)0x17091979; + + if (first_call_) { + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(VECTOR_create(sizeof(uv_process_t *))) + .SetFailReturn(NULL) + .SetReturn(MOCK_UV_PROCESS_VECTOR); + } + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(gballoc_malloc(sizeof(uv_process_t))) + .SetFailReturn(NULL); + + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(VECTOR_push_back(MOCK_UV_PROCESS_VECTOR, IGNORED_PTR_ARG, 1)) + .IgnoreArgument(2) + .SetFailReturn(__LINE__) + .SetReturn(0); + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(uv_default_loop()); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(uv_spawn(MOCK_UV_LOOP, MOCK_UV_PROCESS, IGNORED_PTR_ARG)) + .IgnoreArgument(2) + .IgnoreArgument(3) + .SetFailReturn(__LINE__) + .SetReturn(0); +} + +static inline +void +expected_calls_spawn_child_processes ( + bool failed +) { + static const int FAIL_RESULT = 1979; + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(uv_default_loop()); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(uv_run(MOCK_UV_LOOP, UV_RUN_DEFAULT)) + .SetFailReturn(FAIL_RESULT) + .SetReturn(0); + disableNegativeTest(negative_test_index++); + if (failed) { + STRICT_EXPECTED_CALL(ThreadAPI_Exit(FAIL_RESULT)); + } else { + STRICT_EXPECTED_CALL(ThreadAPI_Exit(0)); + } +} + +static inline +void +expected_calls_update_entrypoint_with_launch_object ( + void +) { + srand((unsigned int)time(NULL)); + static JSON_Array * JSON_PARAMETER_ARRAY = (JSON_Array *)__LINE__; + const size_t PARAMETER_COUNT = (rand() % 5); + + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(json_object_get_string(IGNORED_PTR_ARG, IGNORED_PTR_ARG)) + .SetFailReturn(NULL) + .SetReturn("../path/to/program.exe") + .ValidateArgumentBuffer(2, "path", (sizeof("path") - 1)); + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(json_object_get_array(IGNORED_PTR_ARG, IGNORED_PTR_ARG)) + .SetFailReturn(NULL) + .SetReturn(JSON_PARAMETER_ARRAY) + .ValidateArgumentBuffer(2, "args", (sizeof("args") - 1)); + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(json_array_get_count(IGNORED_PTR_ARG)) + .SetFailReturn(0) + .SetReturn(PARAMETER_COUNT); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(gballoc_malloc(sizeof(char *) * (PARAMETER_COUNT + 2))) + .SetFailReturn(NULL); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(gballoc_malloc(sizeof("../path/to/program.exe"))) + .SetFailReturn(NULL); + for (size_t i = 0; i < PARAMETER_COUNT; ++i) { + disableNegativeTest(negative_test_index++); + // Ensures parameters are not index shifted by one + if (i % 2) { + STRICT_EXPECTED_CALL(json_array_get_string(JSON_PARAMETER_ARRAY, i)) + .SetFailReturn(NULL) + .SetReturn("program_argument"); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(gballoc_malloc(sizeof("program_argument"))) + .SetFailReturn(NULL); + } else { + STRICT_EXPECTED_CALL(json_array_get_string(JSON_PARAMETER_ARRAY, i)) + .SetFailReturn(NULL) + .SetReturn("other_program_argument"); + enableNegativeTest(negative_test_index++); + STRICT_EXPECTED_CALL(gballoc_malloc(sizeof("other_program_argument"))) + .SetFailReturn(NULL); + } + } +} + +static inline +void +expected_calls_validate_launch_arguments ( + const double *grace_period_ms +) { + enableNegativeTest(negative_test_index++); + EXPECTED_CALL(json_object_get_string(IGNORED_PTR_ARG, IGNORED_PTR_ARG)) + .SetFailReturn(NULL) + .SetReturn("../path/to/program.exe") + .ValidateArgumentBuffer(2, "path", (sizeof("path") - 1)); + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(json_object_get_string(IGNORED_PTR_ARG, IGNORED_PTR_ARG)) + .SetFailReturn(NULL) + .SetReturn("1500") + .ValidateArgumentBuffer(2, "grace.period.ms", (sizeof("grace.period.ms") - 1)); + if (grace_period_ms) { + disableNegativeTest(negative_test_index++); + EXPECTED_CALL(json_object_get_number(IGNORED_PTR_ARG, IGNORED_PTR_ARG)) + .SetFailReturn(0.0) + .SetReturn(*grace_period_ms) + .ValidateArgumentBuffer(2, "grace.period.ms", (sizeof("grace.period.ms") - 1)); + } +} + BEGIN_TEST_SUITE(OutprocessLoader_UnitTests) TEST_SUITE_INITIALIZE(TestClassInitialize) @@ -180,6 +495,15 @@ TEST_SUITE_INITIALIZE(TestClassInitialize) REGISTER_UMOCK_ALIAS_TYPE(JSON_Value_Type, int); REGISTER_UMOCK_ALIAS_TYPE(MODULE_API_VERSION, int); REGISTER_UMOCK_ALIAS_TYPE(UNIQUEID_RESULT, int); + REGISTER_UMOCK_ALIAS_TYPE(uv_loop_t *, void *); + REGISTER_UMOCK_ALIAS_TYPE(uv_process_t *, void *); + REGISTER_UMOCK_ALIAS_TYPE(uv_run_mode, int); + REGISTER_UMOCK_ALIAS_TYPE(THREAD_HANDLE, void *); + REGISTER_UMOCK_ALIAS_TYPE(THREAD_START_FUNC, void *); + REGISTER_UMOCK_ALIAS_TYPE(THREADAPI_RESULT, int); + REGISTER_UMOCK_ALIAS_TYPE(TICK_COUNTER_HANDLE, void *); + REGISTER_UMOCK_ALIAS_TYPE(VECTOR_HANDLE, void *); + REGISTER_GLOBAL_MOCK_FAIL_RETURN(json_value_get_object, NULL); // malloc/free hooks @@ -222,6 +546,8 @@ TEST_FUNCTION_INITIALIZE(TestMethodInitialize) } umock_c_reset_all_calls(); + negative_test_index = 0; + negative_tests_to_skip = 0; malloc_will_fail = false; malloc_fail_count = 0; malloc_count = 0; @@ -273,7 +599,7 @@ TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_loader_type_is_not_O TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_control_id_is_NULL) { // arrange - OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, NULL, (STRING_HANDLE)0x42, 0 }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, NULL, (STRING_HANDLE)0x42, 0, NULL, 0 }; MODULE_LOADER loader = { OUTPROCESS, @@ -287,11 +613,11 @@ TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_control_id_is_NULL) ASSERT_IS_NULL(result); } -/*Tests_SRS_OUTPROCESS_LOADER_17_003: [ If the entrypoint's activation_type is not NONE, the this function shall return NULL. ]*/ +/*Tests_SRS_OUTPROCESS_LOADER_27_003: [ If the entrypoint's `activation_type` is invalid, then `OutprocessModuleLoader_Load` shall return `NULL`. ]*/ TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_activation_type_is_not_NONE) { // arrange - OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { (OUTPROCESS_LOADER_ACTIVATION_TYPE)0x42, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0}; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { (OUTPROCESS_LOADER_ACTIVATION_TYPE)0x42, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0, NULL, 0}; MODULE_LOADER loader = { OUTPROCESS, @@ -309,8 +635,19 @@ TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_activation_type_is_n TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_things_fail) { // arrange - OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0 }; - MODULE_LOADER loader = + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_NONE, + (STRING_HANDLE)0x42, + (STRING_HANDLE)0x42, + sizeof(process_argv), + process_argv, + 0 + }; + MODULE_LOADER loader = { OUTPROCESS, NULL, NULL, NULL @@ -345,12 +682,26 @@ TEST_FUNCTION(OutprocessModuleLoader_Load_returns_NULL_when_things_fail) } /*Tests_SRS_OUTPROCESS_LOADER_17_004: [ The loader shall allocate memory for the loader handle. ]*/ +/*Tests_SRS_OUTPROCESS_LOADER_27_005: [ Launch - `OutprocessModuleLoader_Load` shall launch the child process identified by the entrypoint. ]*/ +/*Tests_SRS_OUTPROCESS_LOADER_27_077: [ Launch - `OutprocessModuleLoader_Load` shall spawn the enqueued child processes. ] */ /*Tests_SRS_OUTPROCESS_LOADER_17_006: [ The loader shall store the Outprocess_Module_API_all in the loader handle. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_007: [ Upon success, this function shall return a valid pointer to the loader handle. ]*/ TEST_FUNCTION(OutprocessModuleLoader_Load_succeeds) { // arrange - OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0 }; + global_memory = true; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + (STRING_HANDLE)0x42, + (STRING_HANDLE)0x42, + sizeof(process_argv), + process_argv, + 0 + }; MODULE_LOADER loader = { OUTPROCESS, @@ -358,10 +709,10 @@ TEST_FUNCTION(OutprocessModuleLoader_Load_succeeds) }; umock_c_reset_all_calls(); + expected_calls_launch_child_process_from_entrypoint(true); + expected_calls_OutprocessLoader_SpawnChildProcesses(true); - - STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)) - .IgnoreArgument(1); + EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // act MODULE_LIBRARY_HANDLE result = OutprocessModuleLoader_Load(&loader, &entrypoint); @@ -372,6 +723,8 @@ TEST_FUNCTION(OutprocessModuleLoader_Load_succeeds) // cleanup OutprocessModuleLoader_Unload(&loader, result); + OutprocessLoader_JoinChildProcesses(); + global_memory = false; } TEST_FUNCTION(OutprocessModuleLoader_GetModuleApi_returns_NULL_when_moduleLibraryHandle_is_NULL) @@ -387,7 +740,7 @@ TEST_FUNCTION(OutprocessModuleLoader_GetModuleApi_returns_NULL_when_moduleLibrar TEST_FUNCTION(OutprocessModuleLoader_GetModuleApi_succeeds) { // arrange - OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0}; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0, NULL, 0}; MODULE_LOADER loader = { OUTPROCESS, @@ -424,7 +777,7 @@ TEST_FUNCTION(OutprocessModuleLoader_Unload_does_nothing_when_moduleLibraryHandl TEST_FUNCTION(OutprocessModuleLoader_Unload_frees_things) { // arrange - OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0}; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { OUTPROCESS_LOADER_ACTIVATION_NONE, (STRING_HANDLE)0x42, (STRING_HANDLE)0x42, 0, NULL, 0}; MODULE_LOADER loader = { OUTPROCESS, @@ -493,7 +846,7 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_j /*Tests_SRS_OUTPROCESS_LOADER_17_013: [ This function shall return NULL if "activation.type" is not present in json. ]*/ -TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_json_object_acitvation_type_NULL) +TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_json_object_activation_type_NULL) { // arrange @@ -508,7 +861,8 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_j .SetReturn(activation_type); STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "control.id")) .SetReturn(control_id); - STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) + STRICT_EXPECTED_CALL(json_object_get_object((JSON_Object*)0x43, "launch")); + STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) .SetReturn(NULL); // act @@ -533,7 +887,8 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_j .SetReturn(activation_type); STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "control.id")) .SetReturn(NULL); - STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) + STRICT_EXPECTED_CALL(json_object_get_object((JSON_Object*)0x43, "launch")); + STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) .SetReturn(NULL); // act @@ -544,7 +899,7 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_j ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); } -/*Tests_SRS_OUTPROCESS_LOADER_17_014: [ This function shall return NULL if "activation.type is not "NONE". ]*/ +/*Tests_SRS_OUTPROCESS_LOADER_27_014: [ This function shall return `NULL` if `activation.type` is `OUTPROCESS_LOADER_ACTIVATION_INVALID`. ]*/ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_json_object_type_not_none) { // arrange @@ -559,7 +914,8 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_j .SetReturn(activation_type); STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "control.id")) .SetReturn(control_id); - STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) + STRICT_EXPECTED_CALL(json_object_get_object((JSON_Object*)0x43, "launch")); + STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) .SetReturn(NULL); // act @@ -585,7 +941,8 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_m .SetReturn(activation_type); STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "control.id")) .SetReturn(control_id); - STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) + STRICT_EXPECTED_CALL(json_object_get_object((JSON_Object*)0x43, "launch")); + STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) .SetReturn(NULL); STRICT_EXPECTED_CALL(gballoc_malloc(sizeof(OUTPROCESS_LOADER_ENTRYPOINT))) .SetReturn(NULL); @@ -615,11 +972,10 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_u .SetReturn(activation_type); STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "control.id")) .SetReturn(control_id); - STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) + STRICT_EXPECTED_CALL(json_object_get_object((JSON_Object*)0x43, "launch")); + STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) .SetReturn(NULL); STRICT_EXPECTED_CALL(gballoc_malloc(sizeof(OUTPROCESS_LOADER_ENTRYPOINT))); - STRICT_EXPECTED_CALL(json_object_get_number((JSON_Object*)0x43, "timeout")) - .SetReturn(0); STRICT_EXPECTED_CALL(STRING_construct(control_id)) .SetReturn(NULL); STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)) @@ -634,18 +990,21 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_returns_NULL_when_u ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); } +/*Tests_SRS_OUTPROCESS_LOADER_27_015: [ Launch - `OutprocessModuleLoader_ParseEntrypointFromJson` shall validate the launch parameters. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_016: [ This function shall allocate a OUTPROCESS_LOADER_ENTRYPOINT structure. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_017: [ This function shall assign the entrypoint activation_type to NONE. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_018: [ This function shall assign the entrypoint control_id to the string value of "control.id" in json. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_019: [ This function shall assign the entrypoint message_id to the string value of "message.id" in json, NULL if not present. ]*/ +/*Tests_SRS_OUTPROCESS_LOADER_27_020: [ Launch - `OutprocessModuleLoader_ParseEntrypointFromJson` shall update the entry point with the parsed launch parameters. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_043: [ This function shall read the "timeout" value. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_044: [ If "timeout" is set, the remote_message_wait shall be set to this value, else it will be set to a default of 1000 ms. ]*/ /*Tests_SRS_OUTPROCESS_LOADER_17_022: [ This function shall return a valid pointer to an OUTPROCESS_LOADER_ENTRYPOINT on success. ]*/ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_succeeds) { // arrange - char * activation_type = "none"; + char * activation_type = "launch"; char * control_id = "a url"; + double grace_period_ms = 500; STRICT_EXPECTED_CALL(json_value_get_type((JSON_Value*)0x42)) .SetReturn(JSONObject); @@ -655,12 +1014,16 @@ TEST_FUNCTION(OutprocessModuleLoader_ParseEntrypointFromJson_succeeds) .SetReturn(activation_type); STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "control.id")) .SetReturn(control_id); - STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) + STRICT_EXPECTED_CALL(json_object_get_object((JSON_Object*)0x43, "launch")) + .SetReturn((JSON_Object*)0x44); + STRICT_EXPECTED_CALL(json_object_get_string((JSON_Object*)0x43, "message.id")) .SetReturn(NULL); + expected_calls_validate_launch_arguments(&grace_period_ms); STRICT_EXPECTED_CALL(gballoc_malloc(sizeof(OUTPROCESS_LOADER_ENTRYPOINT))); + STRICT_EXPECTED_CALL(STRING_construct(control_id)); + expected_calls_update_entrypoint_with_launch_object(); STRICT_EXPECTED_CALL(json_object_get_number((JSON_Object*)0x43, "timeout")) .SetReturn(2000); - STRICT_EXPECTED_CALL(STRING_construct(control_id)); STRICT_EXPECTED_CALL(STRING_construct(NULL)); // act @@ -780,6 +1143,8 @@ TEST_FUNCTION(OutprocessModuleLoader_BuildModuleConfiguration_success_with_msg_u OUTPROCESS_LOADER_ACTIVATION_NONE, STRING_construct("control_id"), STRING_construct("message_id"), + 0, + NULL, 0 }; STRING_HANDLE mc = STRING_construct("message config"); @@ -821,6 +1186,8 @@ TEST_FUNCTION(OutprocessModuleLoader_BuildModuleConfiguration_success_with_no_ms STRING_construct("control_id"), NULL, 0, + NULL, + 0 }; STRING_HANDLE mc = STRING_construct("message config"); @@ -854,6 +1221,8 @@ TEST_FUNCTION(OutprocessModuleLoader_BuildModuleConfiguration_returns_null_on_mo OUTPROCESS_LOADER_ACTIVATION_NONE, STRING_construct("control_id"), STRING_construct("message_id"), + 0, + NULL, 0 }; STRING_HANDLE mc = STRING_construct("message config"); @@ -897,6 +1266,8 @@ TEST_FUNCTION(OutprocessModuleLoader_BuildModuleConfiguration_returns_null_on_ms STRING_construct("control_id"), NULL, 0, + NULL, + 0 }; STRING_HANDLE mc = STRING_construct("message config"); @@ -932,6 +1303,8 @@ TEST_FUNCTION(OutprocessModuleLoader_BuildModuleConfiguration_returns_null_on_ma STRING_construct("control_id"), NULL, 0, + NULL, + 0 }; STRING_HANDLE mc = STRING_construct("message config"); @@ -973,6 +1346,8 @@ TEST_FUNCTION(OutprocessModuleLoader_FreeModuleConfiguration_frees_stuff) STRING_construct("control_id"), NULL, 0, + NULL, + 0 }; STRING_HANDLE mc = STRING_construct("message config"); @@ -1020,4 +1395,1034 @@ TEST_FUNCTION(OutprocessLoader_Get_succeeds) ASSERT_IS_TRUE(strcmp(loader->name, "outprocess") == 0); } + +/* Tests_SRS_OUTPROCESS_LOADER_27_050: [ Prerequisite Check - If no threads are running, then `OutprocessLoader_JoinChildProcesses` shall abandon the effort to join the child processes immediately. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_no_threads_running) +{ + // Arrange + global_memory = true; + + // Expected call listing + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(VECTOR_size(NULL)) + .SetReturn(0); + EXPECTED_CALL(uv_default_loop()) + .SetReturn(MOCK_UV_LOOP); + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(0); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_063: [ If no processes are running, then `OutprocessLoader_JoinChildProcesses` shall immediately join the child process management thread. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_no_processes_active) +{ + // Arrange + srand((unsigned int)time(NULL)); + global_memory = true; + + static const bool CHILDREN_HAVE_EXITED = true; + static const double CHILDREN_EXIT_MS = 600; + static const double GRACE_PERIOD_MS = 500; + const size_t PROCESS_COUNT = ((rand() % 5) + 1); + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + result = validate_launch_arguments(NULL); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_launch_child_process_from_entrypoint(0 == i); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(0 == i); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } + + expected_calls_OutprocessLoader_JoinChildProcesses(PROCESS_COUNT, CHILDREN_HAVE_EXITED, (size_t)GRACE_PERIOD_MS, (size_t)CHILDREN_EXIT_MS); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_059: [** If the child processes are not running, `OutprocessLoader_JoinChildProcesses` shall shall immediately join the child process management thread. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_processes_exit_during_grace_period) +{ + // Arrange + srand((unsigned int)time(NULL)); + global_memory = true; + + static const bool CHILDREN_STILL_ACTIVE = false; + static const double CHILDREN_EXIT_MS = 200; + static const double GRACE_PERIOD_MS = 500; + const size_t PROCESS_COUNT = ((rand() % 5) + 1); + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + result = validate_launch_arguments(NULL); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_launch_child_process_from_entrypoint(0 == i); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(0 == i); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } + + expected_calls_OutprocessLoader_JoinChildProcesses(PROCESS_COUNT, CHILDREN_STILL_ACTIVE, (size_t)GRACE_PERIOD_MS, (size_t)CHILDREN_EXIT_MS); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_064: [ `OutprocessLoader_JoinChildProcesses` shall get the count of child processes, by calling `size_t VECTOR_size(VECTOR_HANDLE handle)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_051: [ `OutprocessLoader_JoinChildProcesses` shall create a timer to test for timeout, by calling `TICK_COUNTER_HANDLE tickcounter_create(void)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_053: [ `OutprocessLoader_JoinChildProcesses` shall mark the begin time, by calling `int tickcounter_get_current_ms(TICK_COUNTER_HANDLE tick_counter, tickcounter_ms_t * current_ms)` using the handle called by the previous call to `tickcounter_create`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_055: [ While awaiting the grace period, `OutprocessLoader_JoinChildProcesses` shall mark the current time, by calling `int tickcounter_get_current_ms(TICK_COUNTER_HANDLE tick_counter, tickcounter_ms_t * current_ms)` using the handle called by the previous call to `tickcounter_create`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_057: [ While awaiting the grace period, `OutprocessLoader_JoinChildProcesses` shall check if the processes are running, by calling `int uv_loop_alive(const uv_loop_t * loop)` using the result of `uv_default_loop()` for `loop`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_058: [ `OutprocessLoader_JoinChildProcesses` shall await the grace period in 100ms increments, by calling `void ThreadAPI_Sleep(unsigned int milliseconds)` passing 100 for `milliseconds`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_060: [ If the grace period expired, `OutprocessLoader_JoinChildProcesses` shall get the handle of each child processes, by calling `void * VECTOR_element(VECTOR_HANDLE handle, size_t index)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_061: [ If the grace period expired, `OutprocessLoader_JoinChildProcesses` shall signal each child, by calling `int uv_process_kill(uv_process_t * process, int signum)` passing `SIGTERM` for `signum`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_062: [ `OutprocessLoader_JoinChildProcesses` shall join the child process management thread, by calling `THREADAPI_RESULT ThreadAPI_Join(THREAD_HANDLE threadHandle, int * res)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_065: [ `OutprocessLoader_JoinChildProcesses` shall get the handle of each child processes, by calling `void * VECTOR_element(VECTOR_HANDLE handle, size_t index)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_066: [ `OutprocessLoader_JoinChildProcesses` shall free the resources allocated to each child, by calling `void free(void * _Block)` passing the child handle as `_Block`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_067: [ `OutprocessLoader_JoinChildProcesses` shall destroy the vector of child processes, by calling `void VECTOR_destroy(VECTOR_HANDLE handle)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_068: [ `OutprocessLoader_JoinChildProcesses` shall destroy the timer, by calling `void tickcounter_destroy(TICK_COUNTER_HANDLE tick_counter)`. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_success) +{ + // Arrange + srand((unsigned int)time(NULL)); + global_memory = true; + + static const bool CHILDREN_STILL_ACTIVE = false; + static const double CHILDREN_EXIT_MS = 600; + static const double GRACE_PERIOD_MS = 500; + const size_t PROCESS_COUNT = ((rand() % 5) + 1); + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + result = validate_launch_arguments(NULL); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_launch_child_process_from_entrypoint(0 == i); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(0 == i); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } + + expected_calls_OutprocessLoader_JoinChildProcesses(PROCESS_COUNT, CHILDREN_STILL_ACTIVE, (size_t)GRACE_PERIOD_MS, (size_t)CHILDREN_EXIT_MS); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_052: [ If unable to create a timer, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_unable_to_create_tickcounter) +{ + // Arrange + srand((unsigned int)time(NULL)); + global_memory = true; + + static const double GRACE_PERIOD_MS = 500; + static uv_process_t * MOCK_UV_PROCESS = (uv_process_t *)0x17091979; + const TICK_COUNTER_HANDLE MOCK_TICKCOUNTER = NULL; + const size_t PROCESS_COUNT = ((rand() % 5) + 1); + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + result = validate_launch_arguments(NULL); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_launch_child_process_from_entrypoint(0 == i); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(0 == i); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } + + STRICT_EXPECTED_CALL(VECTOR_size(MOCK_UV_PROCESS_VECTOR)) + .SetReturn(PROCESS_COUNT); + EXPECTED_CALL(uv_default_loop()) + .SetReturn(MOCK_UV_LOOP); + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(__LINE__); + EXPECTED_CALL(tickcounter_create()) + .SetReturn(MOCK_TICKCOUNTER); + + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + STRICT_EXPECTED_CALL(uv_process_kill(MOCK_UV_PROCESS, SIGTERM)) + .SetFailReturn(__LINE__) + .SetReturn(i); + } + STRICT_EXPECTED_CALL(tickcounter_destroy(MOCK_TICKCOUNTER)); + + STRICT_EXPECTED_CALL(ThreadAPI_Join(MOCK_THREAD_HANDLE, IGNORED_PTR_ARG)) + .IgnoreArgument(2) + .SetFailReturn(THREADAPI_ERROR) + .SetReturn(THREADAPI_NO_MEMORY); + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + STRICT_EXPECTED_CALL(gballoc_free(MOCK_UV_PROCESS)); + } + STRICT_EXPECTED_CALL(VECTOR_destroy(MOCK_UV_PROCESS_VECTOR)); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_054: [** If unable to mark the begin time, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_unable_to_mark_start_time) +{ + // Arrange + srand((unsigned int)time(NULL)); + global_memory = true; + + static const double GRACE_PERIOD_MS = 500; + static uv_process_t * MOCK_UV_PROCESS = (uv_process_t *)0x17091979; + const TICK_COUNTER_HANDLE MOCK_TICKCOUNTER = (TICK_COUNTER_HANDLE)0x19790917; + const size_t PROCESS_COUNT = ((rand() % 5) + 1); + + int result; + tickcounter_ms_t injected_ms = 0; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + result = validate_launch_arguments(NULL); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_launch_child_process_from_entrypoint(0 == i); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(0 == i); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } + + STRICT_EXPECTED_CALL(VECTOR_size(MOCK_UV_PROCESS_VECTOR)) + .SetReturn(PROCESS_COUNT); + EXPECTED_CALL(uv_default_loop()) + .SetReturn(MOCK_UV_LOOP); + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(__LINE__); + EXPECTED_CALL(tickcounter_create()) + .SetReturn(MOCK_TICKCOUNTER); + STRICT_EXPECTED_CALL(tickcounter_get_current_ms(MOCK_TICKCOUNTER, IGNORED_PTR_ARG)) + .CopyOutArgumentBuffer(2, &injected_ms, sizeof(tickcounter_ms_t)) + .SetReturn(__LINE__); + + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + STRICT_EXPECTED_CALL(uv_process_kill(MOCK_UV_PROCESS, SIGTERM)) + .SetFailReturn(__LINE__) + .SetReturn(i); + } + STRICT_EXPECTED_CALL(tickcounter_destroy(MOCK_TICKCOUNTER)); + + STRICT_EXPECTED_CALL(ThreadAPI_Join(MOCK_THREAD_HANDLE, IGNORED_PTR_ARG)) + .IgnoreArgument(2) + .SetReturn(THREADAPI_NO_MEMORY); + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + STRICT_EXPECTED_CALL(gballoc_free(MOCK_UV_PROCESS)); + } + STRICT_EXPECTED_CALL(VECTOR_destroy(MOCK_UV_PROCESS_VECTOR)); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_056: [ If unable to mark the current time, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. ] */ +TEST_FUNCTION(OutprocessLoader_JoinChildProcesses_SCENARIO_unable_to_mark_current_time) +{ + // Arrange + srand((unsigned int)time(NULL)); + global_memory = true; + + static const double GRACE_PERIOD_MS = 500; + static uv_process_t * MOCK_UV_PROCESS = (uv_process_t *)0x17091979; + const TICK_COUNTER_HANDLE MOCK_TICKCOUNTER = (TICK_COUNTER_HANDLE)0x19790917; + const size_t PROCESS_COUNT = ((rand() % 5) + 1); + + int result; + tickcounter_ms_t injected_ms = 0; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + result = validate_launch_arguments(NULL); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_launch_child_process_from_entrypoint(0 == i); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(0 == i); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } + + STRICT_EXPECTED_CALL(VECTOR_size(MOCK_UV_PROCESS_VECTOR)) + .SetReturn(PROCESS_COUNT); + EXPECTED_CALL(uv_default_loop()) + .SetReturn(MOCK_UV_LOOP); + STRICT_EXPECTED_CALL(uv_loop_alive(MOCK_UV_LOOP)) + .SetReturn(__LINE__); + EXPECTED_CALL(tickcounter_create()) + .SetReturn(MOCK_TICKCOUNTER); + STRICT_EXPECTED_CALL(tickcounter_get_current_ms(MOCK_TICKCOUNTER, IGNORED_PTR_ARG)) + .CopyOutArgumentBuffer(2, &injected_ms, sizeof(tickcounter_ms_t)) + .SetReturn(0); + STRICT_EXPECTED_CALL(tickcounter_get_current_ms(MOCK_TICKCOUNTER, IGNORED_PTR_ARG)) + .CopyOutArgumentBuffer(2, &injected_ms, sizeof(tickcounter_ms_t)) + .SetReturn(__LINE__); + + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + STRICT_EXPECTED_CALL(uv_process_kill(MOCK_UV_PROCESS, SIGTERM)) + .SetFailReturn(__LINE__) + .SetReturn(i); + } + STRICT_EXPECTED_CALL(tickcounter_destroy(MOCK_TICKCOUNTER)); + + STRICT_EXPECTED_CALL(ThreadAPI_Join(MOCK_THREAD_HANDLE, IGNORED_PTR_ARG)) + .IgnoreArgument(2) + .SetReturn(THREADAPI_NO_MEMORY); + for (size_t i = 0; i < PROCESS_COUNT; ++i) { + STRICT_EXPECTED_CALL(VECTOR_element(MOCK_UV_PROCESS_VECTOR, i)) + .SetReturn(&MOCK_UV_PROCESS); + STRICT_EXPECTED_CALL(gballoc_free(MOCK_UV_PROCESS)); + } + STRICT_EXPECTED_CALL(VECTOR_destroy(MOCK_UV_PROCESS_VECTOR)); + + // Act + OutprocessLoader_JoinChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + + // Cleanup + global_memory = false; +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_045: [ Prerequisite Check - If no processes have been enqueued, then `OutprocessLoader_SpawnChildProcesses` shall take no action and return zero. ] */ +TEST_FUNCTION(OutprocessLoader_SpawnChildProcesses_SCENARIO_no_processes_scheduled) +{ + // Arrange + int result; + + // Expected call listing + umock_c_reset_all_calls(); + + // Act + result = OutprocessLoader_SpawnChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_046: [ Prerequisite Check - If child processes are already running, then `OutprocessLoader_SpawnChildProcesses` shall take no action and return zero. ] */ +TEST_FUNCTION(OutprocessLoader_SpawnChildProcesses_SCENARIO_thread_already_running) +{ + // Arrange + global_memory = true; + static const bool FIRST_CALL = true; + static const bool SUBSEQUENT_CALL = false; + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + + expected_calls_launch_child_process_from_entrypoint(FIRST_CALL); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(FIRST_CALL); + result = OutprocessLoader_SpawnChildProcesses(); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(SUBSEQUENT_CALL); + + // Act + result = OutprocessLoader_SpawnChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup + OutprocessLoader_JoinChildProcesses(); + my_gballoc_free(NULL); + global_memory = false; + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_047: [ `OutprocessLoader_SpawnChildProcesses` shall launch the enqueued child processes by calling `THREADAPI_RESULT ThreadAPI_Create(THREAD_HANDLE * threadHandle, THREAD_START_FUNC func, void * arg)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_049: [ If no errors are encountered, then `OutprocessLoader_SpawnChildProcesses` shall return zero. ] */ +TEST_FUNCTION(OutprocessLoader_SpawnChildProcesses_SCENARIO_success) +{ + // Arrange + global_memory = true; + static const bool FIRST_CALL = true; + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + + expected_calls_launch_child_process_from_entrypoint(FIRST_CALL); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + expected_calls_OutprocessLoader_SpawnChildProcesses(FIRST_CALL); + + // Act + result = OutprocessLoader_SpawnChildProcesses(); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup + OutprocessLoader_JoinChildProcesses(); + my_gballoc_free(NULL); + global_memory = false; + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); +} + +/* Tests_SRS_OUTPROCESS_LOADER_27_048: [ If launching the enqueued child processes fails, then `OutprocessLoader_SpawnChildProcesses` shall return a non-zero value. ] */ +TEST_FUNCTION(OutprocessLoader_SpawnChildProcesses_SCENARIO_negative_tests) +{ + // Arrange + int negativeTestsInitResult = umock_c_negative_tests_init(); + ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult); + + global_memory = true; + static const bool FIRST_CALL = true; + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_launch_child_process_from_entrypoint(FIRST_CALL); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + disableNegativeTestsBeforeIndex(negative_test_index); + expected_calls_OutprocessLoader_SpawnChildProcesses(FIRST_CALL); + umock_c_negative_tests_snapshot(); + + ASSERT_ARE_EQUAL(int, negative_test_index, umock_c_negative_tests_call_count()); + for (size_t i = 0; i < umock_c_negative_tests_call_count(); ++i) { + if (skipNegativeTest(i)) { + printf("%s: Skipping negative tests: %zx\n", __FUNCTION__, i); + continue; + } + printf("%s: Running negative tests: %zx\n", __FUNCTION__, i); + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + // Act + result = OutprocessLoader_SpawnChildProcesses(); + + // Assert + ASSERT_ARE_NOT_EQUAL(int, 0, result); + + // Cleanup + OutprocessLoader_JoinChildProcesses(); + my_gballoc_free(NULL); + global_memory = false; + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); + } + + // Cleanup + umock_c_negative_tests_deinit(); +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_069: [ `launch_child_process_from_entrypoint` shall attempt to create a vector for child processes(unless previously created), by calling `VECTOR_HANDLE VECTOR_create(size_t elementSize)` using `sizeof(uv_process_t *)` as `elementSize`. ]*/ +/* Tests_SRS_OUTPROCESS_LOADER_27_071: [ `launch_child_process_from_entrypoint` shall allocate the memory for the child handle, by calling `void * malloc(size_t _Size)` passing `sizeof(uv_process_t)` as `_Size`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_073: [ `launch_child_process_from_entrypoint` shall store the child's handle, by calling `int VECTOR_push_back(VECTOR_HANDLE handle, const void * elements, size_t numElements)` passing the process vector as `handle` the pointer to the newly allocated memory for the process context as `elements` and 1 as `numElements`. ]*/ +/* Tests_SRS_OUTPROCESS_LOADER_27_075: [ `launch_child_process_from_entrypoint` shall enqueue the child process to be spawned, by calling `int uv_spawn(uv_loop_t * loop, uv_process_t * handle, const uv_process_options_t * options)` passing the result of `uv_default_loop()` as `loop`, the newly allocated process handle as `handle`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_079: [ If no errors are encountered, then `launch_child_process_from_entrypoint` shall return zero. ] */ +TEST_FUNCTION(launch_child_process_from_entrypoint_SCENARIO_success) +{ + // Arrange + global_memory = true; + static const bool FIRST_CALL = true; + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_launch_child_process_from_entrypoint(FIRST_CALL); + + // Act + result = launch_child_process_from_entrypoint(&entrypoint); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup + OutprocessLoader_JoinChildProcesses(); + my_gballoc_free(NULL); + global_memory = false; + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_098: [ If a vector for child processes already exists, then `launch_child_process_from_entrypoint` shall not attempt to recreate the vector. ] */ +TEST_FUNCTION(launch_child_process_from_entrypoint_SCENARIO_subsequent_call) +{ + // Arrange + global_memory = true; + static const bool FIRST_CALL = true; + static const bool SUBSEQUENT_CALL = false; + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + + expected_calls_launch_child_process_from_entrypoint(FIRST_CALL); + result = launch_child_process_from_entrypoint(&entrypoint); + ASSERT_ARE_EQUAL(int, 0, result); + + expected_calls_launch_child_process_from_entrypoint(SUBSEQUENT_CALL); + + // Act + result = launch_child_process_from_entrypoint(&entrypoint); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup + OutprocessLoader_JoinChildProcesses(); + my_gballoc_free(NULL); + my_gballoc_free(NULL); + global_memory = false; + ASSERT_ARE_EQUAL(int, 0, global_malloc_count); +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_070: [ If a vector for the child processes does not exist, then `launch_child_process_from_entrypoint` shall return a non-zero value. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_072: [ If unable to allocate memory for the child handle, then `launch_child_process_from_entrypoint` shall return a non-zero value. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_074: [ If unable to store the child's handle, then `launch_child_process_from_entrypoint` shall free the memory allocated to the child process handle and return a non-zero value. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_076: [ If unable to enqueue the child process, then `launch_child_process_from_entrypoint` shall remove the stored handle, free the memory allocated to the child process handle and return a non-zero value. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_078: [ If launching the enqueued child processes fails, then `launch_child_process_from_entrypoint` shall return a non - zero value. ] */ +TEST_FUNCTION(launch_child_process_from_entrypoint_SCENARIO_negative_tests) +{ + // Arrange + int negativeTestsInitResult = umock_c_negative_tests_init(); + ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult); + + global_memory = true; + static const bool FIRST_CALL = true; + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_launch_child_process_from_entrypoint(FIRST_CALL); + umock_c_negative_tests_snapshot(); + + ASSERT_ARE_EQUAL(int, negative_test_index, umock_c_negative_tests_call_count()); + for (size_t i = 0; i < umock_c_negative_tests_call_count(); ++i) { + if (skipNegativeTest(i)) { + printf("%s: Skipping negative tests: %zx\n", __FUNCTION__, i); + continue; + } + printf("%s: Running negative tests: %zx\n", __FUNCTION__, i); + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + // Act + result = launch_child_process_from_entrypoint(&entrypoint); + + // Assert + ASSERT_ARE_NOT_EQUAL(int, 0, result); + + // Cleanup + OutprocessLoader_JoinChildProcesses(); + } + + // Cleanup + umock_c_negative_tests_deinit(); +} + +/* Tests_SRS_OUTPROCESS_LOADER_27_080: [ `spawn_child_processes` shall start the child process management thread, by calling `int uv_run(uv_loop_t * loop, uv_run_mode mode)` passing the result of `uv_default_loop()` for `loop` and `UV_RUN_DEFAULT` for `mode`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_081: [ If no errors are encountered, then `spawn_child_processes` shall return zero. ] */ +TEST_FUNCTION(spawn_child_processes_SCENARIO_success) +{ + // Arrange + int result; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_spawn_child_processes(false); + + // Act + result = spawn_child_processes(NULL); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_099: [ `spawn_child_processes` shall return the result of the child process management thread to the parent thread, by calling `void ThreadAPI_Exit(int res)` passing the result of `uv_run()` as `res`. ] */ +TEST_FUNCTION(spawn_child_processes_SCENARIO_negative_tests) +{ + // Arrange + int negativeTestsInitResult = umock_c_negative_tests_init(); + ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult); + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_spawn_child_processes(true); + umock_c_negative_tests_snapshot(); + + ASSERT_ARE_EQUAL(int, negative_test_index, umock_c_negative_tests_call_count()); + for (size_t i = 0; i < umock_c_negative_tests_call_count(); ++i) { + if (skipNegativeTest(i)) { + printf("%s: Skipping negative tests: %zx\n", __FUNCTION__, i); + continue; + } + printf("%s: Running negative tests: %zx\n", __FUNCTION__, i); + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + // Act + spawn_child_processes(NULL); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + // Cleanup + } + + // Cleanup + umock_c_negative_tests_deinit(); +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_082: [ `update_entrypoint_with_launch_object` shall retrieve the file path, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `path` as `name`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_083: [ `update_entrypoint_with_launch_object` shall retrieve the JSON arguments array, by calling `JSON_Array json_object_get_array(const JSON_Object * object, const char * name)` passing `args` as `name`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_084: [ `update_entrypoint_with_launch_object` shall determine the size of the JSON arguments array, by calling `size_t json_array_get_count(const JSON_Array * array)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_085: [ `update_entrypoint_with_launch_object` shall allocate the argument array, by calling `void * malloc(size _Size)` passing the result of `json_array_get_count` plus two, one for passing the file path as the first argument and the second for the NULL terminating pointer required by libUV. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_087: [ `update_entrypoint_with_launch_object` shall allocate the space necessary to copy the file path, by calling `void * malloc(size _Size)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_089: [ `update_entrypoint_with_launch_object` shall retrieve each argument from the JSON arguments array, by calling `const char * json_array_get_string(const JSON_Array * array, size_t index)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_090: [ `update_entrypoint_with_launch_object` shall allocate the space necessary for each argument, by calling `void * malloc(size _Size)`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_092: [ If no errors are encountered, then `update_entrypoint_with_launch_object` shall return zero. ] */ +TEST_FUNCTION(update_entrypoint_with_launch_object_SCENARIO_success) +{ + // Arrange + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_update_entrypoint_with_launch_object(); + + // Act + result = update_entrypoint_with_launch_object(&entrypoint, NULL); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_086: [ If unable to allocate the array, then `update_entrypoint_with_launch_object` shall return a non-zero value. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_088: [ If unable to allocate space for the file path, then `update_entrypoint_with_launch_object` shall free the argument array and return a non-zero value. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_091: [ If unable to allocate space for the argument, then `update_entrypoint_with_launch_object` shall free the argument array and return a non - zero value. ] */ +TEST_FUNCTION(update_entrypoint_with_launch_object_SCENARIO_negative_tests) +{ + // Arrange + int negativeTestsInitResult = umock_c_negative_tests_init(); + ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult); + + int result; + char * process_argv[] = { + "program.exe", + "control.id" + }; + OUTPROCESS_LOADER_ENTRYPOINT entrypoint = { + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, + NULL, + NULL, + sizeof(process_argv), + process_argv, + 0 + }; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_update_entrypoint_with_launch_object(); + umock_c_negative_tests_snapshot(); + + ASSERT_ARE_EQUAL(int, negative_test_index, umock_c_negative_tests_call_count()); + for (size_t i = 0; i < umock_c_negative_tests_call_count(); ++i) { + if (skipNegativeTest(i)) { + printf("%s: Skipping negative tests: %zx\n", __FUNCTION__, i); + continue; + } + printf("%s: Running negative tests: %zx\n", __FUNCTION__, i); + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + // Act + result = update_entrypoint_with_launch_object(&entrypoint, NULL); + + // Assert + ASSERT_ARE_NOT_EQUAL(int, 0, result); + + // Cleanup + } + + // Cleanup + umock_c_negative_tests_deinit(); +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_093: [ `validate_launch_arguments` shall retrieve the file path, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `path` as `name`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_095: [ `validate_launch_arguments` shall test for the optional parameter grace period, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `grace.period.ms` as `name`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_096: [ `validate_launch_arguments` shall retrieve the grace period (if provided), by calling `double json_object_get_number(const JSON_Object * object, const char * name)` passing `grace.period.ms` as `name`. ] */ +/* Tests_SRS_OUTPROCESS_LOADER_27_097: [ If no errors are encountered, then `validate_launch_arguments` shall return zero. ] */ +TEST_FUNCTION(validate_launch_arguments_SCENARIO_success) +{ + // Arrange + static const double GRACE_PERIOD_MS = 500; + + int result; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + + // Act + result = validate_launch_arguments(NULL); + + // Assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + ASSERT_ARE_EQUAL(int, 0, result); + + // Cleanup +} + + +/* Tests_SRS_OUTPROCESS_LOADER_27_094: [ If unable to retrieve the file path, then `validate_launch_arguments` shall return a non-zero value. ] */ +TEST_FUNCTION(validate_launch_arguments_SCENARIO_negative_tests) +{ + // Arrange + int negativeTestsInitResult = umock_c_negative_tests_init(); + ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult); + + static const double GRACE_PERIOD_MS = 500; + + int result; + + // Expected call listing + umock_c_reset_all_calls(); + expected_calls_validate_launch_arguments(&GRACE_PERIOD_MS); + umock_c_negative_tests_snapshot(); + + ASSERT_ARE_EQUAL(int, negative_test_index, umock_c_negative_tests_call_count()); + for (size_t i = 0; i < umock_c_negative_tests_call_count(); ++i) { + if (skipNegativeTest(i)) { + printf("%s: Skipping negative tests: %zx\n", __FUNCTION__, i); + continue; + } + printf("%s: Running negative tests: %zx\n", __FUNCTION__, i); + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + // Act + result = validate_launch_arguments(NULL); + + // Assert + ASSERT_ARE_NOT_EQUAL(int, 0, result); + + // Cleanup + } + + // Cleanup + umock_c_negative_tests_deinit(); +} + END_TEST_SUITE(OutprocessLoader_UnitTests); diff --git a/core/tests/outprocess_module_ut/outprocess_module_ut.c b/core/tests/outprocess_module_ut/outprocess_module_ut.c index f7a6f356..f9a35a5d 100644 --- a/core/tests/outprocess_module_ut/outprocess_module_ut.c +++ b/core/tests/outprocess_module_ut/outprocess_module_ut.c @@ -69,8 +69,10 @@ void my_gballoc_free(void* ptr) //Globals //============================================================================= -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { diff --git a/doc/devbox_setup.md b/doc/devbox_setup.md index b397ebb2..a13d3dd9 100644 --- a/doc/devbox_setup.md +++ b/doc/devbox_setup.md @@ -39,7 +39,7 @@ This section shows you how to set up a development environment for the Azure IoT ``` sudo apt-get update - sudo apt-get install curl build-essential libcurl4-openssl-dev git cmake libssl-dev uuid-dev valgrind libglib1.0-dev + sudo apt-get install curl build-essential libcurl4-openssl-dev git cmake libssl-dev uuid-dev valgrind libglib2.0-dev libtool autoconf ``` > Note: libglib1.0-dev is required for ble module/sample. diff --git a/jenkins/ubuntu1510_c.sh b/jenkins/ubuntu-14.04_c.sh similarity index 79% rename from jenkins/ubuntu1510_c.sh rename to jenkins/ubuntu-14.04_c.sh index 14f330ac..6bef8f1a 100755 --- a/jenkins/ubuntu1510_c.sh +++ b/jenkins/ubuntu-14.04_c.sh @@ -6,6 +6,6 @@ build_root=$(cd "$(dirname "$0")/.." && pwd) cd $build_root # -- C -- -./tools/build.sh --run-unittests --enable-nodejs-binding --enable-java-binding --enable-java-remote-modules --enable-dotnet-core-binding "$@" #-x +./tools/build.sh --run-unittests --enable-nodejs-binding --enable-java-binding --disable-native-remote-modules --enable-dotnet-core-binding "$@" #-x [ $? -eq 0 ] || exit $? \ No newline at end of file diff --git a/modules/azure_functions/tests/azure_functions_ut/azure_functions_ut.c b/modules/azure_functions/tests/azure_functions_ut/azure_functions_ut.c index 000c4fe7..a2bb6d63 100644 --- a/modules/azure_functions/tests/azure_functions_ut/azure_functions_ut.c +++ b/modules/azure_functions/tests/azure_functions_ut/azure_functions_ut.c @@ -51,9 +51,10 @@ MOCKABLE_FUNCTION(, JSON_Object*, json_value_get_object, const JSON_Value *, val #include "azure_functions.h" - -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) diff --git a/proxy/CMakeLists.txt b/proxy/CMakeLists.txt index 69e0a99d..f2e6844a 100644 --- a/proxy/CMakeLists.txt +++ b/proxy/CMakeLists.txt @@ -5,3 +5,4 @@ cmake_minimum_required(VERSION 2.8.12) add_subdirectory(gateway) add_subdirectory(modules) +add_subdirectory(message) diff --git a/proxy/gateway/java/nanomsg-binding/java_nanomsg.c b/proxy/gateway/java/nanomsg-binding/java_nanomsg.c index ffe89969..071815d2 100644 --- a/proxy/gateway/java/nanomsg-binding/java_nanomsg.c +++ b/proxy/gateway/java/nanomsg-binding/java_nanomsg.c @@ -5,11 +5,15 @@ #include JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1errno(JNIEnv *env, jobject obj) { + (void)env; + (void)obj; + return nn_errno(); } JNIEXPORT jstring JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror (JNIEnv *env, jobject obj, jint errnum) { + (void)obj; const char *error = nn_strerror(errnum); if (error == 0) error = ""; @@ -18,6 +22,7 @@ JNIEXPORT jstring JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary jthrowable exception = (*env)->ExceptionOccurred(env); if (error_msg == NULL || exception) { + error = ""; (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); } @@ -27,33 +32,45 @@ JNIEXPORT jstring JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1socket (JNIEnv *env, jobject obj, jint domain, jint protocol) { + (void)env; + (void)obj; + return nn_socket(domain, protocol); } JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1close (JNIEnv *env, jobject obj, jint socket) { + (void)env; + (void)obj; + return nn_close(socket); } JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1bind (JNIEnv *env, jobject obj, jint socket, jstring address) { + (void)obj; const char *addr = (*env)->GetStringUTFChars(env, address, NULL); - + return nn_bind(socket, addr); } JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1shutdown (JNIEnv *env, jobject obj, jint socket, jint endpoint) { + (void)env; + (void)obj; + return nn_shutdown(socket, endpoint); } JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1send (JNIEnv *env, jobject obj, jint socket, jbyteArray buffer, jint flags) { + (void)obj; + jint result = -1; if (buffer != NULL) { jsize length = (*env)->GetArrayLength(env, buffer); jbyte* cbuffer = (*env)->GetByteArrayElements(env, buffer, 0); - + if (cbuffer == NULL) { result = -1; @@ -71,6 +88,7 @@ JNIEXPORT jint JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn JNIEXPORT jbyteArray JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv (JNIEnv *env, jobject obj, jint socket, jint flags) { + (void)obj; void *buf = NULL; int nbytes = nn_recv(socket, &buf, NN_MSG, flags); jbyteArray result = 0; @@ -79,7 +97,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibr } else { result = (*env)->NewByteArray(env, nbytes); - + if (result == NULL) { result = 0; } @@ -95,15 +113,16 @@ JNIEXPORT jbyteArray JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibr nn_freemsg(buf); } - + return result; } JNIEXPORT jobject JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_getSymbols (JNIEnv *env, jclass clazz) { + (void)clazz; jobject result = NULL; jclass jMap_class = (*env)->FindClass(env, "java/util/HashMap"); - + jthrowable exception = (*env) -> ExceptionOccurred(env); if (jMap_class == NULL || exception) { @@ -197,4 +216,4 @@ JNIEXPORT jobject JNICALL Java_com_microsoft_azure_gateway_remote_NanomsgLibrary } return result; -} \ No newline at end of file +} diff --git a/proxy/gateway/java/nanomsg-binding/tests/java_nanomsg_ut.c b/proxy/gateway/java/nanomsg-binding/tests/java_nanomsg_ut.c index ddd8eb49..2e7ab1f6 100644 --- a/proxy/gateway/java/nanomsg-binding/tests/java_nanomsg_ut.c +++ b/proxy/gateway/java/nanomsg-binding/tests/java_nanomsg_ut.c @@ -29,8 +29,10 @@ #include #include +#ifdef WIN32 + static TEST_MUTEX_HANDLE g_dllByDll; +#endif static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; #define ENABLE_MOCKS #include @@ -62,28 +64,34 @@ MOCK_FUNCTION_END(methodID) jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...) { + (void)env; + (void)clazz; + (void)methodID; return (jobject)0x42; } jobject CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...) { + (void)env; + (void)obj; + (void)methodID; return (jobject)0x42; } MOCK_FUNCTION_WITH_CODE(JNICALL, jstring, NewStringUTF, JNIEnv*, env, const char*, utf); -jstring jstr = (jstring)utf; + jstring jstr = (jstring)utf; MOCK_FUNCTION_END(jstr) MOCK_FUNCTION_WITH_CODE(JNICALL, const char *, GetStringUTFChars, JNIEnv*, env, jstring, str, jboolean*, isCopy); -char *stringChars = "Test"; + char *stringChars = "Test"; MOCK_FUNCTION_END(stringChars) MOCK_FUNCTION_WITH_CODE(JNICALL, jsize, GetArrayLength, JNIEnv*, env, jarray, arr); -jsize size = sizeof(arr); + jsize size = sizeof(arr); MOCK_FUNCTION_END(size) MOCK_FUNCTION_WITH_CODE(JNICALL, jbyteArray, NewByteArray, JNIEnv*, env, jsize, len); -jbyteArray arr =(jbyteArray)malloc(1); + jbyteArray arr =(jbyteArray)malloc(1); MOCK_FUNCTION_END(arr) MOCK_FUNCTION_WITH_CODE(JNICALL, void, GetByteArrayRegion, JNIEnv*, env, jbyteArray, arr, jsize, start, jsize, len, jbyte*, buf); @@ -101,6 +109,7 @@ MOCK_FUNCTION_END() MOCKABLE_FUNCTION(JNICALL, void, DeleteLocalRef, JNIEnv*, env, jobject, obj); void my_DeleteLocalRef(JNIEnv* env, jobject obj) { + (void)env; free((void*)obj); } @@ -261,8 +270,6 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1errno_s umock_c_reset_all_calls(); jobject jObject = (jobject)0x42; - jint err = (jint)21; - jclass clazz = (jclass)0x42; STRICT_EXPECTED_CALL(nn_errno()) .SetReturn(EAGAIN); @@ -281,7 +288,6 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerro jobject jObject = (jobject)0x42; jint err = (jint)21; - jclass clazz = (jclass)0x42; const char* error = "Error"; STRICT_EXPECTED_CALL(nn_strerror(err)) @@ -293,7 +299,7 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerro .IgnoreArgument(1); //Act - jstring result = Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror(global_env, jObject, err); + (void)Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror(global_env, jObject, err); } TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror_returns_empty) @@ -303,7 +309,6 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerro jobject jObject = (jobject)0x42; jint err = (jint)21; - jclass clazz = (jclass)0x42; const char* error = "Error"; STRICT_EXPECTED_CALL(nn_strerror(err)) @@ -315,7 +320,7 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerro .IgnoreArgument(1); //Act - jstring result = Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror(global_env, jObject, err); + (void)Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror(global_env, jObject, err); } TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror_returns_empty_if_exception) @@ -325,7 +330,6 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerro jobject jObject = (jobject)0x42; jint err = (jint)21; - jclass clazz = (jclass)0x42; const char* error = "Error"; jthrowable exception = (jthrowable)0x42; @@ -341,7 +345,7 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerro .IgnoreArgument(1); //Act - jstring result = Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror(global_env, jObject, err); + (void)Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1strerror(global_env, jObject, err); } TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1socket_success) @@ -503,7 +507,6 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv_su jobject jObject = (jobject)0x42; jint socket = (jint)1; - jbyteArray buffer = (jbyteArray)0x42; jint flags = (jint)1; int expectedResult = 4; void *buf = NULL; @@ -514,9 +517,9 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv_su STRICT_EXPECTED_CALL(SetByteArrayRegion(IGNORED_PTR_ARG, IGNORED_PTR_ARG, 0, expectedResult, IGNORED_PTR_ARG)); STRICT_EXPECTED_CALL(ExceptionOccurred(IGNORED_PTR_ARG)) .IgnoreArgument(1); - + //Act - jbyteArray result = Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv(global_env, jObject, socket, flags); + (void)Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv(global_env, jObject, socket, flags); } TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv_return_null_if_no_message) @@ -526,11 +529,8 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv_re jobject jObject = (jobject)0x42; jint socket = (jint)1; - jbyteArray buffer = (jbyteArray)0x42; jint flags = (jint)1; - int expectedResult = 4; - void *buf = NULL; - + STRICT_EXPECTED_CALL(nn_recv(IGNORED_NUM_ARG, IGNORED_PTR_ARG, NN_MSG, NN_DONTWAIT)) .IgnoreAllArguments() .SetReturn(-1); @@ -549,10 +549,8 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv_re jobject jObject = (jobject)0x42; jint socket = (jint)1; - jbyteArray buffer = (jbyteArray)0x42; jint flags = (jint)1; int expectedResult = 4; - void *buf = NULL; STRICT_EXPECTED_CALL(nn_recv(IGNORED_NUM_ARG, IGNORED_PTR_ARG, NN_MSG, NN_DONTWAIT)) .IgnoreAllArguments() @@ -575,10 +573,8 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_nn_1recv_re jobject jObject = (jobject)0x42; jint socket = (jint)1; - jbyteArray buffer = (jbyteArray)0x42; jint flags = (jint)1; int expectedResult = 4; - void *buf = NULL; jthrowable exception = (jthrowable)0x42; STRICT_EXPECTED_CALL(nn_recv(IGNORED_NUM_ARG, IGNORED_PTR_ARG, NN_MSG, NN_DONTWAIT)) @@ -615,7 +611,7 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_getSymbols_ STRICT_EXPECTED_CALL(nn_symbol(1, IGNORED_PTR_ARG)) .IgnoreArgument(2) .SetReturn(NULL); - + //Act jobject result = Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_getSymbols(global_env, jClass); @@ -643,13 +639,10 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_getSymbols_ umock_c_reset_all_calls(); jclass jClass = (jclass)0x42; - jobject jObject = (jobject)0x42; - jmethodID jMethodId = (jmethodID)0x42; char* symbol = "NN_PAIR"; jthrowable exception = (jthrowable)0x42; - int tests = 0; - tests = umock_c_negative_tests_init(); + umock_c_negative_tests_init(); STRICT_EXPECTED_CALL(nn_symbol(0, IGNORED_PTR_ARG)) .IgnoreArgument(2) @@ -694,13 +687,11 @@ TEST_FUNCTION(Java_com_microsoft_azure_gateway_remote_NanomsgLibrary_getSymbols_ umock_c_reset_all_calls(); jclass jClass = (jclass)0x42; - jobject jObject = (jobject)0x42; jmethodID jMethodId = (jmethodID)0x42; char* symbol = "NN_PAIR"; jthrowable exception = (jthrowable)0x42; - int tests = 0; - tests = umock_c_negative_tests_init(); + umock_c_negative_tests_init(); STRICT_EXPECTED_CALL(FindClass(IGNORED_PTR_ARG, IGNORED_PTR_ARG)) .IgnoreAllArguments() diff --git a/proxy/gateway/native/tests/proxy_gateway_ut/proxy_gateway_ut.c b/proxy/gateway/native/tests/proxy_gateway_ut/proxy_gateway_ut.c index 431479a5..5309401f 100644 --- a/proxy/gateway/native/tests/proxy_gateway_ut/proxy_gateway_ut.c +++ b/proxy/gateway/native/tests/proxy_gateway_ut/proxy_gateway_ut.c @@ -36,8 +36,10 @@ static uint64_t negative_tests_to_skip; #include #include -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; static void * diff --git a/proxy/message/CMakeLists.txt b/proxy/message/CMakeLists.txt new file mode 100644 index 00000000..367c1e1f --- /dev/null +++ b/proxy/message/CMakeLists.txt @@ -0,0 +1,6 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +cmake_minimum_required(VERSION 2.8.12) + +add_subdirectory(tests) diff --git a/proxy/message/src/control_message.c b/proxy/message/src/control_message.c index f525e4a2..3aca901a 100755 --- a/proxy/message/src/control_message.c +++ b/proxy/message/src/control_message.c @@ -31,7 +31,7 @@ static int parse_uint32_t(const unsigned char* source, size_t sourceSize, size_t else { *parsed = 4; - *value = + *value = (source[position + 0] << 24) | (source[position + 1] << 16) | (source[position + 2] << 8) | @@ -59,7 +59,7 @@ static int parse_memory_chunk(const unsigned char* source, size_t sourceSize, si else { if (chunk_size == 0) - { + { *value = NULL; *parsed = current_parsed; *size = chunk_size; @@ -116,22 +116,22 @@ int parse_create_message(const unsigned char* source, size_t sourceSize, size_t (void)parsed; init_create_message_contents(create_msg); /* initialize to NULL for easy cleanup */ - /* - * Parse the number of uris - * We already know we won't extend past the end of the array - - * it was checked before entering this function - */ + /* + * Parse the number of uris + * We already know we won't extend past the end of the array - + * it was checked before entering this function + */ - if (position + 2 > sourceSize) - { - LogError("couldn't parse message uri"); - result = __LINE__; - } - else - { - create_msg->gateway_message_version = (uint8_t)source[position++]; - /*Codes_SRS_CONTROL_MESSAGE_17_012: [ This function shall read the uri_type, uri_size, and the uri. ]*/ - create_msg->uri.uri_type = (uint8_t)source[position++]; + if (position + 2 > sourceSize) + { + LogError("couldn't parse message uri"); + result = __LINE__; + } + else + { + create_msg->gateway_message_version = (uint8_t)source[position++]; + /*Codes_SRS_CONTROL_MESSAGE_17_012: [ This function shall read the uri_type, uri_size, and the uri. ]*/ + create_msg->uri.uri_type = (uint8_t)source[position++]; /*Codes_SRS_CONTROL_MESSAGE_17_013: [ This function shall allocate uri_size bytes for the url. ]*/ if (parse_memory_chunk(source, @@ -139,7 +139,7 @@ int parse_create_message(const unsigned char* source, size_t sourceSize, size_t position, ¤t_parsed, &(create_msg->uri.uri_size), - (unsigned char**)(&(create_msg->uri.uri))) != 0) + (unsigned char**)&(create_msg->uri.uri)) != 0) { LogError("unable to parse a uri"); result = __LINE__; @@ -150,33 +150,35 @@ int parse_create_message(const unsigned char* source, size_t sourceSize, size_t result = 0; } } + if (result != 0) { /* Did not come out of parsing the URLs OK */ - /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ + /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ free_create_message_contents(create_msg); } - else - { - /*Codes_SRS_CONTROL_MESSAGE_17_014: [ This function shall read the args_size and args from the byte stream. ]*/ - /*Codes_SRS_CONTROL_MESSAGE_17_015: [ This function shall allocate args_size + 1 bytes for the args and will null terminate the memory block. ]*/ - if (parse_memory_chunk(source, - sourceSize, - position, - ¤t_parsed, - &(create_msg->args_size), - (unsigned char**)(&(create_msg->args))) != 0) - { - LogError("unable to parse a module args"); - result = __LINE__; - /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ - free_create_message_contents(create_msg); - } - else + else + { + /*Codes_SRS_CONTROL_MESSAGE_17_014: [ This function shall read the args_size and args from the byte stream. ]*/ + /*Codes_SRS_CONTROL_MESSAGE_17_015: [ This function shall allocate args_size + 1 bytes for the args and will null terminate the memory block. ]*/ + if (parse_memory_chunk(source, + sourceSize, + position, + ¤t_parsed, + &(create_msg->args_size), + (unsigned char **)&(create_msg->args)) != 0) + { + LogError("unable to parse a module args"); + result = __LINE__; + /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ + free_create_message_contents(create_msg); + } + else { result = 0; - } - } + } + } + return result; } @@ -210,15 +212,15 @@ CONTROL_MESSAGE * ControlMessage_CreateFromByteArray(const unsigned char* source { size_t currentPosition = 2; /*current position is always the first character that "we are about to look at"*/ int32_t parsed; /*reused in all parsings*/ - /*Codes_SRS_CONTROL_MESSAGE_17_005: [ This function shall read the version, type and size from the byte stream. ]*/ + /*Codes_SRS_CONTROL_MESSAGE_17_005: [ This function shall read the version, type and size from the byte stream. ]*/ uint8_t messageVersion = (uint8_t)source[currentPosition++]; - uint8_t messageType = (uint8_t)source[currentPosition++]; - uint32_t messageSize; - /* we already know buffer is at least BASE_MESSAGE_SIZE - this will always return OK */ - (void)parse_uint32_t(source, size, currentPosition, &parsed, &messageSize); + uint8_t messageType = (uint8_t)source[currentPosition++]; + uint32_t messageSize; + /* we already know buffer is at least BASE_MESSAGE_SIZE - this will always return OK */ + (void)parse_uint32_t(source, size, currentPosition, &parsed, &messageSize); currentPosition += parsed; - /*Codes_SRS_CONTROL_MESSAGE_17_006: [ If the size embedded in the message is not the same as size parameter then this function shall fail and return NULL. ]*/ - if (messageSize != size) + /*Codes_SRS_CONTROL_MESSAGE_17_006: [ If the size embedded in the message is not the same as size parameter then this function shall fail and return NULL. ]*/ + if ((size_t)messageSize != size) { LogError("message size is inconsistent"); result = NULL; @@ -238,14 +240,14 @@ CONTROL_MESSAGE * ControlMessage_CreateFromByteArray(const unsigned char* source result = (CONTROL_MESSAGE *)malloc(sizeof(CONTROL_MESSAGE_MODULE_CREATE)); if (result != NULL) { - /*Codes_SRS_CONTROL_MESSAGE_17_024: [ Upon valid reading of the byte stream, this function shall assign the message version and type into the CONTROL_MESSAGE base structure. ]*/ - result->version = messageVersion; + /*Codes_SRS_CONTROL_MESSAGE_17_024: [ Upon valid reading of the byte stream, this function shall assign the message version and type into the CONTROL_MESSAGE base structure. ]*/ + result->version = messageVersion; result->type = messageType; if (parse_create_message( - source, - size, - currentPosition, - &parsed, + source, + size, + currentPosition, + &parsed, (CONTROL_MESSAGE_MODULE_CREATE*)result) != 0) { /*Codes_SRS_CONTROL_MESSAGE_17_022: [ This function shall release all allocated memory upon failure. ]*/ diff --git a/proxy/message/tests/CMakeLists.txt b/proxy/message/tests/CMakeLists.txt new file mode 100644 index 00000000..a3e46d23 --- /dev/null +++ b/proxy/message/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +#Copyright (c) Microsoft. All rights reserved. +#Licensed under the MIT license. See LICENSE file in the project root for full license information. + +cmake_minimum_required(VERSION 2.8.12) + +add_subdirectory(control_msg_ut) diff --git a/core/tests/control_msg_ut/CMakeLists.txt b/proxy/message/tests/control_msg_ut/CMakeLists.txt similarity index 82% rename from core/tests/control_msg_ut/CMakeLists.txt rename to proxy/message/tests/control_msg_ut/CMakeLists.txt index 3a1033f3..4c561ee2 100644 --- a/core/tests/control_msg_ut/CMakeLists.txt +++ b/proxy/message/tests/control_msg_ut/CMakeLists.txt @@ -17,6 +17,7 @@ set(${theseTestsName}_c_files set(${theseTestsName}_h_files ) +include_directories(../../inc) include_directories(${GW_INC}) -build_c_test_artifacts(${theseTestsName} ON "tests/UnitTests") \ No newline at end of file +build_c_test_artifacts(${theseTestsName} ON "tests/UnitTests") diff --git a/core/tests/control_msg_ut/control_message_ut.c b/proxy/message/tests/control_msg_ut/control_message_ut.c similarity index 99% rename from core/tests/control_msg_ut/control_message_ut.c rename to proxy/message/tests/control_msg_ut/control_message_ut.c index e46b62e4..46df6e99 100644 --- a/core/tests/control_msg_ut/control_message_ut.c +++ b/proxy/message/tests/control_msg_ut/control_message_ut.c @@ -10,8 +10,10 @@ #include "control_message.h" -static TEST_MUTEX_HANDLE g_testByTest; +#ifdef WIN32 static TEST_MUTEX_HANDLE g_dllByDll; +#endif +static TEST_MUTEX_HANDLE g_testByTest; #include "message.h" diff --git a/core/tests/control_msg_ut/main.c b/proxy/message/tests/control_msg_ut/main.c similarity index 100% rename from core/tests/control_msg_ut/main.c rename to proxy/message/tests/control_msg_ut/main.c diff --git a/proxy/modules/native_module_host/tests/native_module_host_ut/native_module_host_ut.c b/proxy/modules/native_module_host/tests/native_module_host_ut/native_module_host_ut.c index 457bbf6e..09e94f8e 100644 --- a/proxy/modules/native_module_host/tests/native_module_host_ut/native_module_host_ut.c +++ b/proxy/modules/native_module_host/tests/native_module_host_ut/native_module_host_ut.c @@ -14,12 +14,15 @@ #include "umocktypes_stdint.h" #include "azure_c_shared_utility/macro_utils.h" + +#ifdef WIN32 +static TEST_MUTEX_HANDLE g_dllByDll; +#endif + #ifdef __cplusplus extern "C" { #endif - - static bool malloc_will_fail = false; static size_t malloc_fail_count = 0; static size_t malloc_count = 0; @@ -235,7 +238,6 @@ static void my_ModuleLoader_FreeModuleConfiguration(const MODULE_LOADER* loader, static TEST_MUTEX_HANDLE g_testByTest; -static TEST_MUTEX_HANDLE g_dllByDll; DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) diff --git a/core/devdoc/control_message_requirements.md b/proxy/outprocess/devdoc/control_message_requirements.md similarity index 100% rename from core/devdoc/control_message_requirements.md rename to proxy/outprocess/devdoc/control_message_requirements.md diff --git a/core/devdoc/out-process-control-messages.md b/proxy/outprocess/devdoc/out-process-control-messages.md similarity index 100% rename from core/devdoc/out-process-control-messages.md rename to proxy/outprocess/devdoc/out-process-control-messages.md diff --git a/core/devdoc/outprocess_hld.md b/proxy/outprocess/devdoc/outprocess_hld.md similarity index 100% rename from core/devdoc/outprocess_hld.md rename to proxy/outprocess/devdoc/outprocess_hld.md diff --git a/proxy/outprocess/devdoc/outprocess_loader_requirements.md b/proxy/outprocess/devdoc/outprocess_loader_requirements.md new file mode 100644 index 00000000..394a4783 --- /dev/null +++ b/proxy/outprocess/devdoc/outprocess_loader_requirements.md @@ -0,0 +1,384 @@ +Out of Process Module Loader Requirements +========================================= + +Overview +-------- + +The out of process (outprocess) module loader implements loading of gateway modules that are proxies for modules hosted out of process. + +References +---------- + +[Module loader design](./module_loaders.md) +[Out of Process Module HLD](./outprocess_hld.md) +[Out of Process Module requirements](./outprocess_module_requirements.md) + +Exposed API +----------- + +```C +#define OUTPROCESS_LOADER_ACTIVATION_TYPE_VALUES \ + OUTPROCESS_LOADER_ACTIVATION_NONE + +/** + * @brief Enumeration listing all supported module loaders + */ +DEFINE_ENUM(OUTPROCESS_LOADER_ACTIVATION_TYPE, OUTPROCESS_LOADER_ACTIVATION_TYPE_VALUES); + +/** @brief Structure to load an out of process proxy module */ +typedef struct OUTPROCESS_LOADER_ENTRYPOINT_TAG +{ + /** + * @brief Tells the module and loader how much control the gateway + * has over the module host process. + */ + OUTPROCESS_LOADER_ACTIVATION_TYPE activation_type; + /** @brief The URI for the module host control channel.*/ + STRING_HANDLE control_id; + /** @brief The URI for the gateway message channel.*/ + STRING_HANDLE message_id; + /** @brief controls timeout for ipc retries. */ + unsigned int default_wait; +} OUTPROCESS_LOADER_ENTRYPOINT; + +/** @brief The API for the out of process proxy module loader. */ +MOCKABLE_FUNCTION(, GATEWAY_EXPORT const MODULE_LOADER*, OutprocessLoader_Get); +``` + + +OutprocessModuleLoader_Load +--------------------------- + +```C +MODULE_LIBRARY_HANDLE OutprocessModuleLoader_Load(const MODULE_LOADER* loader, const void* entrypoint) +``` + +Loads the outprocess module according to the `entrypoint` given. The outprocess module is statically linked in the gateway library, so there is no shared library to load. + +**SRS_OUTPROCESS_LOADER_17_001: [** If `loader` or `entrypoint` are `NULL`, then this function shall return `NULL`. **]** + +**SRS_OUTPROCESS_LOADER_17_042: [** If the loader type is not `OUTPROCESS`, then this function shall return `NULL`. **]** + +**SRS_OUTPROCESS_LOADER_17_002: [** If the entrypoint's `control_id` is `NULL`, then this function shall return `NULL`. **]** + +**SRS_OUTPROCESS_LOADER_27_003: [** If the entrypoint's `activation_type` is invalid, then `OutprocessModuleLoader_Load` shall return `NULL`. **]** + +**SRS_OUTPROCESS_LOADER_17_004: [** The loader shall allocate memory for the loader handle. **]** + +**SRS_OUTPROCESS_LOADER_27_005: [** *Launch* - `OutprocessModuleLoader_Load` shall launch the child process identified by the entrypoint. **]** + +**SRS_OUTPROCESS_LOADER_17_006: [** The loader shall store a pointer to the `MODULE_API` in the loader handle. **]** + +**SRS_OUTPROCESS_LOADER_17_007: [** Upon success, this function shall return a valid pointer to the loader handle. **]** + +**SRS_OUTPROCESS_LOADER_17_008: [** If any call in this function fails, this function shall return `NULL`. **]** + + +OutprocessModuleLoader_GetModuleApi +----------------------------------- + +```C +extern const MODULE_API* `OutprocessModuleLoader_GetModuleApi`(const MODULE_LOADER* loader, MODULE_LIBRARY_HANDLE moduleLibraryHandle); +``` + +The out of process module is linked with the outprocess loader. + +**SRS_OUTPROCESS_LOADER_17_009: [** This function shall return a valid pointer to the `MODULE_API` MODULE_API. **]** + + +OutprocessModuleLoader_Unload +----------------------------- + +```C +void OutprocessModuleLoader_Unload(const MODULE_LOADER* loader, MODULE_LIBRARY_HANDLE moduleLibraryHandle); +``` + +**SRS_OUTPROCESS_LOADER_17_010: [** This function shall do nothing if `moduleLibraryHandle` is `NULL`. **]** + +**SRS_OUTPROCESS_LOADER_17_011: [** This function shall release all resources created by this loader. **]** + + +OutprocessModuleLoader_ParseEntrypointFromJson +---------------------------------------------- + +```C +void* OutprocessModuleLoader_ParseEntrypointFromJson(const struct MODULE_LOADER_TAG* loader, const JSON_Value* json); +``` + +Parses entrypoint JSON as it applies to out of process modules and returns a pointer to the parsed data. + +**SRS_OUTPROCESS_LOADER_17_012: [** This function shall return `NULL` if `json` is `NULL`. **]** + +**SRS_OUTPROCESS_LOADER_17_013: [** This function shall return `NULL` if `activation.type` is not present in `json`. **]** + +**SRS_OUTPROCESS_LOADER_27_014: [** This function shall return `NULL` if `activation.type` is `OUTPROCESS_LOADER_ACTIVATION_INVALID`. **]** + +**SRS_OUTPROCESS_LOADER_27_015: [** *Launch* - `OutprocessModuleLoader_ParseEntrypointFromJson` shall validate the launch parameters. **]** + +**SRS_OUTPROCESS_LOADER_17_041: [** This function shall return `NULL` if `control.id` is not present in `json`. **]** + +**SRS_OUTPROCESS_LOADER_17_016: [** This function shall allocate a `OUTPROCESS_LOADER_ENTRYPOINT` structure. **]** + +**SRS_OUTPROCESS_LOADER_17_043: [** This function shall read the `timeout` value. **]** + +**SRS_OUTPROCESS_LOADER_17_044: [** If `timeout` is set, the `default_wait` shall be set to this value, else it will be set to a default of 1000 ms. **]** + +This timeout controls how long a module will wait before retrying to connect to remote module on startup. If remote module is expected to take a long time to start, setting this will reduce the number of retires before success. + +**SRS_OUTPROCESS_LOADER_17_017: [** This function shall assign the entrypoint `activation_type` to `NONE`. **]** + +**SRS_OUTPROCESS_LOADER_17_018: [** This function shall assign the entrypoint `control_id` to the string value of "ipc://" + "control.id" in `json`. **]** + +**SRS_OUTPROCESS_LOADER_27_020: [** *Launch* - `OutprocessModuleLoader_ParseEntrypointFromJson` shall update the entry point with the parsed launch parameters. **]** + +**SRS_OUTPROCESS_LOADER_17_019: [** This function shall assign the entrypoint `message_id` to the string value of "ipc://" + "message.id" in `json`, `NULL` if not present. **]** + +**SRS_OUTPROCESS_LOADER_17_021: [** This function shall return `NULL` if any calls fails. **]** + +**SRS_OUTPROCESS_LOADER_17_022: [** This function shall return a valid pointer to an `OUTPROCESS_LOADER_ENTRYPOINT` on success. **]** + + +OutprocessModuleLoader_FreeEntrypoint +------------------------------------- + +```C +void OutprocessModuleLoader_FreeEntrypoint(const struct MODULE_LOADER_TAG* loader, void* entrypoint) +``` + +**SRS_OUTPROCESS_LOADER_17_023: [** This function shall release all resources allocated by `OutprocessModuleLoader_ParseEntrypointFromJson`. **]** + + +OutprocessModuleLoader_ParseConfigurationFromJson +------------------------------------------------- + +```C +MODULE_LOADER_BASE_CONFIGURATION* OutprocessModuleLoader_ParseConfigurationFromJson(const struct MODULE_LOADER_TAG* loader, const JSON_Value* json); +``` + +**SRS_OUTPROCESS_LOADER_17_024: [** The out of process loader does not have any configuration. So this method shall return `NULL`. **]** + + +OutprocessModuleLoader_FreeConfiguration +---------------------------------------- + +```C +void OutprocessModuleLoader_FreeConfiguration(const struct MODULE_LOADER_TAG* loader, MODULE_LOADER_BASE_CONFIGURATION* configuration); +``` + +The out of process loader does not have any configuration. So there is nothing to free here. + +**SRS_OUTPROCESS_LOADER_17_025: [** This function shall move along, nothing to free here. **]** + + +OutprocessModuleLoader_BuildModuleConfiguration +----------------------------------------------- + +```C +void* OutprocessModuleLoader_BuildModuleConfiguration( + const MODULE_LOADER* loader, + const void* entrypoint, + const void* module_configuration +); +``` + +**SRS_OUTPROCESS_LOADER_17_026: [** This function shall return `NULL` if `entrypoint`, `control_id`, or `module_configuration` is `NULL`. **]** Note: If there is no module configuration, this function expects the configuration will be the string "null". + +**SRS_OUTPROCESS_LOADER_17_027: [** This function shall allocate a `OUTPROCESS_MODULE_CONFIG` structure. **]** + +**SRS_OUTPROCESS_LOADER_17_029: [** If the entrypoint's `message_id` is `NULL`, then the loader shall construct an IPC uri. **]** + +**SRS_OUTPROCESS_LOADER_17_030: [** The loader shall create a unique id, if needed for URI constrution. **]** + +**SRS_OUTPROCESS_LOADER_17_032: [** The message uri shall be composed of "ipc://" + unique id. **]** + +**SRS_OUTPROCESS_LOADER_17_033: [** This function shall allocate and copy each string in `OUTPROCESS_LOADER_ENTRYPOINT` and assign them to the corresponding fields in `OUTPROCESS_MODULE_CONFIG`. **]** + +**SRS_OUTPROCESS_LOADER_17_034: [** This function shall allocate and copy the `module_configuration` string and assign it the `OUTPROCESS_MODULE_CONFIG::outprocess_module_args` field. **]** + +**SRS_OUTPROCESS_LOADER_17_035: [** Upon success, this function shall return a valid pointer to an `OUTPROCESS_MODULE_CONFIG` structure. **]** + +**SRS_OUTPROCESS_LOADER_17_036: [** If any call fails, this function shall return `NULL`. **]** + + +OutprocessModuleLoader_FreeModuleConfiguration +---------------------------------------------- + +```C +void OutprocessModuleLoader_FreeModuleConfiguration(const struct MODULE_LOADER_TAG* loader, const void* module_configuration); +``` + +**SRS_OUTPROCESS_LOADER_17_037: [** This function shall release all memory allocated by `OutprocessModuleLoader_BuildModuleConfiguration`. **]** + + +OutprocessModuleLoader_Get +-------------------------- + +```C +const MODULE_LOADER* OutprocessModuleLoader_Get(void); +``` + +**SRS_OUTPROCESS_LOADER_17_038: [** `OutprocessModuleLoader_Get` shall return a non-`NULL` pointer to a `MODULE_LOADER` struct. **]** + +**SRS_OUTPROCESS_LOADER_17_039: [** `MODULE_LOADER::type` shall be `OUTPROCESS`. **]** + +**SRS_OUTPROCESS_LOADER_17_040: [** `MODULE_LOADER::name` shall be the string 'outprocess'. **]** + + +OutprocessLoader_SpawnChildProcesses +------------------------------------ + +```C +int OutprocessLoader_SpawnChildProcesses(void); +``` + +**SRS_OUTPROCESS_LOADER_27_045: [** *Prerequisite Check* - If no processes have been enqueued, then `OutprocessLoader_SpawnChildProcesses` shall take no action and return zero. **]** + +**SRS_OUTPROCESS_LOADER_27_046: [** *Prerequisite Check* - If child processes are already running, then `OutprocessLoader_SpawnChildProcesses` shall take no action and return zero. **]** + +**SRS_OUTPROCESS_LOADER_27_047: [** `OutprocessLoader_SpawnChildProcesses` shall launch the enqueued child processes by calling `THREADAPI_RESULT ThreadAPI_Create(THREAD_HANDLE * threadHandle, THREAD_START_FUNC func, void * arg)`. **]** + +**SRS_OUTPROCESS_LOADER_27_048: [** If launching the enqueued child processes fails, then `OutprocessLoader_SpawnChildProcesses` shall return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_049: [** If no errors are encountered, then `OutprocessLoader_SpawnChildProcesses` shall return zero. **]** + + +OutprocessLoader_JoinChildProcesses +----------------------------------- + +```C +void OutprocessLoader_JoinChildProcesses(void); +``` + +**SRS_OUTPROCESS_LOADER_27_050: [** *Prerequisite Check* - If no threads are running, then `OutprocessLoader_JoinChildProcesses` shall abandon the effort to join the child processes immediately. **]** + +**SRS_OUTPROCESS_LOADER_27_064: [** `OutprocessLoader_JoinChildProcesses` shall get the count of child processes, by calling `size_t VECTOR_size(VECTOR_HANDLE handle)`. **]** + +**SRS_OUTPROCESS_LOADER_27_063: [** If no processes are running, then `OutprocessLoader_JoinChildProcesses` shall immediately join the child process management thread. **]** + +**SRS_OUTPROCESS_LOADER_27_051: [** `OutprocessLoader_JoinChildProcesses` shall create a timer to test for timeout, by calling `TICK_COUNTER_HANDLE tickcounter_create(void)`. **]** + +**SRS_OUTPROCESS_LOADER_27_052: [** If unable to create a timer, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. **]** + +**SRS_OUTPROCESS_LOADER_27_053: [** `OutprocessLoader_JoinChildProcesses` shall mark the begin time, by calling `int tickcounter_get_current_ms(TICK_COUNTER_HANDLE tick_counter, tickcounter_ms_t * current_ms)` using the handle called by the previous call to `tickcounter_create`. **]** + +**SRS_OUTPROCESS_LOADER_27_054: [** If unable to mark the begin time, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. **]** + +**SRS_OUTPROCESS_LOADER_27_055: [** While awaiting the grace period, `OutprocessLoader_JoinChildProcesses` shall mark the current time, by calling `int tickcounter_get_current_ms(TICK_COUNTER_HANDLE tick_counter, tickcounter_ms_t * current_ms)` using the handle called by the previous call to `tickcounter_create`. **]** + +**SRS_OUTPROCESS_LOADER_27_056: [** If unable to mark the current time, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. **]** + +**SRS_OUTPROCESS_LOADER_27_057: [** While awaiting the grace period, `OutprocessLoader_JoinChildProcesses` shall check if the processes are running, by calling `int uv_loop_alive(const uv_loop_t * loop)` using the result of `uv_default_loop()` for `loop`. **]** + +**SRS_OUTPROCESS_LOADER_27_059: [** If the child processes are not running, `OutprocessLoader_JoinChildProcesses` shall shall immediately join the child process management thread. **]** + +**SRS_OUTPROCESS_LOADER_27_058: [** `OutprocessLoader_JoinChildProcesses` shall await the grace period in 100ms increments, by calling `void ThreadAPI_Sleep(unsigned int milliseconds)` passing 100 for `milliseconds`. **]** + +**SRS_OUTPROCESS_LOADER_27_060: [** If the grace period expired, `OutprocessLoader_JoinChildProcesses` shall get the handle of each child processes, by calling `void * VECTOR_element(VECTOR_HANDLE handle, size_t index)`. **]** + +**SRS_OUTPROCESS_LOADER_27_061: [** If the grace period expired, `OutprocessLoader_JoinChildProcesses` shall signal each child, by calling `int uv_process_kill(uv_process_t * process, int signum)` passing `SIGTERM` for `signum`. **]** + +**SRS_OUTPROCESS_LOADER_27_068: [** `OutprocessLoader_JoinChildProcesses` shall destroy the timer, by calling `void tickcounter_destroy(TICK_COUNTER_HANDLE tick_counter)`. **]** + +**SRS_OUTPROCESS_LOADER_27_062: [** `OutprocessLoader_JoinChildProcesses` shall join the child process management thread, by calling `THREADAPI_RESULT ThreadAPI_Join(THREAD_HANDLE threadHandle, int * res)`. **]** + +**SRS_OUTPROCESS_LOADER_27_065: [** `OutprocessLoader_JoinChildProcesses` shall get the handle of each child processes, by calling `void * VECTOR_element(VECTOR_HANDLE handle, size_t index)`. **]** + +**SRS_OUTPROCESS_LOADER_27_066: [** `OutprocessLoader_JoinChildProcesses` shall free the resources allocated to each child, by calling `void free(void * _Block)` passing the child handle as `_Block`. **]** + +**SRS_OUTPROCESS_LOADER_27_067: [** `OutprocessLoader_JoinChildProcesses` shall destroy the vector of child processes, by calling `void VECTOR_destroy(VECTOR_HANDLE handle)`. **]** + + +launch_child_process_from_entrypoint (*internal*) +------------------------------------------------- + +```C +int launch_child_process_from_entrypoint (OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry); +``` + +**SRS_OUTPROCESS_LOADER_27_098: [** *Prerequisite Check* If a vector for child processes already exists, then `launch_child_process_from_entrypoint` shall not attempt to recreate the vector. **]** + +**SRS_OUTPROCESS_LOADER_27_069: [** `launch_child_process_from_entrypoint` shall attempt to create a vector for child processes (unless previously created), by calling `VECTOR_HANDLE VECTOR_create(size_t elementSize)` using `sizeof(uv_process_t *)` as `elementSize`. **]** + +**SRS_OUTPROCESS_LOADER_27_070: [** If a vector for the child processes does not exist, then `launch_child_process_from_entrypoint` shall return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_071: [** `launch_child_process_from_entrypoint` shall allocate the memory for the child handle, by calling `void * malloc(size_t _Size)` passing `sizeof(uv_process_t)` as `_Size`. **]** + +**SRS_OUTPROCESS_LOADER_27_072: [** If unable to allocate memory for the child handle, then `launch_child_process_from_entrypoint` shall return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_073: [** `launch_child_process_from_entrypoint` shall store the child's handle, by calling `int VECTOR_push_back(VECTOR_HANDLE handle, const void * elements, size_t numElements)` passing the process vector as `handle` and 1 as `numElements`. **]** + +**SRS_OUTPROCESS_LOADER_27_074: [** If unable to store the child's handle, then `launch_child_process_from_entrypoint` shall free the memory allocated to the child process handle and return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_075: [** `launch_child_process_from_entrypoint` shall enqueue the child process to be spawned, by calling `int uv_spawn(uv_loop_t * loop, uv_process_t * handle, const uv_process_options_t * options)` passing the result of `uv_default_loop()` as `loop`, the newly allocated process handle as `handle` and the options parsed from the entrypoint as `options`. **]** + +**SRS_OUTPROCESS_LOADER_27_076: [** If unable to enqueue the child process, then `launch_child_process_from_entrypoint` shall remove the stored handle, free the memory allocated to the child process handle and return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_077: [** `launch_child_process_from_entrypoint` shall spawn the enqueued child processes. **]** + +**SRS_OUTPROCESS_LOADER_27_078: [** If launching the enqueued child processes fails, then `launch_child_process_from_entrypoint` shall return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_079: [** If no errors are encountered, then `launch_child_process_from_entrypoint` shall return zero. **]** + + +spawn_child_processes (*internal*) +---------------------------------- + +```C +int spawn_child_processes (void * context); +``` + +**SRS_OUTPROCESS_LOADER_27_080: [** `spawn_child_processes` shall start the child process management thread, by calling `int uv_run(uv_loop_t * loop, uv_run_mode mode)` passing the result of `uv_default_loop()` for `loop` and `UV_RUN_DEFAULT` for `mode`. **]** + +**SRS_OUTPROCESS_LOADER_27_099: [** `spawn_child_processes` shall return the result of the child process management thread to the parent thread, by calling `void ThreadAPI_Exit(int res)` passing the result of `uv_run()` as `res`. **]** + +**SRS_OUTPROCESS_LOADER_27_081: [** If no errors are encountered, then `spawn_child_processes` shall return zero. **]** + + +update_entrypoint_with_launch_object (*internal*) +-------------------------------------------------------------- + +```C +int update_entrypoint_with_launch_object (OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry, const char * launch_path, JSON_Array * launch_args) +``` + +**SRS_OUTPROCESS_LOADER_27_082: [** `update_entrypoint_with_launch_object` shall retrieve the file path, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `path` as `name`. **]** + +**SRS_OUTPROCESS_LOADER_27_083: [** `update_entrypoint_with_launch_object` shall retrieve the JSON arguments array, by calling `JSON_Array json_object_get_array(const JSON_Object * object, const char * name)` passing `args` as `name`. **]** + +**SRS_OUTPROCESS_LOADER_27_084: [** `update_entrypoint_with_launch_object` shall determine the size of the JSON arguments array, by calling `size_t json_array_get_count(const JSON_Array * array)`. **]** + +**SRS_OUTPROCESS_LOADER_27_085: [** `update_entrypoint_with_launch_object` shall allocate the argument array, by calling `void * malloc(size _Size)` passing the result of `json_array_get_count` plus two, one for passing the file path as the first argument and the second for the NULL terminating pointer required by libUV. **]** + +**SRS_OUTPROCESS_LOADER_27_086: [** If unable to allocate the array, then `update_entrypoint_with_launch_object` shall return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_087: [** `update_entrypoint_with_launch_object` shall allocate the +space necessary to copy the file path, by calling `void * malloc(size _Size)`. **]** + +**SRS_OUTPROCESS_LOADER_27_088: [** If unable to allocate space for the file path, then `update_entrypoint_with_launch_object` shall free the argument array and return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_089: [** `update_entrypoint_with_launch_object` shall retrieve each argument from the JSON arguments array, by calling `const char * json_array_get_string(const JSON_Array * array, size_t index)`. **]** + +**SRS_OUTPROCESS_LOADER_27_090: [** `update_entrypoint_with_launch_object` shall allocate the space necessary for each argument, by calling `void * malloc(size _Size)`. **]** + +**SRS_OUTPROCESS_LOADER_27_091: [** If unable to allocate space for the argument, then `update_entrypoint_with_launch_object` shall free the argument array and return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_092: [** If no errors are encountered, then `update_entrypoint_with_launch_object` shall return zero. **]** + + +validate_launch_arguments (*internal*) +-------------------------------------- + +```C +int validate_launch_arguments (const JSON_Object * launch_object); +``` + +**SRS_OUTPROCESS_LOADER_27_093: [** `validate_launch_arguments` shall retrieve the file path, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `path` as `name`. **]** + +**SRS_OUTPROCESS_LOADER_27_094: [** If unable to retrieve the file path, then `validate_launch_arguments` shall return a non-zero value. **]** + +**SRS_OUTPROCESS_LOADER_27_095: [** `validate_launch_arguments` shall test for the optional parameter grace period, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `grace.period.ms` as `name`. **]** + +**SRS_OUTPROCESS_LOADER_27_096: [** `validate_launch_arguments` shall retrieve the grace period, by calling `double json_object_get_number(const JSON_Object * object, const char * name)` passing `grace.period.ms` as `name`. **]** + +**SRS_OUTPROCESS_LOADER_27_097: [** If no errors are encountered, then `validate_launch_arguments` shall return zero. **]** + diff --git a/core/devdoc/outprocess_module_requirements.md b/proxy/outprocess/devdoc/outprocess_module_requirements.md similarity index 100% rename from core/devdoc/outprocess_module_requirements.md rename to proxy/outprocess/devdoc/outprocess_module_requirements.md diff --git a/proxy/outprocess/inc/module_loaders/outprocess_loader.h b/proxy/outprocess/inc/module_loaders/outprocess_loader.h index c7a2b3b7..b83252d9 100644 --- a/proxy/outprocess/inc/module_loaders/outprocess_loader.h +++ b/proxy/outprocess/inc/module_loaders/outprocess_loader.h @@ -26,7 +26,9 @@ extern "C" #define OUTPROCESS_LOADER_NAME "outprocess" #define OUTPROCESS_LOADER_ACTIVATION_TYPE_VALUES \ - OUTPROCESS_LOADER_ACTIVATION_NONE + OUTPROCESS_LOADER_ACTIVATION_NONE, \ + OUTPROCESS_LOADER_ACTIVATION_LAUNCH, \ + OUTPROCESS_LOADER_ACTIVATION_INVALID \ /** * @brief Enumeration listing all supported module loaders @@ -45,13 +47,29 @@ typedef struct OUTPROCESS_LOADER_ENTRYPOINT_TAG STRING_HANDLE control_id; /** @brief The URI for the gateway message channel.*/ STRING_HANDLE message_id; - /** @brief controls timeout for ipc retries. */ + /** @brief The count of arguments to be handed-off to the child process.*/ + size_t process_argc; + /** @brief The arguments to be handed-off to the child process.*/ + char ** process_argv; + /** @brief controls timeout for ipc retries. */ unsigned int remote_message_wait; } OUTPROCESS_LOADER_ENTRYPOINT; /** @brief The API for the out of process proxy module loader. */ MOCKABLE_FUNCTION(, GATEWAY_EXPORT const MODULE_LOADER*, OutprocessLoader_Get); +/** + * @brief Join the OutprocessLoader child processes (static) + */ +MOCKABLE_FUNCTION(, GATEWAY_EXPORT void, OutprocessLoader_JoinChildProcesses); + +/** +* @brief Spawn the OutprocessLoader child processes (static) +* +* @return Returns 0 on success and non-zero to indicate an error occurred +*/ +MOCKABLE_FUNCTION(, GATEWAY_EXPORT int, OutprocessLoader_SpawnChildProcesses); + #ifdef __cplusplus } #endif diff --git a/proxy/outprocess/src/module_loaders/outprocess_loader.c b/proxy/outprocess/src/module_loaders/outprocess_loader.c index 31e12141..edbba0c8 100644 --- a/proxy/outprocess/src/module_loaders/outprocess_loader.c +++ b/proxy/outprocess/src/module_loaders/outprocess_loader.c @@ -1,16 +1,23 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#include "module_loaders/outprocess_loader.h" + +#include #include +#include + #include "azure_c_shared_utility/gballoc.h" -#include "azure_c_shared_utility/xlogging.h" +#include "azure_c_shared_utility/threadapi.h" +#include "azure_c_shared_utility/tickcounter.h" #include "azure_c_shared_utility/uniqueid.h" #include "azure_c_shared_utility/urlencode.h" +#include "azure_c_shared_utility/vector.h" +#include "azure_c_shared_utility/xlogging.h" #include "parson.h" #include "module.h" #include "module_loader.h" -#include "module_loaders/outprocess_loader.h" #include "module_loaders/outprocess_module.h" DEFINE_ENUM_STRINGS(OUTPROCESS_LOADER_ACTIVATION_TYPE, OUTPROCESS_LOADER_ACTIVATION_TYPE_VALUES); @@ -20,348 +27,701 @@ DEFINE_ENUM_STRINGS(OUTPROCESS_LOADER_ACTIVATION_TYPE, OUTPROCESS_LOADER_ACTIVAT #define IPC_URI_HEAD_SIZE 6 #define MESSAGE_URI_SIZE (INPROC_URI_HEAD_SIZE + LOADER_GUID_SIZE +1) -#define REMOTE_MESSAGE_WAIT_DEFAULT 1000; +#define GRACE_PERIOD_MS_DEFAULT 3000 +#define REMOTE_MESSAGE_WAIT_DEFAULT 1000 +#define GRACE_AWAIT_DELAY_MS 100 typedef struct OUTPROCESS_MODULE_HANDLE_DATA_TAG { - const MODULE_API* api; + const MODULE_API* api; + } OUTPROCESS_MODULE_HANDLE_DATA; +static VECTOR_HANDLE uv_processes = NULL; +static THREAD_HANDLE uv_thread = NULL; +static tickcounter_ms_t uv_process_grace_period_ms = 0; + +int launch_child_process_from_entrypoint (OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry) +{ + int result; + + uv_process_t * child = NULL; + const uv_process_options_t options = { + .exit_cb = NULL, + .file = outprocess_entry->process_argv[0], + .args = outprocess_entry->process_argv, + .flags = UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS + }; + + /* Codes_SRS_OUTPROCESS_LOADER_27_098: [ If a vector for child processes already exists, then `launch_child_process_from_entrypoint` shall not attempt to recreate the vector. ] */ + if (NULL == uv_processes) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_069: [ `launch_child_process_from_entrypoint` shall attempt to create a vector for child processes(unless previously created), by calling `VECTOR_HANDLE VECTOR_create(size_t elementSize)` using `sizeof(uv_process_t *)` as `elementSize`. ]*/ + uv_processes = VECTOR_create(sizeof(uv_process_t *)); + } + + /* Codes_SRS_OUTPROCESS_LOADER_27_070: [ If a vector for the child processes does not exist, then `launch_child_process_from_entrypoint` shall return a non-zero value. ] */ + if (NULL == uv_processes) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_072: [ If unable to allocate memory for the child handle, then `launch_child_process_from_entrypoint` shall return a non-zero value. ] */ + LogError("Unable to create uv_process_t vector"); + result = __LINE__; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_071: [ `launch_child_process_from_entrypoint` shall allocate the memory for the child handle, by calling `void * malloc(size_t _Size)` passing `sizeof(uv_process_t)` as `_Size`. ] */ + else if (NULL == (child = (uv_process_t *)malloc(sizeof(uv_process_t)))) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_074: [ If unable to store the child's handle, then `launch_child_process_from_entrypoint` shall free the memory allocated to the child process handle and return a non-zero value. ] */ + LogError("Unable to allocate child handle"); + result = __LINE__; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_073: [ `launch_child_process_from_entrypoint` shall store the child's handle, by calling `int VECTOR_push_back(VECTOR_HANDLE handle, const void * elements, size_t numElements)` passing the process vector as `handle` the pointer to the newly allocated memory for the process context as `elements` and 1 as `numElements`. ]*/ + else if (0 != VECTOR_push_back(uv_processes, &child, 1)) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_076: [ If unable to enqueue the child process, then `launch_child_process_from_entrypoint` shall remove the stored handle, free the memory allocated to the child process handle and return a non-zero value. ] */ + LogError("Unable to store child handle"); + free(child); + result = __LINE__; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_075: [ `launch_child_process_from_entrypoint` shall enqueue the child process to be spawned, by calling `int uv_spawn(uv_loop_t * loop, uv_process_t * handle, const uv_process_options_t * options)` passing the result of `uv_default_loop()` as `loop`, the newly allocated process handle as `handle`. ] */ + else if (0 != uv_spawn(uv_default_loop(), child, &options)) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_078: [ If launching the enqueued child processes fails, then `launch_child_process_from_entrypoint` shall return a non - zero value. ] */ + LogError("Unable to spawn child process"); + (void)VECTOR_erase(uv_processes, VECTOR_back(uv_processes), 1); + free(child); + result = __LINE__; + } + else + { + /* Codes_SRS_OUTPROCESS_LOADER_27_079: [ If no errors are encountered, then `launch_child_process_from_entrypoint` shall return zero. ] */ + result = 0; + } + + return result; +} + +int spawn_child_processes (void * context) +{ + (void)context; + + /* Codes_SRS_OUTPROCESS_LOADER_27_080: [ `spawn_child_processes` shall start the child process management thread, by calling `int uv_run(uv_loop_t * loop, uv_run_mode mode)` passing the result of `uv_default_loop()` for `loop` and `UV_RUN_DEFAULT` for `mode`. ] */ + /* Codes_SRS_OUTPROCESS_LOADER_27_099: [ `spawn_child_processes` shall return the result of the child process management thread to the parent thread, by calling `void ThreadAPI_Exit(int res)` passing the result of `uv_run()` as `res`. ] */ + ThreadAPI_Exit(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + /* Codes_SRS_OUTPROCESS_LOADER_27_081: [ If no errors are encountered, then `spawn_child_processes` shall return zero. ] */ + return 0; +} + +int update_entrypoint_with_launch_object(OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry, const JSON_Object * launch_object) +{ + int result; + + /* Codes_SRS_OUTPROCESS_LOADER_27_082: [ `update_entrypoint_with_launch_object` shall retrieve the file path, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `path` as `name`. **] */ + const char * launch_path = json_object_get_string(launch_object, "path"); + /* Codes_SRS_OUTPROCESS_LOADER_27_083: [ `update_entrypoint_with_launch_object` shall retrieve the JSON arguments array, by calling `JSON_Array json_object_get_array(const JSON_Object * object, const char * name)` passing `args` as `name`. ] */ + JSON_Array * launch_args = json_object_get_array(launch_object, "args"); + + /* Codes_SRS_OUTPROCESS_LOADER_27_084: [ `update_entrypoint_with_launch_object` shall determine the size of the JSON arguments array, by calling `size_t json_array_get_count(const JSON_Array * array)`. ] */ + outprocess_entry->process_argc = (json_array_get_count(launch_args) + 1); // Add 1 to make room for launch path + /* Codes_SRS_OUTPROCESS_LOADER_27_085: [ `update_entrypoint_with_launch_object` shall allocate the argument array, by calling `void * malloc(size _Size)` passing the result of `json_array_get_count` plus two, one for passing the file path as the first argument and the second for the NULL terminating pointer required by libUV. ] */ + if (NULL == (outprocess_entry->process_argv = (char **)malloc(sizeof(char *) * (outprocess_entry->process_argc + 1)))) // Add 1 to make room for NULL terminator + { + /* Codes_SRS_OUTPROCESS_LOADER_27_086: [ If unable to allocate the array, then `update_entrypoint_with_launch_object` shall return a non - zero value. ] */ + LogError("Unable to allocate argument string array."); + result = __LINE__; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_087: [ `update_entrypoint_with_launch_object` shall allocate the space necessary to copy the file path, by calling `void * malloc(size _Size)`. ] */ + else if (NULL == (outprocess_entry->process_argv[0] = (char *)malloc(strlen(launch_path) + 1))) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_088: [ If unable to allocate space for the file path, then `update_entrypoint_with_launch_object` shall free the argument array and return a non-zero value. ] */ + LogError("Unable to allocate argument[0] string."); + free(outprocess_entry->process_argv); + result = __LINE__; + } + else if (NULL == strcpy(outprocess_entry->process_argv[0], launch_path)) + { + LogError("Unable to copy argument[0] string."); + free(outprocess_entry->process_argv[0]); + free(outprocess_entry->process_argv); + result = __LINE__; + } + else + { + int i; + /* Codes_SRS_OUTPROCESS_LOADER_27_092: [ If no errors are encountered, then `update_entrypoint_with_launch_object` shall return zero. ] */ + for (result = 0, i = 1; i < (int)outprocess_entry->process_argc; ++i) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_089: [ `update_entrypoint_with_launch_object` shall retrieve each argument from the JSON arguments array, by calling `const char * json_array_get_string(const JSON_Array * array, size_t index)`. ] */ + const char * arg = json_array_get_string(launch_args, (i - 1)); + /* Codes_SRS_OUTPROCESS_LOADER_27_090: [ `update_entrypoint_with_launch_object` shall allocate the space necessary for each argument, by calling `void * malloc(size _Size)`. ] */ + if (NULL == (outprocess_entry->process_argv[i] = (char *)malloc(strlen(arg) + 1))) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_091: [ If unable to allocate space for the argument, then `update_entrypoint_with_launch_object` shall free the argument array and return a non - zero value. ] */ + LogError("Unable to allocate argument[%lu] string.", i); + for (int j = (i - 1); 0 <= j; --j) { free(outprocess_entry->process_argv[j]); } + free(outprocess_entry->process_argv); + result = __LINE__; + break; + } + else if (NULL == strcpy(outprocess_entry->process_argv[i], arg)) + { + LogError("Unable to copy argument[%lu] string.", i); + for (int j = i; 0 <= j; --j) { free(outprocess_entry->process_argv[j]); } + free(outprocess_entry->process_argv); + result = __LINE__; + break; + } + } + outprocess_entry->process_argv[i] = NULL; // NULL terminate the array + } + + return result; +} + +int validate_launch_arguments(const JSON_Object * launch_object) +{ + int result; + + /* Codes_SRS_OUTPROCESS_LOADER_27_093: [ `validate_launch_arguments` shall retrieve the file path, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `path` as `name`. ] */ + if (NULL == json_object_get_string(launch_object, "path")) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_094: [ If unable to retrieve the file path, then `validate_launch_arguments` shall return a non - zero value. ] */ + LogError("Activation type launch specified with nothing to launch!"); + result = __LINE__; + } + else + { + /* Codes_SRS_OUTPROCESS_LOADER_27_095: [ `validate_launch_arguments` shall test for the optional parameter grace period, by calling `const char * json_object_get_string(const JSON_Object * object, const char * name)` passing `grace.period.ms` as `name`. ] */ + if (NULL == json_object_get_string(launch_object, "grace.period.ms")) + { + // No value set, use default + if (GRACE_PERIOD_MS_DEFAULT > uv_process_grace_period_ms) + { + uv_process_grace_period_ms = GRACE_PERIOD_MS_DEFAULT; + } + } + else + { + /* Codes_SRS_OUTPROCESS_LOADER_27_096: [ `validate_launch_arguments` shall retrieve the grace period (if provided), by calling `double json_object_get_number(const JSON_Object * object, const char * name)` passing `grace.period.ms` as `name`. ] */ + const size_t ms = (size_t)json_object_get_number(launch_object, "grace.period.ms"); + if (ms > uv_process_grace_period_ms) + { + uv_process_grace_period_ms = ms; + } + } + /* Codes_SRS_OUTPROCESS_LOADER_27_097: [ If no errors are encountered, then `validate_launch_arguments` shall return zero. ] */ + result = 0; + } + + return result; +} + +int OutprocessLoader_SpawnChildProcesses(void) { + int result; + + if (NULL == uv_processes) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_045: [ Prerequisite Check - If no processes have been enqueued, then `OutprocessLoader_SpawnChildProcesses` shall take no action and return zero. ] */ + LogInfo("No child process(es) scheduled."); + result = 0; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_046: [ Prerequisite Check - If child processes are already running, then `OutprocessLoader_SpawnChildProcesses` shall take no action and return zero. ] */ + else if (NULL != uv_thread) + { + LogInfo("Child process(es) already running!"); + result = 0; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_047: [ `OutprocessLoader_SpawnChildProcesses` shall launch the enqueued child processes by calling `THREADAPI_RESULT ThreadAPI_Create(THREAD_HANDLE * threadHandle, THREAD_START_FUNC func, void * arg)`. ] */ + else if (THREADAPI_OK != ThreadAPI_Create(&uv_thread, spawn_child_processes, NULL)) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_048: [** If launching the enqueued child processes fails, then `OutprocessLoader_SpawnChildProcesses` shall return a non-zero value. ] */ + LogError("Unable to spawn child process(es)!"); + result = __LINE__; + } + else + { + /* Codes_SRS_OUTPROCESS_LOADER_27_049: [** If no errors are encountered, then `OutprocessLoader_SpawnChildProcesses` shall return zero. ] */ + result = 0; + } + + return result; +} + +void OutprocessLoader_JoinChildProcesses(void) { + /* Codes_SRS_OUTPROCESS_LOADER_27_064: [ `OutprocessLoader_JoinChildProcesses` shall get the count of child processes, by calling `size_t VECTOR_size(VECTOR_HANDLE handle)`. ] */ + const size_t child_count = VECTOR_size(uv_processes); + + TICK_COUNTER_HANDLE ticks = NULL; + tickcounter_ms_t started_waiting; + int uv_thread_result = 0; + bool timed_out; + + if (uv_loop_alive(uv_default_loop())) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_051: [ `OutprocessLoader_JoinChildProcesses` shall create a timer to test for timeout, by calling `TICK_COUNTER_HANDLE tickcounter_create(void)`. ] */ + if (NULL == (ticks = tickcounter_create())) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_052: [ If unable to create a timer, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. ] */ + timed_out = true; + LogError("failed to create tickcounter"); + } + /* Codes_SRS_OUTPROCESS_LOADER_27_053: [ `OutprocessLoader_JoinChildProcesses` shall mark the begin time, by calling `int tickcounter_get_current_ms(TICK_COUNTER_HANDLE tick_counter, tickcounter_ms_t * current_ms)` using the handle called by the previous call to `tickcounter_create`. ] */ + else if (tickcounter_get_current_ms(ticks, &started_waiting)) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_054: [** If unable to mark the begin time, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. ] */ + timed_out = true; + LogError("failed to sample tickcounter"); + } + else + { + // Wait for child to clean up + tickcounter_ms_t now = started_waiting; + /* Codes_SRS_OUTPROCESS_LOADER_27_058: [ `OutprocessLoader_JoinChildProcesses` shall await the grace period in 100ms increments, by calling `void ThreadAPI_Sleep(unsigned int milliseconds)` passing 100 for `milliseconds`. ] */ + for (timed_out = true; (now - started_waiting) < uv_process_grace_period_ms; ThreadAPI_Sleep(GRACE_AWAIT_DELAY_MS)) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_055: [ While awaiting the grace period, `OutprocessLoader_JoinChildProcesses` shall mark the current time, by calling `int tickcounter_get_current_ms(TICK_COUNTER_HANDLE tick_counter, tickcounter_ms_t * current_ms)` using the handle called by the previous call to `tickcounter_create`. ] */ + if (tickcounter_get_current_ms(ticks, &now)) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_056: [ If unable to mark the current time, `OutprocessLoader_JoinChildProcesses` shall abandon awaiting the grace period. ] */ + LogError("failed to sample tickcounter"); + break; + } + /* Codes_SRS_OUTPROCESS_LOADER_27_057: [ While awaiting the grace period, `OutprocessLoader_JoinChildProcesses` shall check if the processes are running, by calling `int uv_loop_alive(const uv_loop_t * loop)` using the result of `uv_default_loop()` for `loop`. ] */ + else if (!uv_loop_alive(uv_default_loop())) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_059: [** If the child processes are not running, `OutprocessLoader_JoinChildProcesses` shall shall immediately join the child process management thread. ] */ + timed_out = false; + break; + } + } + } + + // Children did not clean up, now SIGNAL + if (timed_out) + { + for (size_t i = 0; i < child_count; ++i) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_060: [ If the grace period expired, `OutprocessLoader_JoinChildProcesses` shall get the handle of each child processes, by calling `void * VECTOR_element(VECTOR_HANDLE handle, size_t index)`. ] */ + uv_process_t * child = *((uv_process_t **)VECTOR_element(uv_processes, i)); + /* Codes_SRS_OUTPROCESS_LOADER_27_061: [ If the grace period expired, `OutprocessLoader_JoinChildProcesses` shall signal each child, by calling `int uv_process_kill(uv_process_t * process, int signum)` passing `SIGTERM` for `signum`. ] */ + (void)uv_process_kill(child, SIGTERM); + } + } + /* Codes_SRS_OUTPROCESS_LOADER_27_068: [ `OutprocessLoader_JoinChildProcesses` shall destroy the timer, by calling `void tickcounter_destroy(TICK_COUNTER_HANDLE tick_counter)`. ] */ + tickcounter_destroy(ticks); + } + + uv_process_grace_period_ms = 0; + + if (NULL == uv_thread) + { + /* Codes_SRS_OUTPROCESS_LOADER_27_050: [ Prerequisite Check - If no threads are running, then `OutprocessLoader_JoinChildProcesses` shall abandon the effort to join the child processes immediately. ] */ + LogInfo("The process management thread is not currently running."); + } + else + { + /* Codes_SRS_OUTPROCESS_LOADER_27_062: [ `OutprocessLoader_JoinChildProcesses` shall join the child process management thread, by calling `THREADAPI_RESULT ThreadAPI_Join(THREAD_HANDLE threadHandle, int * res)`. ] */ + (void)ThreadAPI_Join(uv_thread, &uv_thread_result); + uv_thread = NULL; + } + + /* + * Under normal conditions the process vector would not exist without + * a process management thread, but under certain error conditions, it + * is necessary to clean the process resources without regard for the + * existence of the process management thread. + */ + /* Codes_SRS_OUTPROCESS_LOADER_27_063: [ If no processes are running, then `OutprocessLoader_JoinChildProcesses` shall immediately join the child process management thread. ] */ + if (NULL != uv_processes) + { + for (size_t i = 0; i < child_count; ++i) { + /* Codes_SRS_OUTPROCESS_LOADER_27_065: [ `OutprocessLoader_JoinChildProcesses` shall get the handle of each child processes, by calling `void * VECTOR_element(VECTOR_HANDLE handle, size_t index)`. ] */ + uv_process_t * child = *((uv_process_t **)VECTOR_element(uv_processes, i)); + /* Codes_SRS_OUTPROCESS_LOADER_27_066: [ `OutprocessLoader_JoinChildProcesses` shall free the resources allocated to each child, by calling `void free(void * _Block)` passing the child handle as `_Block`. ] */ + free(child); + } + /* Codes_SRS_OUTPROCESS_LOADER_27_067: [ `OutprocessLoader_JoinChildProcesses` shall destroy the vector of child processes, by calling `void VECTOR_destroy(VECTOR_HANDLE handle)`. ] */ + VECTOR_destroy(uv_processes); + uv_processes = NULL; + } +} + static MODULE_LIBRARY_HANDLE OutprocessModuleLoader_Load(const MODULE_LOADER* loader, const void* entrypoint) { - OUTPROCESS_MODULE_HANDLE_DATA * result; - - if (loader == NULL || entrypoint == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_001: [ If loader or entrypoint are NULL, then this function shall return NULL. ] */ - result = NULL; - LogError( - "invalid input - loader = %p, entrypoint = %p", - loader, entrypoint - ); - } - else - { - if (loader->type != OUTPROCESS) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_042: [ If the loader type is not OUTPROCESS, then this function shall return NULL. ] */ - result = NULL; - LogError("loader->type is not remote"); - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_002: [ If the entrypoint's outprocess_loader_args or control_id are NULL, then this function shall return NULL. ] */ - /*Codes_SRS_OUTPROCESS_LOADER_17_003: [ If the entrypoint's activation_type is not NONE, the this function shall return NULL. ] */ - OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry = (OUTPROCESS_LOADER_ENTRYPOINT*)entrypoint; - if ((outprocess_entry->control_id == NULL) || - (outprocess_entry->activation_type != OUTPROCESS_LOADER_ACTIVATION_NONE)) - { - result = NULL; - LogError("Invalid arguments activation type=[%s]", - ENUM_TO_STRING(OUTPROCESS_LOADER_ACTIVATION_TYPE, outprocess_entry->activation_type)); - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_004: [ The loader shall allocate memory for the loader handle. ] */ - result = (OUTPROCESS_MODULE_HANDLE_DATA*)malloc(sizeof(OUTPROCESS_MODULE_HANDLE_DATA)); - if (result == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_008: [ If any call in this function fails, this function shall return NULL. ] */ - LogError("malloc(sizeof(OUTPROCESS_MODULE_HANDLE_DATA)) failed."); - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_006: [ The loader shall store the Outprocess_Module_API_all in the loader handle. ] */ - /*Codes_SRS_OUTPROCESS_LOADER_17_007: [ Upon success, this function shall return a valid pointer to the loader handle. ] */ - result->api = (const MODULE_API*)&Outprocess_Module_API_all; - } - } - } - } - return result; + OUTPROCESS_LOADER_ENTRYPOINT * outprocess_entry = (OUTPROCESS_LOADER_ENTRYPOINT *)entrypoint; + OUTPROCESS_MODULE_HANDLE_DATA * result; + + if (loader == NULL || entrypoint == NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_001: [ If loader or entrypoint are NULL, then this function shall return NULL. ] */ + result = NULL; + LogError( + "invalid input - loader = %p, entrypoint = %p", + loader, entrypoint + ); + } + else if (loader->type != OUTPROCESS) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_042: [ If the loader type is not OUTPROCESS, then this function shall return NULL. ] */ + result = NULL; + LogError("loader->type is not remote"); + } + /*Codes_SRS_OUTPROCESS_LOADER_17_002: [ If the entrypoint's `control_id` is `NULL`, then this function shall return `NULL`. ] */ + /*Codes_SRS_OUTPROCESS_LOADER_27_003: [ If the entrypoint's `activation_type` is invalid, then `OutprocessModuleLoader_Load` shall return `NULL`. ] */ + else if ((outprocess_entry->control_id == NULL) || + ((outprocess_entry->activation_type != OUTPROCESS_LOADER_ACTIVATION_NONE) && + (outprocess_entry->activation_type != OUTPROCESS_LOADER_ACTIVATION_LAUNCH))) + { + result = NULL; + LogError("Invalid arguments activation type"); + } + /*Codes_SRS_OUTPROCESS_LOADER_27_005: [ Launch - `OutprocessModuleLoader_Load` shall launch the child process identified by the entrypoint. ]*/ + else if ((OUTPROCESS_LOADER_ACTIVATION_LAUNCH == outprocess_entry->activation_type) && launch_child_process_from_entrypoint(outprocess_entry)) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_008: [ If any call in this function fails, this function shall return NULL. ] */ + result = NULL; + LogError("Unable to launch external process!"); + } + /* Codes_SRS_OUTPROCESS_LOADER_27_077: [ Launch - `OutprocessModuleLoader_Load` shall spawn the enqueued child processes. ] */ + else if ((OUTPROCESS_LOADER_ACTIVATION_LAUNCH == outprocess_entry->activation_type) && OutprocessLoader_SpawnChildProcesses()) + { + /* + * Here, we are beyond the point of failing gracefully from inside the function. + * + * Due to the way libuv works, this function must be called after `uv_spawn` (in + * order for the thread to stay alive) and the resources allocated during this + * function will need to be left intact until they are reclaimed by a subsequent + * call to `OutprocessLoader_JoinChildProcesses()`. + * + * To remedy this pitfall, the sychronous `Module_Create` behavior of the + * outprocess module would need to be made asynchronous. This would in turn + * eliminate the need to call `OutprocessLoader_SpawnChildProcesses` from + * inside this function, and therefore the existence of this state altogether. + */ + result = NULL; + LogError("Unable to launch uv thread! Must call `OutprocessLoader_JoinChildProcesses` to recover."); + } + /*Codes_SRS_OUTPROCESS_LOADER_17_004: [ The loader shall allocate memory for the loader handle. ] */ + else if (NULL == (result = (OUTPROCESS_MODULE_HANDLE_DATA*)malloc(sizeof(OUTPROCESS_MODULE_HANDLE_DATA)))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_008: [ If any call in this function fails, this function shall return NULL. ] */ + LogError("malloc(sizeof(OUTPROCESS_MODULE_HANDLE_DATA)) failed."); + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_006: [ The loader shall store the Outprocess_Module_API_all in the loader handle. ] */ + /*Codes_SRS_OUTPROCESS_LOADER_17_007: [ Upon success, this function shall return a valid pointer to the loader handle. ] */ + result->api = (const MODULE_API*)&Outprocess_Module_API_all; + } + + return result; } static const MODULE_API* OutprocessModuleLoader_GetModuleApi(const MODULE_LOADER* loader, MODULE_LIBRARY_HANDLE moduleLibraryHandle) { - (void)loader; - - const MODULE_API* result; - - if (moduleLibraryHandle == NULL) - { - result = NULL; - LogError("moduleLibraryHandle is NULL"); - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_009: [ This function shall return a valid pointer to the Outprocess_Module_API_all MODULE_API. ] */ - OUTPROCESS_MODULE_HANDLE_DATA* loader_data = moduleLibraryHandle; - result = loader_data->api; - } - return result; + (void)loader; + + const MODULE_API* result; + + if (moduleLibraryHandle == NULL) + { + result = NULL; + LogError("moduleLibraryHandle is NULL"); + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_009: [ This function shall return a valid pointer to the Outprocess_Module_API_all MODULE_API. ] */ + OUTPROCESS_MODULE_HANDLE_DATA* loader_data = moduleLibraryHandle; + result = loader_data->api; + } + return result; } static void OutprocessModuleLoader_Unload(const struct MODULE_LOADER_TAG* loader, MODULE_LIBRARY_HANDLE handle) { - (void)loader; - - if (handle != NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_011: [ This function shall release all resources created by this loader. ] */ - OUTPROCESS_MODULE_HANDLE_DATA* loader_data = (OUTPROCESS_MODULE_HANDLE_DATA*)handle; - - free(loader_data); - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_010: [ This function shall do nothing if moduleLibraryHandle is NULL. ] */ - LogError("moduleLibraryHandle is NULL"); - } + (void)loader; + + if (handle != NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_011: [ This function shall release all resources created by this loader. ] */ + OUTPROCESS_MODULE_HANDLE_DATA* loader_data = (OUTPROCESS_MODULE_HANDLE_DATA*)handle; + + free(loader_data); + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_010: [ This function shall do nothing if moduleLibraryHandle is NULL. ] */ + LogError("moduleLibraryHandle is NULL"); + } } static void* OutprocessModuleLoader_ParseEntrypointFromJson(const struct MODULE_LOADER_TAG* loader, const JSON_Value* json) { - (void)loader; - // The input is a JSON object that looks like this: - // "entrypoint": { - // "activation.type" : "none", - // "control.id" : "outproc_module_control", - // "message.id" : "outproc_module_message", (optional) - // "timeout" : numeric, (optional, default 250 ms) - // } - // } - OUTPROCESS_LOADER_ENTRYPOINT * config; - if (json == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_012: [ This function shall return NULL if json is NULL. ] */ - LogError("json input is NULL"); - config = NULL; - } - else - { - // "json" must be an "object" type - if (json_value_get_type(json) != JSONObject) - { - LogError("'json' is not an object value"); - config = NULL; - } - else - { - JSON_Object* entrypoint = json_value_get_object(json); - if (entrypoint == NULL) - { - LogError("json_value_get_object failed"); - - config = NULL; - } - else - { - const char* activationType = json_object_get_string(entrypoint, "activation.type"); - const char* controlId = json_object_get_string(entrypoint, "control.id"); - const char* messageId = json_object_get_string(entrypoint, "message.id"); - /*Codes_SRS_OUTPROCESS_LOADER_17_013: [ This function shall return NULL if "activation.type" is not present in json. ] */ - /*Codes_SRS_OUTPROCESS_LOADER_17_014: [ This function shall return NULL if "activation.type is not "NONE". */ - /*Codes_SRS_OUTPROCESS_LOADER_17_041: [ This function shall return NULL if "control.id" is not present in json. ] */ - if ((activationType == NULL) || (controlId ==NULL) || strcmp(activationType, "none")) - { - LogError("Invalid JSON parameters, activation type=[%s], controlURI=[%p]", - activationType, controlId); - - /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ]*/ - config = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_016: [ This function shall allocate a OUTPROCESS_LOADER_ENTRYPOINT structure. ] */ - config = (OUTPROCESS_LOADER_ENTRYPOINT*)malloc(sizeof(OUTPROCESS_LOADER_ENTRYPOINT)); - if (config == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ] */ - LogError("entrypoint allocation failed"); - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_043: [ This function shall read the "timeout" value. ]*/ - /*Codes_SRS_OUTPROCESS_LOADER_17_044: [ If "timeout" is set, the remote_message_wait shall be set to this value, else it will be set to a default of 1000 ms. ]*/ - double timeout = json_object_get_number(entrypoint, "timeout"); - if (timeout == 0) - { - config->remote_message_wait = REMOTE_MESSAGE_WAIT_DEFAULT; - } - else - { - config->remote_message_wait = (unsigned int)timeout; - } - /*Codes_SRS_OUTPROCESS_LOADER_17_017: [ This function shall assign the entrypoint activation_type to NONE. ] */ - config->activation_type = OUTPROCESS_LOADER_ACTIVATION_NONE; - /*Codes_SRS_OUTPROCESS_LOADER_17_018: [ This function shall assign the entrypoint control_id to the string value of "control.id" in json, NULL if not present. ] */ - config->control_id = STRING_construct(controlId); - if (config->control_id == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ] */ - LogError("Could not allocate loader args string"); - free(config); - config = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_019: [ This function shall assign the entrypoint message_id to the string value of "message.id" in json, NULL if not present. ] */ - config->message_id = STRING_construct(messageId); - /*Codes_SRS_OUTPROCESS_LOADER_17_022: [ This function shall return a valid pointer to an OUTPROCESS_LOADER_ENTRYPOINT on success. ]*/ - } - } - } - } - } - } - return (void*)config; + (void)loader; + + OUTPROCESS_LOADER_ENTRYPOINT * config; + JSON_Object* entrypoint; + + if (json == NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_012: [ This function shall return NULL if json is NULL. ] */ + LogError("json input is NULL"); + config = NULL; + } + // "json" must be an "object" type + else if (json_value_get_type(json) != JSONObject) + { + LogError("'json' is not an object value"); + config = NULL; + } + else if (NULL == (entrypoint = json_value_get_object(json))) + { + LogError("json_value_get_object failed"); + + config = NULL; + } + else + { + OUTPROCESS_LOADER_ACTIVATION_TYPE activationType; + const char* activationTypeString = json_object_get_string(entrypoint, "activation.type"); + const char* controlId = json_object_get_string(entrypoint, "control.id"); + JSON_Object* launchObject = json_object_get_object(entrypoint, "launch"); + const char* messageId = json_object_get_string(entrypoint, "message.id"); + + // Transform activation type string into OUTPROCESS_LOADER_ACTIVATION_TYPE + if (activationTypeString) + { + if (!strncmp("none", activationTypeString, sizeof("none"))) + { + activationType = OUTPROCESS_LOADER_ACTIVATION_NONE; + } + /*Codes_SRS_OUTPROCESS_LOADER_27_015: [ Launch - `OutprocessModuleLoader_ParseEntrypointFromJson` shall validate the launch parameters. ]*/ + else if ((!strncmp("launch", activationTypeString, sizeof("launch"))) && (0 == validate_launch_arguments(launchObject))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ]*/ + activationType = OUTPROCESS_LOADER_ACTIVATION_LAUNCH; + } + else + { + activationType = OUTPROCESS_LOADER_ACTIVATION_INVALID; + LogError("Invalid activation type specified!"); + } + } + else + { + activationType = OUTPROCESS_LOADER_ACTIVATION_INVALID; + LogError("No activation type specified!"); + } + + /*Codes_SRS_OUTPROCESS_LOADER_17_013: [ This function shall return NULL if "activation.type" is not present in json. ] */ + /*Codes_SRS_OUTPROCESS_LOADER_27_014: [ This function shall return NULL if "activation.type" is `OUTPROCESS_LOADER_ACTIVATION_INVALID`. */ + /*Codes_SRS_OUTPROCESS_LOADER_17_041: [ This function shall return NULL if "control.id" is not present in json. ] */ + if ((activationType == OUTPROCESS_LOADER_ACTIVATION_INVALID) || (controlId == NULL)) + { + LogError("Invalid JSON parameters, activation type=[%s], controlURI=[%p]", + activationTypeString, controlId); + + /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ]*/ + config = NULL; + } + /*Codes_SRS_OUTPROCESS_LOADER_17_016: [ This function shall allocate a OUTPROCESS_LOADER_ENTRYPOINT structure. ] */ + else if (NULL == (config = (OUTPROCESS_LOADER_ENTRYPOINT*)malloc(sizeof(OUTPROCESS_LOADER_ENTRYPOINT)))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ] */ + LogError("Entrypoint allocation failed"); + } + else + { + // Initialize variables to ensure proper clean-up behavior + config->process_argc = 0; + config->process_argv = NULL; + + /*Codes_SRS_OUTPROCESS_LOADER_17_018: [ This function shall assign the entrypoint control_id to the string value of "control.id" in json, NULL if not present. ] */ + if (NULL == (config->control_id = STRING_construct(controlId))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ] */ + LogError("Could not allocate loader args string"); + free(config); + config = NULL; + } + /*Codes_SRS_OUTPROCESS_LOADER_27_020: [ Launch - `OutprocessModuleLoader_ParseEntrypointFromJson` shall update the entry point with the parsed launch parameters. ]*/ + else if ((OUTPROCESS_LOADER_ACTIVATION_LAUNCH == activationType) && update_entrypoint_with_launch_object(config, launchObject)) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_021: [ This function shall return NULL if any calls fails. ] */ + LogError("Unable to update entrypoint with launch parameters!"); + free(config); + config = NULL; + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_043: [ This function shall read the "timeout" value. ]*/ + /*Codes_SRS_OUTPROCESS_LOADER_17_044: [ If "timeout" is set, the remote_message_wait shall be set to this value, else it will be set to a default of 1000 ms. ]*/ + double timeout = json_object_get_number(entrypoint, "timeout"); + if (timeout == 0) + { + config->remote_message_wait = REMOTE_MESSAGE_WAIT_DEFAULT; + } + else + { + config->remote_message_wait = (unsigned int)timeout; + } + + /*Codes_SRS_OUTPROCESS_LOADER_17_017: [ This function shall assign the entrypoint activation_type to the decoded value. ] */ + config->activation_type = activationType; + + /*Codes_SRS_OUTPROCESS_LOADER_17_019: [ This function shall assign the entrypoint message_id to the string value of "message.id" in json, NULL if not present. ] */ + config->message_id = STRING_construct(messageId); + + /*Codes_SRS_OUTPROCESS_LOADER_17_022: [ This function shall return a valid pointer to an OUTPROCESS_LOADER_ENTRYPOINT on success. ]*/ + } + } + } + + return (void*)config; } static void OutprocessModuleLoader_FreeEntrypoint(const struct MODULE_LOADER_TAG* loader, void* entrypoint) { - (void)loader; - - if (entrypoint != NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_023: [ This function shall release all resources allocated by OutprocessModuleLoader_ParseEntrypointFromJson. ] */ - OUTPROCESS_LOADER_ENTRYPOINT* ep = (OUTPROCESS_LOADER_ENTRYPOINT*)entrypoint; - if (ep->message_id != NULL) - STRING_delete(ep->message_id); - STRING_delete(ep->control_id); - free(ep); - } - else - { - LogError("entrypoint is NULL"); - } + (void)loader; + + if (entrypoint != NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_023: [ This function shall release all resources allocated by OutprocessModuleLoader_ParseEntrypointFromJson. ] */ + OUTPROCESS_LOADER_ENTRYPOINT* ep = (OUTPROCESS_LOADER_ENTRYPOINT*)entrypoint; + if (ep->message_id != NULL) + STRING_delete(ep->message_id); + STRING_delete(ep->control_id); + if (ep->process_argv) { + for (size_t i = 0; i < ep->process_argc; ++i) { + free(ep->process_argv[i]); + } + free(ep->process_argv); + } + + free(ep); + } + else + { + LogError("entrypoint is NULL"); + } } static MODULE_LOADER_BASE_CONFIGURATION* OutprocessModuleLoader_ParseConfigurationFromJson(const struct MODULE_LOADER_TAG* loader, const JSON_Value* json) { - (void)loader; - (void)json; - /*Codes_SRS_OUTPROCESS_LOADER_17_024: [ The out of process loader does not have any configuration. So this method shall return NULL. ]*/ - return NULL; + (void)json; + (void)loader; + /*Codes_SRS_OUTPROCESS_LOADER_17_024: [ The out of process loader does not have any configuration. So this method shall return NULL. ]*/ + return NULL; } static void OutprocessModuleLoader_FreeConfiguration(const struct MODULE_LOADER_TAG* loader, MODULE_LOADER_BASE_CONFIGURATION* configuration) { - (void)loader; - (void)configuration; - /*Codes_SRS_OUTPROCESS_LOADER_17_025: [ This function shall move along, nothing to free here. ]*/ + (void)loader; + (void)configuration; + /*Codes_SRS_OUTPROCESS_LOADER_17_025: [ This function shall move along, nothing to free here. ]*/ } static void* OutprocessModuleLoader_BuildModuleConfiguration( - const MODULE_LOADER* loader, - const void* entrypoint, - const void* module_configuration + const MODULE_LOADER* loader, + const void* entrypoint, + const void* module_configuration ) { - OUTPROCESS_MODULE_CONFIG * fullModuleConfiguration; - (void)loader; - - if ((entrypoint == NULL) || (module_configuration == NULL)) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_026: [ This function shall return NULL if entrypoint, control_id, or module_configuration is NULL. ] */ - LogError("Remote Loader needs both entry point and module configuration"); - fullModuleConfiguration = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_027: [ This function shall allocate a OUTPROCESS_MODULE_CONFIG structure. ]*/ - fullModuleConfiguration = (OUTPROCESS_MODULE_CONFIG*)malloc(sizeof(OUTPROCESS_MODULE_CONFIG)); - if (fullModuleConfiguration == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ - LogError("couldn't allocate module config"); - } - else - { - OUTPROCESS_LOADER_ENTRYPOINT* ep = (OUTPROCESS_LOADER_ENTRYPOINT*)entrypoint; - char uuid[LOADER_GUID_SIZE]; - UNIQUEID_RESULT uuid_result = UNIQUEID_OK; - if (ep->message_id == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_029: [ If the entrypoint's message_id is NULL, then the loader shall construct an IPC uri. ]*/ - memset(uuid, 0, LOADER_GUID_SIZE); - /*Codes_SRS_OUTPROCESS_LOADER_17_030: [ The loader shall create a unique id, if needed for URI constrution. ]*/ - uuid_result = UniqueId_Generate(uuid, LOADER_GUID_SIZE); - if (uuid_result != UNIQUEID_OK) - { - LogError("Unable to generate unique Id."); - fullModuleConfiguration->message_uri = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_032: [ The message uri shall be composed of "ipc://" + unique id . ]*/ - fullModuleConfiguration->message_uri = STRING_construct_sprintf("%s%s", IPC_URI_HEAD, uuid); - } - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_033: [ This function shall allocate and copy each string in OUTPROCESS_LOADER_ENTRYPOINT and assign them to the corresponding fields in OUTPROCESS_MODULE_CONFIG. ]*/ - fullModuleConfiguration->message_uri = STRING_construct_sprintf("%s%s", IPC_URI_HEAD, STRING_c_str(ep->message_id)); - } - if (fullModuleConfiguration->message_uri == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ - LogError("unable to create a message channel URI"); - free(fullModuleConfiguration); - fullModuleConfiguration = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_033: [ This function shall allocate and copy each string in OUTPROCESS_LOADER_ENTRYPOINT and assign them to the corresponding fields in OUTPROCESS_MODULE_CONFIG. ]*/ - fullModuleConfiguration->control_uri = STRING_construct_sprintf("%s%s", IPC_URI_HEAD, STRING_c_str(ep->control_id)); - if (fullModuleConfiguration->control_uri == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_026: [ This function shall return NULL if entrypoint, control_id, or module_configuration is NULL. ] */ - /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ - LogError("unable to allocate a control channel URI"); - STRING_delete(fullModuleConfiguration->message_uri); - free(fullModuleConfiguration); - fullModuleConfiguration = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_033: [ This function shall allocate and copy each string in OUTPROCESS_LOADER_ENTRYPOINT and assign them to the corresponding fields in OUTPROCESS_MODULE_CONFIG. ]*/ - /*Codes_SRS_OUTPROCESS_LOADER_17_034: [ This function shall allocate and copy the module_configuration string and assign it the OUTPROCESS_MODULE_CONFIG::outprocess_module_args field. ]*/ - fullModuleConfiguration->outprocess_module_args = STRING_clone((STRING_HANDLE)module_configuration); - if (fullModuleConfiguration->outprocess_module_args == NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ - LogError("unable to allocate a loader arguments string"); - STRING_delete(fullModuleConfiguration->message_uri); - STRING_delete(fullModuleConfiguration->control_uri); - free(fullModuleConfiguration); - fullModuleConfiguration = NULL; - } - else - { - /*Codes_SRS_OUTPROCESS_LOADER_17_035: [ Upon success, this function shall return a valid pointer to an OUTPROCESS_MODULE_CONFIG structure. ]*/ - fullModuleConfiguration->remote_message_wait = ep->remote_message_wait; - fullModuleConfiguration->lifecycle_model = OUTPROCESS_LIFECYCLE_SYNC; - } - } - } - } - } - - return (void *)fullModuleConfiguration; + (void)loader; + OUTPROCESS_MODULE_CONFIG * fullModuleConfiguration; + + if ((entrypoint == NULL) || (module_configuration == NULL)) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_026: [ This function shall return NULL if entrypoint, control_id, or module_configuration is NULL. ] */ + LogError("Remote Loader needs both entry point and module configuration"); + fullModuleConfiguration = NULL; + } + /*Codes_SRS_OUTPROCESS_LOADER_17_027: [ This function shall allocate a OUTPROCESS_MODULE_CONFIG structure. ]*/ + else if (NULL == (fullModuleConfiguration = (OUTPROCESS_MODULE_CONFIG*)malloc(sizeof(OUTPROCESS_MODULE_CONFIG)))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ + LogError("couldn't allocate module config"); + } + else + { + OUTPROCESS_LOADER_ENTRYPOINT* ep = (OUTPROCESS_LOADER_ENTRYPOINT*)entrypoint; + char uuid[LOADER_GUID_SIZE]; + UNIQUEID_RESULT uuid_result = UNIQUEID_OK; + + if (ep->message_id == NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_029: [ If the entrypoint's message_id is NULL, then the loader shall construct an IPC uri. ]*/ + memset(uuid, 0, LOADER_GUID_SIZE); + /*Codes_SRS_OUTPROCESS_LOADER_17_030: [ The loader shall create a unique id, if needed for URI constrution. ]*/ + uuid_result = UniqueId_Generate(uuid, LOADER_GUID_SIZE); + if (uuid_result != UNIQUEID_OK) + { + LogError("Unable to generate unique Id."); + fullModuleConfiguration->message_uri = NULL; + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_032: [ The message uri shall be composed of "ipc://" + unique id . ]*/ + fullModuleConfiguration->message_uri = STRING_construct_sprintf("%s%s", IPC_URI_HEAD, uuid); + } + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_033: [ This function shall allocate and copy each string in OUTPROCESS_LOADER_ENTRYPOINT and assign them to the corresponding fields in OUTPROCESS_MODULE_CONFIG. ]*/ + fullModuleConfiguration->message_uri = STRING_construct_sprintf("%s%s", IPC_URI_HEAD, STRING_c_str(ep->message_id)); + } + + if (fullModuleConfiguration->message_uri == NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ + LogError("unable to create a message channel URI"); + free(fullModuleConfiguration); + fullModuleConfiguration = NULL; + } + /*Codes_SRS_OUTPROCESS_LOADER_17_033: [ This function shall allocate and copy each string in OUTPROCESS_LOADER_ENTRYPOINT and assign them to the corresponding fields in OUTPROCESS_MODULE_CONFIG. ]*/ + else if (NULL == (fullModuleConfiguration->control_uri = STRING_construct_sprintf("%s%s", IPC_URI_HEAD, STRING_c_str(ep->control_id)))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_026: [ This function shall return NULL if entrypoint, control_id, or module_configuration is NULL. ] */ + /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ + LogError("unable to allocate a control channel URI"); + STRING_delete(fullModuleConfiguration->message_uri); + free(fullModuleConfiguration); + fullModuleConfiguration = NULL; + } + /*Codes_SRS_OUTPROCESS_LOADER_17_033: [ This function shall allocate and copy each string in OUTPROCESS_LOADER_ENTRYPOINT and assign them to the corresponding fields in OUTPROCESS_MODULE_CONFIG. ]*/ + /*Codes_SRS_OUTPROCESS_LOADER_17_034: [ This function shall allocate and copy the module_configuration string and assign it the OUTPROCESS_MODULE_CONFIG::outprocess_module_args field. ]*/ + else if (NULL == (fullModuleConfiguration->outprocess_module_args = STRING_clone((STRING_HANDLE)module_configuration))) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_036: [ If any call fails, this function shall return NULL. ]*/ + LogError("unable to allocate a loader arguments string"); + STRING_delete(fullModuleConfiguration->message_uri); + STRING_delete(fullModuleConfiguration->control_uri); + free(fullModuleConfiguration); + fullModuleConfiguration = NULL; + } + else + { + /*Codes_SRS_OUTPROCESS_LOADER_17_035: [ Upon success, this function shall return a valid pointer to an OUTPROCESS_MODULE_CONFIG structure. ]*/ + fullModuleConfiguration->remote_message_wait = ep->remote_message_wait; + fullModuleConfiguration->lifecycle_model = OUTPROCESS_LIFECYCLE_SYNC; + } + } + + return (void *)fullModuleConfiguration; } static void OutprocessModuleLoader_FreeModuleConfiguration(const struct MODULE_LOADER_TAG* loader, const void* module_configuration) { - (void)loader; - if (module_configuration != NULL) - { - /*Codes_SRS_OUTPROCESS_LOADER_17_037: [ This function shall release all memory allocated by OutprocessModuleLoader_BuildModuleConfiguration. ]*/ - OUTPROCESS_MODULE_CONFIG * config = (OUTPROCESS_MODULE_CONFIG*)module_configuration; - STRING_delete(config->control_uri); - STRING_delete(config->message_uri); - STRING_delete(config->outprocess_module_args); - free(config); - } + (void)loader; + if (module_configuration != NULL) + { + /*Codes_SRS_OUTPROCESS_LOADER_17_037: [ This function shall release all memory allocated by OutprocessModuleLoader_BuildModuleConfiguration. ]*/ + OUTPROCESS_MODULE_CONFIG * config = (OUTPROCESS_MODULE_CONFIG*)module_configuration; + STRING_delete(config->control_uri); + STRING_delete(config->message_uri); + STRING_delete(config->outprocess_module_args); + free(config); + } } @@ -393,6 +753,6 @@ static MODULE_LOADER OutProcess_Module_Loader = const MODULE_LOADER* OutprocessLoader_Get(void) { - /*Codes_SRS_OUTPROCESS_LOADER_17_038: [ OutprocessModuleLoader_Get shall return a non-NULL pointer to a MODULE_LOADER struct. ]*/ + /*Codes_SRS_OUTPROCESS_LOADER_17_038: [ OutprocessModuleLoader_Get shall return a non-NULL pointer to a MODULE_LOADER struct. ]*/ return &OutProcess_Module_Loader; } diff --git a/samples/proxy_sample/CMakeLists.txt b/samples/proxy_sample/CMakeLists.txt index 4c96ca22..d35f0973 100644 --- a/samples/proxy_sample/CMakeLists.txt +++ b/samples/proxy_sample/CMakeLists.txt @@ -28,12 +28,14 @@ set(proxy_sample_main_sources if(WIN32) set(proxy_sample_main_sources ${proxy_sample_main_sources} + ./src/launch_sample_win.json ./src/proxy_sample_win.json ) set_source_files_properties(./src/proxy_sample_win.json PROPERTIES HEADER_FILE_ONLY ON) else() set(proxy_sample_main_sources ${proxy_sample_main_sources} + ./src/launch_sample_lin.json ./src/proxy_sample_lin.json ) set_source_files_properties(./src/proxy_sample_lin.json PROPERTIES HEADER_FILE_ONLY ON) diff --git a/samples/proxy_sample/src/launch_sample_lin.json b/samples/proxy_sample/src/launch_sample_lin.json new file mode 100644 index 00000000..d4d2b764 --- /dev/null +++ b/samples/proxy_sample/src/launch_sample_lin.json @@ -0,0 +1,53 @@ +{ + "modules": [ + { + "name": "logger", + "loader": { + "name": "native", + "entrypoint": { + "module.path": "../../modules/logger/liblogger.so" + } + }, + "args": { + "filename": "log.txt" + } + }, + { + "name": "out of process", + "loader": { + "name": "outprocess", + "entrypoint": { + "activation.type": "launch", + "control.id": "outprocess_module_control", + "launch": { + "path": "proxy_sample_remote.exe", + "args": [ + "outprocess_module_control" + ] + } + } + }, + "args": "THIS IS THE OOP MODULE CONFIGURATION" + }, + { + "name": "hello_world", + "loader": { + "name": "native", + "entrypoint": { + "module.path": "../../modules/hello_world/libhello_world.so" + } + }, + "args": null + } + ], + "links": [ + { + "source": "hello_world", + "sink": "out of process" + }, + { + "source": "out of process", + "sink": "logger" + } + ] +} diff --git a/samples/proxy_sample/src/launch_sample_win.json b/samples/proxy_sample/src/launch_sample_win.json new file mode 100644 index 00000000..4fbf0f64 --- /dev/null +++ b/samples/proxy_sample/src/launch_sample_win.json @@ -0,0 +1,53 @@ +{ + "modules": [ + { + "name": "logger", + "loader": { + "name": "native", + "entrypoint": { + "module.path": "..\\..\\..\\modules\\logger\\Debug\\logger.dll" + } + }, + "args": { + "filename": "log.txt" + } + }, + { + "name": "out of process", + "loader": { + "name": "outprocess", + "entrypoint": { + "activation.type": "launch", + "control.id": "outprocess_module_control", + "launch": { + "path": "Debug\\proxy_sample_remote.exe", + "args": [ + "outprocess_module_control" + ] + } + } + }, + "args": "THIS IS THE OOP MODULE CONFIGURATION" + }, + { + "name": "hello_world", + "loader": { + "name": "native", + "entrypoint": { + "module.path": "..\\..\\..\\modules\\hello_world\\Debug\\hello_world.dll" + } + }, + "args": null + } + ], + "links": [ + { + "source": "hello_world", + "sink": "out of process" + }, + { + "source": "out of process", + "sink": "logger" + } + ] +} \ No newline at end of file diff --git a/samples/proxy_sample/src/remote_main.c b/samples/proxy_sample/src/remote_main.c index 32df0724..1a18aeb3 100644 --- a/samples/proxy_sample/src/remote_main.c +++ b/samples/proxy_sample/src/remote_main.c @@ -20,7 +20,7 @@ MODULE_HANDLE RemoteSample_Create(BROKER_HANDLE broker, const void* configuratio { (void)configuration; printf("Remote Module Create\n"); - theBrokerHandle = broker; + theBrokerHandle = broker; MODULE_HANDLE m = (MODULE_HANDLE)"remote module"; return m; } @@ -31,7 +31,7 @@ void RemoteSample_Destroy(MODULE_HANDLE moduleHandle) void RemoteSample_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE messageHandle) { printf("Remote Module Receive: %s, %p\n", (char *)moduleHandle, messageHandle); - Broker_Publish(theBrokerHandle, moduleHandle, messageHandle); + Broker_Publish(theBrokerHandle, moduleHandle, messageHandle); } void RemoteSample_Start(MODULE_HANDLE moduleHandle) { @@ -56,6 +56,16 @@ const MODULE_API * pRemoteModuleApi = (const MODULE_API *)&remoteModuleApi; int main(int argc, char** argv) { REMOTE_MODULE_HANDLE remote_module; + + // Log received parameters to file + FILE * param_log = fopen("param.txt", "w+"); + if (param_log) { + for (int i = 0; i < argc; ++i) { + fprintf(param_log, "%s\n", argv[i]); + } + fclose(param_log); + } + if (argc != 2) { printf("usage: proxy_sample_remote control_channel_id\n"); diff --git a/tools/build_libuv.cmd b/tools/build_libuv.cmd new file mode 100644 index 00000000..e6b4dd6a --- /dev/null +++ b/tools/build_libuv.cmd @@ -0,0 +1,56 @@ +@REM Copyright (c) Microsoft. All rights reserved. +@REM Licensed under the MIT license. See LICENSE file in the project root for full license information. + +@setlocal EnableExtensions EnableDelayedExpansion +@echo off + +set current-path=%~dp0 + +rem Remove trailing slash +set current-path=%current-path:~0,-1% + +set build-root=%current-path%\..\build_libuv + +rem Resolve to fully qualified path +for %%i in ("%build-root%") do set build-root=%%~fi + +rem Clear the nodejs build folder so we have a fresh build +rmdir /s/q %build-root% +mkdir %build-root% + +pushd %build-root% + +rem Clone libuv +git clone https://github.com/libuv/libuv.git +pushd libuv +git checkout -b v1.10.1 tags/v1.10.1 + +rem The following 2 variables must be un-defined because libuv's +rem vcbuild.bat skips its VS version detection code if these are +rem defined and defaults to using VS 2012. +set "WindowsSDKDir_Value=%WindowsSDKDir%" +set WindowsSDKDir= +set "VCINSTALLDIR=%VCINSTALLDIR%" +set VCINSTALLDIR= + +rem Build libuv +call vcbuild.bat release %1 +popd + +rem Create a 'dist' folder where the includes/libs live +mkdir dist\include +copy libuv\include\*.h dist\include + +mkdir dist\lib +copy libuv\Release\lib\libuv.lib dist\lib + +popd + +rem Export environment variables for where the include/lib files can be found +set "LIBUV_INCLUDE=%build-root%\dist\inc" +set "LIBUV_LIB=%build-root%\dist\lib" + +rem Set environment variables for where the include/lib files can be found +@endlocal & set LIBUV_INCLUDE=%build-root%\dist\include\ & set LIBUV_LIB=%build-root%\dist\lib\ + +goto :eof diff --git a/tools/build_libuv.sh b/tools/build_libuv.sh new file mode 100755 index 00000000..fce3c908 --- /dev/null +++ b/tools/build_libuv.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file +# in the project root for full license information. + +set -e + +build_root=$(cd "$(dirname "$0")/.." && pwd) +build_root=$build_root/build_libuv + +# clear the libuv build folder so we have a fresh build +rm -rf $build_root +mkdir -p $build_root + +# build libuv +pushd $build_root +git clone https://github.com/libuv/libuv.git +cd libuv +git checkout -b v1.11.0 tags/v1.11.0 +sh autogen.sh +./configure --prefix=$build_root/dist/ CFLAGS='-fPIC' CXXFLAGS='-fPIC' +make -j $(nproc) +make install +popd diff --git a/tools/gen_docs.sh b/tools/gen_docs.sh old mode 100644 new mode 100755 diff --git a/tools/inteledison_c.sh b/tools/inteledison_c.sh index 41a116c4..76e58175 100755 --- a/tools/inteledison_c.sh +++ b/tools/inteledison_c.sh @@ -110,7 +110,7 @@ cmake_root="$build_root"/build rm -r -f "$cmake_root" mkdir -p "$cmake_root" pushd "$cmake_root" -cmake $dependency_install_prefix -DCMAKE_TOOLCHAIN_FILE=$FILE -DCMAKE_BUILD_TYPE=Debug -Drun_e2e_tests:BOOL=OFF -Drun_unittests:BOOL=OFF -Drun_valgrind:BOOL=OFF "$build_root" +cmake $dependency_install_prefix -DCMAKE_TOOLCHAIN_FILE=$FILE -DCMAKE_BUILD_TYPE=Debug -Drun_e2e_tests:BOOL=OFF -Drun_unittests:BOOL=OFF -Denable_native_remote_modules:BOOL=OFF -Drun_valgrind:BOOL=OFF "$build_root" [ $? -eq 0 ] || exit $? make --jobs=$(nproc) diff --git a/tools/windriver_linux_c.sh b/tools/windriver_linux_c.sh index a5fe9051..1e4b9838 100755 --- a/tools/windriver_linux_c.sh +++ b/tools/windriver_linux_c.sh @@ -108,7 +108,7 @@ cmake_root="$build_root"/build rm -r -f "$cmake_root" mkdir -p "$cmake_root" pushd "$cmake_root" -cmake $TOOLCHAIN_OPTION -Ddependency_install_prefix=$local_install -DCMAKE_BUILD_TYPE=Debug -Drun_unittests:BOOL=OFF -Drun_e2e_tests:BOOL=OFF -Drun_valgrind:BOOL=OFF "$build_root" +cmake $TOOLCHAIN_OPTION -Ddependency_install_prefix=$local_install -DCMAKE_BUILD_TYPE=Debug -Drun_unittests:BOOL=OFF -Drun_e2e_tests:BOOL=OFF -Denable_native_remote_modules:BOOL=OFF -Drun_valgrind:BOOL=OFF "$build_root" [ $? -eq 0 ] || exit $? make --jobs=$(nproc)