diff --git a/CMakeLists.txt b/CMakeLists.txt index b6183a0..14a4c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,16 @@ -cmake_minimum_required (VERSION 3.16.3) -project (TESTCONTAINERS-C) - -include(CTest) - -include_directories(${CMAKE_CURRENT_BINARY_DIR}/testcontainers-c) - -add_subdirectory(testcontainers-c) -add_subdirectory(modules) -if(NOT DEFINED SKIP_DEMOS) - add_subdirectory(demo) -endif() +cmake_minimum_required (VERSION 3.26) +project (TESTCONTAINERS-C + VERSION 0.1.0 + DESCRIPTION "Testcontainers for C and other native languages" + LANGUAGES C CXX +) + +include(GNUInstallDirs) +include(CTest) + +add_subdirectory(testcontainers-bridge) +add_subdirectory(testcontainers-c) +add_subdirectory(modules) +if(NOT DEFINED SKIP_DEMOS) + add_subdirectory(demo) +endif() diff --git a/demo/generic-container/CMakeLists.txt b/demo/generic-container/CMakeLists.txt index 7559e5f..716c9bd 100644 --- a/demo/generic-container/CMakeLists.txt +++ b/demo/generic-container/CMakeLists.txt @@ -1,15 +1,15 @@ -project(testcontainers-c-generic-container-demo - VERSION 0.0.1 - DESCRIPTION "Demonstrates usage of the generic container API in a simple main app") +cmake_minimum_required (VERSION 3.26) +project (generic-container-demo + VERSION 0.1.0 + DESCRIPTION "Demonstrates usage of the generic container API in a simple main app" + LANGUAGES C +) -set(TARGET_OUT demo_generic_container.out) +set(TARGET_OUT ${PROJECT_NAME}.out) -include_directories(${testcontainers-c_SOURCE_DIR}) -# WORKING_DIRECTORY breaks shared lib loading file(COPY test_data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) # Vanilla Demo for WireMock add_executable(${TARGET_OUT} generic_container_demo.c) -add_dependencies(${TARGET_OUT} testcontainers-c-shim) target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c) add_test(NAME generic_container_demo COMMAND ${TARGET_OUT}) diff --git a/demo/generic-container/generic_container_demo.c b/demo/generic-container/generic_container_demo.c index ff600f4..417f840 100644 --- a/demo/generic-container/generic_container_demo.c +++ b/demo/generic-container/generic_container_demo.c @@ -1,5 +1,5 @@ #include -#include "testcontainers-c.h" +#include "testcontainers-c/container.h" #define DEFAULT_IMAGE "wiremock/wiremock:3.0.1-1" @@ -7,28 +7,29 @@ int main() { printf("Using WireMock with the Testcontainers C binding:\n"); printf("Creating new container: %s\n", DEFAULT_IMAGE); - int requestId = tc_new_container_request(DEFAULT_IMAGE); - tc_with_exposed_tcp_port(requestId, 8080); - tc_with_wait_for_http(requestId, 8080, "/__admin/mappings"); - tc_with_file(requestId, "test_data/hello.json", "/home/wiremock/mappings/hello.json"); - struct tc_run_container_return ret = tc_run_container(requestId); - int containerId = ret.r0; - if (!ret.r1) { - printf("Failed to run the container: %s\n", ret.r2); + int requestId = tc_container_create(DEFAULT_IMAGE); + tc_container_with_exposed_tcp_port(requestId, 8080); + tc_container_with_wait_for_http(requestId, 8080, "/__admin/mappings"); + tc_container_with_file(requestId, "test_data/hello.json", "/home/wiremock/mappings/hello.json"); + char* error; + int containerId = tc_container_run(requestId, error); + if (containerId == -1) { + printf("Failed to run the container: %s\n", error); return -1; } printf("Sending HTTP request to the container\n"); - struct tc_send_http_get_return response = tc_send_http_get(containerId, 8080, "/hello"); - if (response.r0 == -1) { - printf("Failed to send HTTP request: %s\n", response.r2); + char *response_body; + int response_code = tc_container_send_http_get(containerId, 8080, "/hello", response_body, error); + if (response_code == -1) { + printf("Failed to send HTTP request: %s\n", error); return -1; } - if (response.r0 != 200) { - printf("Received wrong response code: %d instead of %d\n%s\n%s\n", response.r0, 200, response.r1, response.r2); + if (response_code != 200) { + printf("Received wrong response code: %d instead of %d\n%s\n%s\n", response_code, 200, response_body, error); return -1; } - printf("Server Response: HTTP-%d\n%s\n\n", response.r0, response.r1); + printf("Server Response: HTTP-%d\n%s\n\n", response_code, response_body); return 0; } diff --git a/demo/google-test/CMakeLists.txt b/demo/google-test/CMakeLists.txt index ede0901..4d0bd09 100644 --- a/demo/google-test/CMakeLists.txt +++ b/demo/google-test/CMakeLists.txt @@ -1,8 +1,11 @@ # Google Test demo # This is based on https://google.github.io/googletest/quickstart-cmake.html -project(google-test-demo - VERSION 0.0.1 - DESCRIPTION "Demonstrates usage of Testcontainers C in Google Test") +cmake_minimum_required (VERSION 3.26) +project (google-test-demo + VERSION 0.1.0 + DESCRIPTION "Demonstrates usage of Testcontainers C in Google Test" + LANGUAGES CXX +) set(TARGET_OUT ${PROJECT_NAME}.out) @@ -12,7 +15,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare( googletest - URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.17.0 ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) @@ -21,7 +25,6 @@ enable_testing() file(COPY test_data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) add_executable(${TARGET_OUT} test.cpp) -add_dependencies(${TARGET_OUT} testcontainers-c-shim) target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c) target_link_libraries(${TARGET_OUT} PRIVATE GTest::gtest_main) diff --git a/demo/google-test/test.cpp b/demo/google-test/test.cpp index ae87581..56782c3 100644 --- a/demo/google-test/test.cpp +++ b/demo/google-test/test.cpp @@ -1,7 +1,10 @@ #include #include #include -#include "testcontainers-c.h" + +extern "C" { +#include "testcontainers-c/container.h" +} class WireMockTestContainer : public ::testing::Test { @@ -11,23 +14,23 @@ const char* WIREMOCK_ADMIN_MAPPING_ENDPOINT = "/__admin/mappings"; protected: void SetUp() override { std::cout << "Creating new container: " << WIREMOCK_IMAGE << '\n'; - - int requestId = tc_new_container_request(WIREMOCK_IMAGE); - tc_with_exposed_tcp_port(requestId, 8080); - tc_with_wait_for_http(requestId, 8080, WIREMOCK_ADMIN_MAPPING_ENDPOINT); - tc_with_file(requestId, "test_data/hello.json", "/home/wiremock/mappings/hello.json"); - tc_with_file(requestId, "test_data/hello_with_resource.json", "/home/wiremock/mappings/hello2.json"); - tc_with_file(requestId, "test_data/hello_with_missing_resource.json", "/home/wiremock/mappings/hello3.json"); - tc_with_file(requestId, "test_data/response.xml", "/home/wiremock/__files/response.xml"); - - struct tc_run_container_return ret = tc_run_container(requestId); - containerId = ret.r0; - - EXPECT_TRUE(ret.r1) << "Failed to run the container: " << ret.r2; + + int requestId = tc_container_create(WIREMOCK_IMAGE); + tc_container_with_exposed_tcp_port(requestId, 8080); + tc_container_with_wait_for_http(requestId, 8080, WIREMOCK_ADMIN_MAPPING_ENDPOINT); + tc_container_with_file(requestId, "test_data/hello.json", "/home/wiremock/mappings/hello.json"); + tc_container_with_file(requestId, "test_data/hello_with_resource.json", "/home/wiremock/mappings/hello2.json"); + tc_container_with_file(requestId, "test_data/hello_with_missing_resource.json", "/home/wiremock/mappings/hello3.json"); + tc_container_with_file(requestId, "test_data/response.xml", "/home/wiremock/__files/response.xml"); + + char* error; + int containerId = tc_container_run(requestId, error); + + EXPECT_TRUE(containerId != -1) << "Failed to run the container: " << error; }; void TearDown() override { - char* error = tc_terminate_container(containerId); + char* error = tc_container_terminate(containerId); ASSERT_EQ(error, nullptr) << "Failed to terminate the container after the test: " << error; }; @@ -36,32 +39,33 @@ const char* WIREMOCK_ADMIN_MAPPING_ENDPOINT = "/__admin/mappings"; TEST_F(WireMockTestContainer, HelloWorld) { std::cout << "Sending HTTP request to the container\n"; - struct tc_send_http_get_return response = tc_send_http_get(containerId, 8080, "/hello"); - - ASSERT_NE(response.r0, -1) << "Failed to send HTTP request: " << response.r2; - ASSERT_EQ(response.r0, 200) << "Received wrong response code: " << response.r1 << response.r2; - - std::cout << "Server Response: HTTP-" << response.r0 << '\n' << response.r1 << '\n'; + char *response_body; + char *error; + int response_code = tc_container_send_http_get(containerId, 8080, "/hello", response_body, error); + + ASSERT_NE(response_code, -1) << "Failed to send HTTP request: " << error; + ASSERT_EQ(response_code, 200) << "Received wrong response code: " << response_body << error; + + std::cout << "Server Response: HTTP-" << response_code << '\n' << response_body << '\n'; } TEST_F(WireMockTestContainer, HelloWorldFromResource) { std::cout << "Sending HTTP request to the container\n"; - struct tc_send_http_get_return response = tc_send_http_get(containerId, 8080, "/hello-from-resource"); - - ASSERT_NE(response.r0, -1) << "Failed to send HTTP request: " << response.r2; - ASSERT_EQ(response.r0, 200) << "Received wrong response code: " << response.r1 << response.r2; - - std::cout << "Server Response: HTTP-" << response.r0 << '\n' << response.r1 << '\n'; + char *response_body; + char *error; + int response_code = tc_container_send_http_get(containerId, 8080, "/hello-from-resource", response_body, error); + + ASSERT_NE(response_code, -1) << "Failed to send HTTP request: " << error; + ASSERT_EQ(response_code, 200) << "Received wrong response code: " << response_body << error; + + std::cout << "Server Response: HTTP-" << response_code << '\n' << response_body << '\n'; } TEST_F(WireMockTestContainer, HelloWorldFromMissingResource) { std::cout << "Sending HTTP request to the container\n"; - struct tc_send_http_get_return response = tc_send_http_get(containerId, 8080, "/hello-from-missing-resource"); - - ASSERT_EQ(response.r0, 500) << "The request should have failed"; -} + char *response_body; + char *error; + int response_code = tc_container_send_http_get(containerId, 8080, "/hello-from-missing-resource", response_body, error); -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ASSERT_EQ(response_code, 500) << "The request should have failed"; } diff --git a/demo/wiremock/CMakeLists.txt b/demo/wiremock/CMakeLists.txt index aa232ba..6f861c8 100644 --- a/demo/wiremock/CMakeLists.txt +++ b/demo/wiremock/CMakeLists.txt @@ -1,17 +1,16 @@ -project(testcontainers-c-wiremock-demo - VERSION 0.0.1 - DESCRIPTION "Demonstrates usage of the WireMock module for Testcontainers C in a simple main app") - -set(TARGET_OUT demo_wiremock_module.out) - -include_directories(${testcontainers-c_SOURCE_DIR}) -# WORKING_DIRECTORY breaks shared lib loading -file(COPY test_data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - -# WireMock Module demo -add_executable(${TARGET_OUT} wiremock_module_demo.c) -add_dependencies(${TARGET_OUT} testcontainers-c-shim) -target_include_directories(${TARGET_OUT} PRIVATE ${testcontainers-c-wiremock_SOURCE_DIR}) -target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c) -target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c-wiremock) -add_test(NAME wiremock_module_demo COMMAND ${TARGET_OUT}) +cmake_minimum_required (VERSION 3.26) +project (wiremock-demo + VERSION 0.1.0 + DESCRIPTION "Demonstrates usage of the WireMock module for Testcontainers C in a simple main app" + LANGUAGES C +) + +set(TARGET_OUT demo_wiremock_module.out) + +file(COPY test_data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +# WireMock Module demo +add_executable(${TARGET_OUT} wiremock_module_demo.c) +target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c) +target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c-wiremock) +add_test(NAME wiremock_module_demo COMMAND ${TARGET_OUT}) diff --git a/demo/wiremock/wiremock_module_demo.c b/demo/wiremock/wiremock_module_demo.c index 9e70598..740de8d 100644 --- a/demo/wiremock/wiremock_module_demo.c +++ b/demo/wiremock/wiremock_module_demo.c @@ -1,6 +1,7 @@ #include #include #include "testcontainers-c-wiremock.h" +#include "testcontainers-c/container.h" int main() { printf("Using WireMock with the Testcontainers C binding:\n"); @@ -8,14 +9,14 @@ int main() { printf("Creating new container: %s\n", DEFAULT_WIREMOCK_IMAGE); int requestId = tc_wm_new_default_container(); //FIXME: This method is bogus - tc_wm_with_mapping(requestId, "test_data/hello.json", "hello"); - tc_with_file(requestId, "test_data/hello.json", "/home/wiremock/mappings/hello2.json"); - struct tc_run_container_return ret = tc_run_container(requestId); - int containerId = ret.r0; - if (!ret.r1) { - printf("Failed to run the container: %s\n", ret.r2); + // tc_wm_with_mapping(requestId, "test_data/hello.json", "hello"); + tc_container_with_file(requestId, "test_data/hello.json", "/home/wiremock/mappings/hello2.json"); + char* error; + int containerId = tc_container_run(requestId, error); + if (containerId == -1) { + printf("Failed to run the container: %s\n", error); if (containerId != -1) { // Print container log - char* log = tc_get_container_log(containerId); + char* log = tc_container_get_log(containerId); if (log != NULL) { printf("\n%s\n", log); } @@ -33,16 +34,17 @@ int main() { } printf("Sending HTTP request to the container\n"); - struct tc_send_http_get_return response = tc_send_http_get(containerId, 8080, "/hello"); - if (response.r0 == -1) { - printf("Failed to send HTTP request: %s\n", response.r2); + char *response_body; + int response_code = tc_container_send_http_get(containerId, 8080, "/hello", response_body, error); + if (response_code == -1) { + printf("Failed to send HTTP request: %s\n", error); return -1; } - if (response.r0 != 200) { - printf("Received wrong response code: %d instead of %d\n%s\n", response.r0, 200, response.r2); + if (response_code != 200) { + printf("Received wrong response code: %d instead of %d\n%s\n", response_code, 200, error); return -1; } - printf("Server Response: HTTP-%d\n%s\n\n", response.r0, response.r1); + printf("Server Response: HTTP-%d\n%s\n\n", response_code, response_body); return 0; } diff --git a/docs/c/README.md b/docs/c/README.md index ef5ccf2..b6a29de 100644 --- a/docs/c/README.md +++ b/docs/c/README.md @@ -121,7 +121,6 @@ enable_testing() file(COPY test_data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) add_executable(${TARGET_OUT} mytest.cpp) -add_dependencies(${TARGET_OUT} testcontainers-c-shim) target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c) add_test(NAME wiremock_module_demo COMMAND ${TARGET_OUT}) ``` diff --git a/modules/wiremock/CMakeLists.txt b/modules/wiremock/CMakeLists.txt index 640928a..6527ca6 100644 --- a/modules/wiremock/CMakeLists.txt +++ b/modules/wiremock/CMakeLists.txt @@ -1,24 +1,22 @@ +set(TARGET testcontainers-c-wiremock) +set(TARGET_NAME ${TARGET}) +set(TARGET_DESCRIPTION "Wiremock testcontainer abstractions for C") +set(TARGET_VERSION ${PROJECT_VERSION}) -cmake_minimum_required(VERSION 3.9) -project(testcontainers-c-wiremock VERSION 0.0.1 - DESCRIPTION "WireMock module for Testcontainers C") - -include(GNUInstallDirs) - -add_library(${PROJECT_NAME} SHARED - testcontainers-c-wiremock.h +add_library(${TARGET} SHARED impl.c ) -add_dependencies(${PROJECT_NAME} testcontainers-c) -include_directories(${testcontainers-c_SOURCE_DIR}) +target_sources(${TARGET} + PUBLIC FILE_SET HEADERS + BASE_DIRS . + FILES testcontainers-c-wiremock.h +) -set_target_properties(${PROJECT_NAME} PROPERTIES - VERSION ${PROJECT_VERSION} - PUBLIC_HEADER testcontainers-c-wiremock.h) +target_link_libraries(${TARGET} PRIVATE testcontainers-c) -configure_file(cmake.pc.in ${PROJECT_NAME}.pc @ONLY) -install(TARGETS ${PROJECT_NAME} +configure_file(cmake.pc.in ${TARGET}.pc @ONLY) +install(TARGETS ${TARGET} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) +install(FILES ${CMAKE_BINARY_DIR}/${TARGET}.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) diff --git a/modules/wiremock/cmake.pc.in b/modules/wiremock/cmake.pc.in index 9b15f62..8c83ec9 100644 --- a/modules/wiremock/cmake.pc.in +++ b/modules/wiremock/cmake.pc.in @@ -3,10 +3,10 @@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ -Name: @PROJECT_NAME@ -Description: @PROJECT_DESCRIPTION@ -Version: @PROJECT_VERSION@ +Name: @TARGET_NAME@ +Description: @TARGET_DESCRIPTION@ +Version: @TARGET_VERSION@ Requires: -Libs: -L${libdir} -lmylib +Libs: -L${libdir} Cflags: -I${includedir} diff --git a/modules/wiremock/impl.c b/modules/wiremock/impl.c index ad0bf34..6462566 100644 --- a/modules/wiremock/impl.c +++ b/modules/wiremock/impl.c @@ -1,23 +1,24 @@ #include "testcontainers-c-wiremock.h" +#include "testcontainers-c/container.h" #include #include -GoInt tc_wm_new_default_container() { +int tc_wm_new_default_container() { return tc_wm_new_container(DEFAULT_WIREMOCK_IMAGE); } -GoInt tc_wm_new_container(char* image) { - GoInt requestId = tc_new_container_request(image); - tc_with_exposed_tcp_port(requestId, 8080); - tc_with_wait_for_http(requestId, 8080, "/__admin/mappings"); +int tc_wm_new_container(char* image) { + int requestId = tc_container_create(image); + tc_container_with_exposed_tcp_port(requestId, 8080); + tc_container_with_wait_for_http(requestId, 8080, "/__admin/mappings"); return requestId; }; -void tc_wm_with_mapping(GoInt requestID, char* filePath, char* destination) { +void tc_wm_with_mapping(int requestID, char* filePath, char* destination) { char dest_file[128] = ""; strcat(dest_file, WIREMOCK_MAPPINGS_DIR); strcat(dest_file, destination); - + // Append extension if missing if(strlen(destination) > 5 && !strcmp(destination + strlen(destination) - 5, ".json")) { strcat(dest_file, ".json"); @@ -27,21 +28,23 @@ void tc_wm_with_mapping(GoInt requestID, char* filePath, char* destination) { } printf("DEBUG: %s to %s.\n", filePath, dest_file); - tc_with_file(requestID, filePath, dest_file); + tc_container_with_file(requestID, filePath, dest_file); }; -struct WireMock_Mapping tc_wm_get_mappings(GoInt containerId) { - struct tc_send_http_get_return response = tc_send_http_get(containerId, 8080, "/__admin/mappings"); - if (response.r0 == -1) { +struct WireMock_Mapping tc_wm_get_mappings(int containerId) { + char *response_body; + char *error; + int response_code = tc_container_send_http_get(containerId, 8080, "/__admin/mappings", response_body, error); + if (response_code == -1) { char errorMsg[8000] = ""; - sprintf(errorMsg, "Failed to send HTTP request: %s\n", response.r2); + sprintf(errorMsg, "Failed to send HTTP request: %s\n", error); return (struct WireMock_Mapping) { -1, NULL, errorMsg}; } - if (response.r0 != 200) { + if (response_code != 200) { char errorMsg[8000] = ""; - sprintf(errorMsg, "Received wrong response code: %d instead of %d\n%s\n%s\n", response.r0, 200, response.r1, response.r2); - return (struct WireMock_Mapping) { response.r0, NULL, errorMsg}; + sprintf(errorMsg, "Received wrong response code: %d instead of %d\n%s\n%s\n", response_code, 200, response_body, error); + return (struct WireMock_Mapping) { response_code, NULL, errorMsg}; } - return (struct WireMock_Mapping) {response.r0, response.r1, NULL}; + return (struct WireMock_Mapping) {response_code, response_body, NULL}; }; diff --git a/modules/wiremock/testcontainers-c-wiremock.h b/modules/wiremock/testcontainers-c-wiremock.h index 4ed78b9..a892182 100644 --- a/modules/wiremock/testcontainers-c-wiremock.h +++ b/modules/wiremock/testcontainers-c-wiremock.h @@ -1,5 +1,3 @@ -#include "testcontainers-c.h" - #ifndef TESTCONTAINERS_WIREMOCK_H #define TESTCONTAINERS_WIREMOCK_H @@ -13,29 +11,29 @@ struct WireMock_Mapping { - GoInt responseCode; + int responseCode; char* json; char* error; }; /// @brief Creates a container request with a default image, exposed port and init logic /// @return Container request ID -GoInt tc_wm_new_default_container(); +int tc_wm_new_default_container(); /// @brief Creates a container request with a default image, exposed port and init logic /// @param image Full image name /// @return Container request ID -GoInt tc_wm_new_container(char* image); +int tc_wm_new_container(char* image); /// @brief Adds WireMock mapping to the request /// @param requestID Container Request ID /// @param filePath Source file in the local filesystem /// @param destination Destination file, relative to the mappings dir. Extension is optional -void tc_wm_with_mapping(GoInt requestID, char* filePath, char* destination); +void tc_wm_with_mapping(int requestID, char* filePath, char* destination); /// @brief Gets WireMock mappings using Admin API /// @param containerId Container ID /// @return Mapping information if response code is 200, error details otherwise -struct WireMock_Mapping tc_wm_get_mappings(GoInt containerId); +struct WireMock_Mapping tc_wm_get_mappings(int containerId); #endif diff --git a/testcontainers-bridge/CMakeLists.txt b/testcontainers-bridge/CMakeLists.txt new file mode 100644 index 0000000..fff34e4 --- /dev/null +++ b/testcontainers-bridge/CMakeLists.txt @@ -0,0 +1,29 @@ +set(SRCS testcontainers-bridge.go) + +set(SHIM_TARGET testcontainers-bridge-shim) +set(SHIM_TARGET_LIB testcontainers-bridge.so) +set(SHIM_TARGET_HEADER testcontainers-bridge.h) + +add_custom_command(OUTPUT ${SHIM_TARGET_LIB} ${SHIM_TARGET_HEADER} + DEPENDS ${SRCS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND env GOPATH=${GOPATH} go build -buildmode=c-archive -linkshared + -o "${CMAKE_CURRENT_BINARY_DIR}/${SHIM_TARGET_LIB}" + ${CMAKE_GO_FLAGS} ${SRCS} + COMMENT "Building Testcontainers Go to C bridge library") + +add_custom_target(${SHIM_TARGET} DEPENDS ${SHIM_TARGET_LIB} ${SHIM_TARGET_HEADER}) + +set(TARGET testcontainers-bridge) +set(TARGET_NAME ${TARGET}) +set(TARGET_DESCRIPTION "Go to C bridge for Testcontainers functionality") +set(TARGET_VERSION ${PROJECT_VERSION}) + +add_library(${TARGET} STATIC IMPORTED GLOBAL) +add_dependencies(${TARGET} ${SHIM_TARGET}) +set_target_properties(${TARGET} PROPERTIES + LINKER_LANGUAGE C + VERSION ${TARGET_VERSION} + PUBLIC_HEADER ${SHIM_TARGET_HEADER} + IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${SHIM_TARGET_LIB} + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/testcontainers-c/go.mod b/testcontainers-bridge/go.mod similarity index 97% rename from testcontainers-c/go.mod rename to testcontainers-bridge/go.mod index 201e396..778b9ac 100644 --- a/testcontainers-c/go.mod +++ b/testcontainers-bridge/go.mod @@ -1,4 +1,4 @@ -module github.com/testcontainers/testcontainers-c +module github.com/testcontainers/testcontainers-native go 1.19 diff --git a/testcontainers-c/go.sum b/testcontainers-bridge/go.sum similarity index 100% rename from testcontainers-c/go.sum rename to testcontainers-bridge/go.sum diff --git a/testcontainers-c/testcontainers-c.go b/testcontainers-bridge/testcontainers-bridge.go similarity index 78% rename from testcontainers-c/testcontainers-c.go rename to testcontainers-bridge/testcontainers-bridge.go index 769b721..6acef3f 100644 --- a/testcontainers-c/testcontainers-c.go +++ b/testcontainers-bridge/testcontainers-bridge.go @@ -21,8 +21,8 @@ var customizers map[int][]*testcontainers.CustomizeRequestOption // Creates Unique container request and returns its ID // -//export tc_new_container_request -func tc_new_container_request(image *C.cchar_t) (id int) { +//export tc_bridge_new_container_request +func tc_bridge_new_container_request(image *C.cchar_t) (id int) { req := testcontainers.ContainerRequest{ Image: C.GoString(image), } @@ -31,8 +31,8 @@ func tc_new_container_request(image *C.cchar_t) (id int) { return len(containerRequests) - 1 } -//export tc_run_container -func tc_run_container(requestID int) (id int, ok bool, errstr *C.char) { +//export tc_bridge_run_container +func tc_bridge_run_container(requestID int) (id int, ok bool, errstr *C.char) { id, ok, err := _RunContainer(requestID) if err != nil { return -1, ok, ToCString(err) @@ -68,15 +68,15 @@ func _RunContainer(requestID int) (id int, ok bool, err error) { return containerId, true, nil } -//export tc_terminate_container -func tc_terminate_container(containerID int) *C.char { +//export tc_bridge_terminate_container +func tc_bridge_terminate_container(containerID int) *C.char { ctx := context.Background() container := *containers[containerID] return ToCString(container.Terminate(ctx)) } -//export tc_get_container_log -func tc_get_container_log(containerID int) (log *C.char) { +//export tc_bridge_get_container_log +func tc_bridge_get_container_log(containerID int) (log *C.char) { ctx := context.Background() container := *containers[containerID] @@ -94,11 +94,15 @@ func tc_get_container_log(containerID int) (log *C.char) { return C.CString(string(bytes)) } -//export tc_get_uri -func tc_get_uri(containerID int, port int) (uri string, e error) { +//export tc_bridge_get_uri +func tc_bridge_get_uri(containerID int, port int) (uri *C.char, ok bool, errstr *C.char) { ctx := context.Background() container := *containers[containerID] - return _GetURI(ctx, container, port) + str, err := _GetURI(ctx, container, port) + if err != nil { + return nil, false, ToCString(err) + } + return C.CString(str), true, nil } func _GetURI(ctx context.Context, container testcontainers.Container, port int) (string, error) { @@ -115,8 +119,8 @@ func _GetURI(ctx context.Context, container testcontainers.Container, port int) return "http://" + hostIP + ":" + mappedPort.Port(), nil } -//export tc_with_wait_for_http -func tc_with_wait_for_http(requestID int, port int, url *C.cchar_t) { +//export tc_bridge_with_wait_for_http +func tc_bridge_with_wait_for_http(requestID int, port int, url *C.cchar_t) { req := func(req *testcontainers.GenericContainerRequest) { req.WaitingFor = wait.ForHTTP(C.GoString(url)).WithPort(nat.Port(strconv.Itoa(port))) } @@ -124,8 +128,8 @@ func tc_with_wait_for_http(requestID int, port int, url *C.cchar_t) { registerCustomizer(requestID, req) } -//export tc_with_file -func tc_with_file(requestID int, filePath *C.cchar_t, targetPath *C.cchar_t) { +//export tc_bridge_with_file +func tc_bridge_with_file(requestID int, filePath *C.cchar_t, targetPath *C.cchar_t) { req := func(req *testcontainers.GenericContainerRequest) { cfgFile := testcontainers.ContainerFile{ HostFilePath: C.GoString(filePath), @@ -138,8 +142,8 @@ func tc_with_file(requestID int, filePath *C.cchar_t, targetPath *C.cchar_t) { registerCustomizer(requestID, req) } -//export tc_with_exposed_tcp_port -func tc_with_exposed_tcp_port(requestID int, port int) { +//export tc_bridge_with_exposed_tcp_port +func tc_bridge_with_exposed_tcp_port(requestID int, port int) { req := func(req *testcontainers.GenericContainerRequest) { req.ExposedPorts = append(req.ExposedPorts, strconv.Itoa(port)+"/tcp") } @@ -155,8 +159,8 @@ func registerCustomizer(requestID int, customizer testcontainers.CustomizeReques return len(customizers[requestID]) - 1 } -//export tc_send_http_get -func tc_send_http_get(containerID int, port int, endpoint *C.cchar_t) (responseCode C.int, responseBody *C.char, errstr *C.char) { +//export tc_bridge_send_http_get +func tc_bridge_send_http_get(containerID int, port int, endpoint *C.cchar_t) (responseCode C.int, responseBody *C.char, errstr *C.char) { container := *containers[containerID] responseCodeVal, responseBodyStr, err := SendHttpRequest(http.MethodGet, container, port, C.GoString(endpoint), nil) if err != nil { diff --git a/testcontainers-c/CMakeLists.txt b/testcontainers-c/CMakeLists.txt index 9583bd6..611e326 100644 --- a/testcontainers-c/CMakeLists.txt +++ b/testcontainers-c/CMakeLists.txt @@ -1,37 +1,23 @@ -cmake_minimum_required(VERSION 3.0) -project(testcontainers-c VERSION 0.0.1 - DESCRIPTION "Testcontainers C library") - -include(GNUInstallDirs) - -set(SRCS testcontainers-c.go) - -set(TARGET testcontainers-c-shim) -set(TARGET_LIB testcontainers-c.so) -set(TARGET_HEADER testcontainers-c.h) - -add_custom_command(OUTPUT ${TARGET_LIB} ${TARGET_HEADER} - DEPENDS ${SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND env GOPATH=${GOPATH} go build -buildmode=c-shared - -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_LIB}" - ${CMAKE_GO_FLAGS} ${SRCS} - COMMENT "Building Testcontainers C shared library") - -add_custom_target(${TARGET} DEPENDS ${TARGET_LIB} ${TARGET_HEADER}) - -add_library(${PROJECT_NAME} SHARED IMPORTED GLOBAL) -add_dependencies(${PROJECT_NAME} ${TARGET}) -set_target_properties(${PROJECT_NAME} PROPERTIES - LINKER_LANGUAGE C - VERSION ${PROJECT_VERSION} - PUBLIC_HEADER testcontainers-c.h - IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_LIB} - INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) - -configure_file(cmake.pc.in ${PROJECT_NAME}.pc @ONLY) -install(FILES ${TARGET_LIB} - DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(FILES ${TARGET_HEADER} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) +set(TARGET testcontainers-c) +set(TARGET_NAME ${TARGET}) +set(TARGET_DESCRIPTION "Testcontainer library for C") +set(TARGET_VERSION ${PROJECT_VERSION}) + +add_library(${TARGET} SHARED + src/container.c +) + +target_sources(${TARGET} + PUBLIC FILE_SET HEADERS + BASE_DIRS include + FILES include/testcontainers-c/container.h +) + +target_link_libraries(${TARGET} PRIVATE testcontainers-bridge) + +configure_file(cmake.pc.in ${TARGET}.pc @ONLY) +install(FILES ${SHIM_TARGET_LIB} + DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(FILES ${SHIM_TARGET_HEADER} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(FILES ${CMAKE_BINARY_DIR}/${TARGET}.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) diff --git a/testcontainers-c/cmake.pc.in b/testcontainers-c/cmake.pc.in index 9b15f62..8c83ec9 100644 --- a/testcontainers-c/cmake.pc.in +++ b/testcontainers-c/cmake.pc.in @@ -3,10 +3,10 @@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ -Name: @PROJECT_NAME@ -Description: @PROJECT_DESCRIPTION@ -Version: @PROJECT_VERSION@ +Name: @TARGET_NAME@ +Description: @TARGET_DESCRIPTION@ +Version: @TARGET_VERSION@ Requires: -Libs: -L${libdir} -lmylib +Libs: -L${libdir} Cflags: -I${includedir} diff --git a/testcontainers-c/include/testcontainers-c/container.h b/testcontainers-c/include/testcontainers-c/container.h new file mode 100644 index 0000000..938ec19 --- /dev/null +++ b/testcontainers-c/include/testcontainers-c/container.h @@ -0,0 +1,59 @@ +#ifndef CONTAINER_H +#define CONTAINER_H + +/** + * @param The container image name. + * @return -1 if no image is provided or creation fails. Otherwise, + * the ID of the created container. + */ +int tc_container_create(const char* image); + +/** + * + */ +int tc_container_run(int container_id, char* error); + +/** + * + */ +char* tc_container_terminate(int container_id); + +/** + * + */ +char* tc_container_get_log(int container_id); + +/** + * Retrieves the URI for container with id `container_id` + * It optionally stores an error message in `error`. + */ +char* tc_container_get_uri(int container_id, int port, char* error); + +/** + * + */ +void tc_container_with_wait_for_http(int request_id, int port, const char* url); + +/** + * + */ +void tc_container_with_file(int request_id, const char* file_path, const char* target_path); + +/** + * + */ +void tc_container_with_exposed_tcp_port(int request_id, int port); + +/** + * Sends an HTTP GET request to an endpoint in a container. + * + * @param container_id The ID of the container. + * @param port The port of the container HTTP server. + * @param endpoint The endpoint to request. + * @param response_body The body of the response. + * @param error The error message if the request fails. + * @return Response code from the HTTP request. + */ +int tc_container_send_http_get(int container_id, int port, const char* endpoint, char* response_body, char* error); + +#endif // !CONTAINER_H diff --git a/testcontainers-c/src/container.c b/testcontainers-c/src/container.c new file mode 100644 index 0000000..3f84567 --- /dev/null +++ b/testcontainers-c/src/container.c @@ -0,0 +1,67 @@ +#include + +#include "testcontainers-c/container.h" + +int tc_container_create(const char* image) { + if (image == NULL) { + return -1; + } + + return tc_bridge_new_container_request(image); +} + +int tc_container_run(int container_id, char* error) { + struct tc_bridge_run_container_return result = tc_bridge_run_container(container_id); + + if (error != NULL) { + error = result.r2; + } + + if (!result.r1) { + return -1; + } + + return result.r0; +} + +char* tc_container_terminate(int container_id) { return tc_bridge_terminate_container(container_id); } + +char* tc_container_get_log(int container_id) { return tc_bridge_get_container_log(container_id); } + +char* tc_container_get_uri(int container_id, int port, char* error) { + struct tc_bridge_get_uri_return result = tc_bridge_get_uri(container_id, port); + + if (error != NULL) { + error = result.r2; + } + + if (result.r1) { + return result.r0; + } + + return NULL; +} + +void tc_container_with_wait_for_http(int request_id, int port, const char* url) { + tc_bridge_with_wait_for_http(request_id, port, url); +} + +void tc_container_with_file(int request_id, const char* file_path, const char* target_path) { + tc_bridge_with_file(request_id, file_path, target_path); +} + +void tc_container_with_exposed_tcp_port(int request_id, int port) { tc_bridge_with_exposed_tcp_port(request_id, port); } + +int tc_container_send_http_get(int container_id, int port, const char* endpoint, char* response_body, char* error) { + struct tc_bridge_send_http_get_return result = tc_bridge_send_http_get(container_id, port, endpoint); + + if (error != NULL) { + error = result.r2; + } + + if (response_body != NULL) { + response_body = result.r1; + } + + return result.r0; +}