Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ RUN apt-get install -y \
build-essential git checkinstall libssl-dev openssl software-properties-common \
golang-go \
python3.11 python3-pip python3.11-venv libsass-dev libcairo2 libpango-1.0-0 libpangoft2-1.0-0 pangocairo-1.0 pngquant \
sudo
sudo \
libpq-dev

## vscode user
RUN useradd -ms /bin/bash vscode \
Expand Down
1 change: 1 addition & 0 deletions demo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
add_subdirectory(generic-container)
add_subdirectory(wiremock)
add_subdirectory(google-test)
add_subdirectory(postgresql)
23 changes: 23 additions & 0 deletions demo/postgresql/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
cmake_minimum_required (VERSION 3.26)
project (postgresql-demo
VERSION 0.1.0
DESCRIPTION "Demonstrates usage of the PostgreSQL module for Testcontainers C in a simple main app"
LANGUAGES C
)

set(TARGET_OUT demo_postgresql_module.out)

include(FindPostgreSQL)

# v17: use a lower version of the client library as not many distros ship libpq v18.x.
# Client-server communication is fully backwards compatible unless we want to use new client features.
find_package(PostgreSQL 17)

if (${PostgreSQL_FOUND})
add_executable(${TARGET_OUT} postgresql_module_demo.c)
target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c)
target_link_libraries(${TARGET_OUT} PRIVATE testcontainers-c-postgresql)
target_link_libraries(${TARGET_OUT} PRIVATE ${PostgreSQL_LIBRARIES})
else()
message(WARNING "Could not find PostgreSQL client libraries. Skipping build of PostgreSQL demo. To fix this, try installing libpq and/or libpq-devel.")
endif()
18 changes: 18 additions & 0 deletions demo/postgresql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Demo - WireMocPostgreSQLk on Testcontainers C

Demonstrates usage of the [PostgreSQL Module for Testcontainers C](../../modules/postgresql/README.md) in a simple main function.
No test framework is used here.

## Running demo

First ensure `libpq` and/or `libpq-devel` is installed on your system - this is required to build the example.

From the root of the repository:

```bash
cmake -B build/
cmake --build build/
./build/demo/postgresql/demo_postgresql_module.out
```

The demo program will start a PostgreSQL container, use `libpq` to connect to it, and then terminate the container.
53 changes: 53 additions & 0 deletions demo/postgresql/postgresql_module_demo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <testcontainers-c-postgresql.h>
#include <testcontainers-c/container.h>

#include <libpq-fe.h>

static const char* postgresqlImage = "postgres:18.1";

int main() {
printf("Creating new PostgreSQL container. Using image: %s\n", postgresqlImage);
int requestId = tc_psql_new_container(postgresqlImage);

char* error;
int containerId = tc_container_run(requestId, error);

if (containerId == -1) {
fprintf(stderr, "Failed to run the container: %s\n", error);
return EXIT_FAILURE;
}

char connectionString[512];
int bytesWritten = tc_psql_get_connection_string(containerId, connectionString, sizeof(connectionString));
if (bytesWritten == -1) {
fprintf(stderr, "Failed to get connection string\n");
return EXIT_FAILURE;
}

printf("Connecting to PostgreSQL using connection string: %s\n", connectionString);

PGconn* conn = PQconnectdb(connectionString);
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Failed to connect to PostgreSQL: %s", PQerrorMessage(conn));
PQfinish(conn);
return EXIT_FAILURE;
}

printf("Successfully connected to PostgreSQL!\n");
PQfinish(conn);

error = tc_container_terminate(containerId);
if (error != NULL) {
fprintf(stderr, "Failed to terminate PostgreSQL container: %s", error);
return EXIT_FAILURE;
}

printf("Terminated PostgreSQL container\n");

return EXIT_SUCCESS;
}

1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
go
ninja
pre-commit
libpq
];
};
};
Expand Down
1 change: 1 addition & 0 deletions modules/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(wiremock)
add_subdirectory(postgresql)
3 changes: 2 additions & 1 deletion modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ or even attach full-fledged API clients for fine-grain testing.
The following modules are available for this project:

- Embedded Generic container for DYI containers - [DEMO](../demo/generic-container/README.md)
- [WireMock for API Mocking](./wiremock/README.md)
- [WireMock for API Mocking](./wiremock/README.md)#
- [PostgreSQL database](./postgresql/README.md)

## Using Modules

Expand Down
22 changes: 22 additions & 0 deletions modules/postgresql/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
set(TARGET testcontainers-c-postgresql)
set(TARGET_NAME ${TARGET})
set(TARGET_DESCRIPTION "PostgreSQL testcontainer abstractions for C")
set(TARGET_VERSION ${PROJECT_VERSION})

add_library(${TARGET} SHARED
impl.c
)

target_sources(${TARGET}
PUBLIC FILE_SET HEADERS
BASE_DIRS .
FILES testcontainers-c-postgresql.h
)

target_link_libraries(${TARGET} PRIVATE testcontainers-c)

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}/${TARGET}.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
20 changes: 20 additions & 0 deletions modules/postgresql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# PostgreSQL module for Testcontainers C

This module allows starting up a PostgreSQL database container with a simpler API surface compared to
setting one up manually.

PostgreSQL is a popular open-source SQL-based RDBMS which can be accessed from native apps by using
the `libpq` library for C or the `libpqxx` library for C++.

See [this page](https://testcontainers.com/modules/postgresql/)
for the list of modules for other languages.

## Examples

- [Usage Demo](../../demo/postgresql/README.md)

## Read more

- [libpq: C library for PostgreSQL](https://www.postgresql.org/docs/current/libpq.html)
- [libpq Examples](https://www.postgresql.org/docs/current/libpq-example.html)
- [libpqxx: C++ library for PostgreSQL](https://pqxx.org/development/libpqxx/)
12 changes: 12 additions & 0 deletions modules/postgresql/cmake.pc.in
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know what this does, but I copied it from the WireMock folder ... it seems agnostic of what folder it lives in. Something to do with packaging?

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@

Name: @TARGET_NAME@
Description: @TARGET_DESCRIPTION@
Version: @TARGET_VERSION@

Requires:
Libs: -L${libdir}
Cflags: -I${includedir}
93 changes: 93 additions & 0 deletions modules/postgresql/impl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "testcontainers-c-postgresql.h"
#include "testcontainers-c/container.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>

#define DEFAULT_POSTGRESQL_IMAGE_NAME "postgres"
#define DEFAULT_POSTGRESQL_VERSION "18.1"
#define DEFAULT_POSTGRESQL_PORT 5432
#define DEFAULT_POSTGRESQL_IMAGE DEFAULT_POSTGRESQL_IMAGE_NAME ":" DEFAULT_POSTGRESQL_VERSION

#define DEFAULT_POSTGRESQL_USERNAME "postgres"
#define DEFAULT_POSTGRESQL_PASSWORD "postgres"
#define DEFAULT_POSTGRESQL_DB "postgres"

static bool pg_isready_is_success(int exitCode) {
return exitCode == 0;
}

static const char* pgIsReadyCmd[] = { "pg_isready", "--host", "localhost", "--username", DEFAULT_POSTGRESQL_USERNAME, "--dbname", DEFAULT_POSTGRESQL_DB };
static const size_t pgIsReadyCmdLen = sizeof(pgIsReadyCmd) / sizeof(char*);

int tc_psql_new_default_container() {
return tc_psql_new_container(DEFAULT_POSTGRESQL_IMAGE);
}

int tc_psql_new_container(const char *image) {
int requestId = tc_container_create(image);

tc_container_with_env(requestId, "POSTGRES_USER", DEFAULT_POSTGRESQL_USERNAME);
tc_container_with_env(requestId, "POSTGRES_PASSWORD", DEFAULT_POSTGRESQL_PASSWORD);
tc_container_with_env(requestId, "POSTGRES_DB", DEFAULT_POSTGRESQL_DB);
tc_container_with_exposed_tcp_port(requestId, DEFAULT_POSTGRESQL_PORT);

int strategyId = tc_container_with_wait_for_exec(requestId, pgIsReadyCmd, pgIsReadyCmdLen);
tc_exec_with_exit_code_matcher(strategyId, pg_isready_is_success);

// TODO: in other Testcontainers implementations, they pass some command parameters to the container
// to disable certain hardening features and improve performance as the container is ephemeral. We
// should consider doing the same if we add the ability to modify container commands to the bridge.
// See: https://www.postgresql.org/docs/current/non-durability.html

return requestId;
}

int tc_psql_get_connection_string(int containerId, char* buffer, size_t bufferLen) {
if (buffer == NULL || bufferLen <= 0) {
// Invalid arguments
return -1;
}

char* error;

const char* hostname = tc_container_get_hostname(containerId, &error);
if (hostname == NULL) {
fprintf(stderr, "Failed to get PostgreSQL hostname: %s", error);
free(error);
return -1;
}

int port = tc_container_get_mapped_port(containerId, DEFAULT_POSTGRESQL_PORT, &error);
if (port == -1) {
fprintf(stderr, "Failed to get PostgreSQL mapped port: %s", error);
free(error);
return -1;
}


int written = snprintf(
buffer,
bufferLen,
"host=%s port=%d user=%s password=%s dbname=%s",
hostname,
port,
DEFAULT_POSTGRESQL_USERNAME,
DEFAULT_POSTGRESQL_PASSWORD,
DEFAULT_POSTGRESQL_DB
);

if (written < 0) {
return -1;
}

if (written >= bufferLen) {
// Provided buffer was not long enough; truncation has occurred
// If written == bufferLen, that means we missed 1 character because of the null terminator
return -1;
}

return written;
}
24 changes: 24 additions & 0 deletions modules/postgresql/testcontainers-c-postgresql.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef TESTCONTAINERS_POSTGRESQL_H
#define TESTCONTAINERS_POSTGRESQL_H

#include <stddef.h>

/// @brief Creates a container request with a default image, exposed port and init logic
/// @return Container request ID
int tc_psql_new_default_container();

/// @brief Creates a container request with a specified image, exposed port and init logic
/// @param image Full image name
/// @return Container request ID
int tc_psql_new_container(const char* image);

/// @brief Gets the connection string that can be used with libpq/libpqxx to connect to the database.
/// @param containerId Container ID
/// @param buffer A buffer to write the connection string into. The written value will be null-terminated.
/// @param bufferLen The length of the buffer.
/// @return The number of bytes written, excluding the null terminator, or -1 if there was an error.
int tc_psql_get_connection_string(int containerId, char* buffer, size_t bufferLen);

// TODO: further container customisation to allow configuring database name, username, password, etc.

#endif
Loading