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
12 changes: 10 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ if(PROJECT_IS_TOP_LEVEL)
if(BUILD_TESTING)
find_package(PkgConfig REQUIRED)
pkg_search_module(unity REQUIRED IMPORTED_TARGET unity)

file(GLOB_RECURSE UNITY_CFG_SRCS CONFIGURE_DEPENDS "unity_config/*.c")
add_library(unity-config STATIC ${UNITY_CFG_SRCS})
target_include_directories(unity-config PUBLIC unity_config)
target_compile_definitions(PkgConfig::unity
INTERFACE "UNITY_INCLUDE_CONFIG_H=1")
target_link_libraries(unity-config PRIVATE PkgConfig::unity)
endif()

# Put outputs in build/bin and build/lib
Expand Down Expand Up @@ -245,8 +252,9 @@ if(PROJECT_IS_TOP_LEVEL)
APPEND_STRING
PROPERTY COMPILE_FLAGS "-frandom-seed=${test_dir}/main.c")
add_executable(test_${test_name} ${test_dir}/main.c)
target_link_libraries(test_${test_name} PRIVATE gg-sdk gg-ipc-mock
PkgConfig::unity)
target_link_libraries(
test_${test_name} PRIVATE gg-sdk gg-ipc-mock unity-config
PkgConfig::unity)
target_compile_options(test_${test_name} PRIVATE)
target_link_options(test_${test_name} PUBLIC)
install(TARGETS test_${test_name})
Expand Down
5 changes: 3 additions & 2 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ if(aws-greengrass-component-sdk_IS_TOP_LEVEL)
APPEND_STRING
PROPERTY COMPILE_FLAGS "-frandom-seed=${test_dir}/main.cpp")
add_executable(test_cpp_${test_name} ${test_dir}/main.cpp)
target_link_libraries(test_cpp_${test_name} PRIVATE gg-sdk++ gg-ipc-mock
PkgConfig::unity)
target_link_libraries(
test_cpp_${test_name} PRIVATE gg-sdk++ gg-sdk gg-ipc-mock unity-config
PkgConfig::unity)
install(TARGETS test_cpp_${test_name})
endforeach()
endif()
Expand Down
144 changes: 144 additions & 0 deletions cpp/test/client_connect/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <gg/error.hpp>
#include <gg/ipc/client.hpp>
#include <source_location>
extern "C" {
#include <gg/ipc/mock.h>
#include <gg/ipc/packet_sequences.h>
#include <gg/process_wait.h>
#include <unistd.h>
#include <unity.h>
}

namespace {
int server_handle = -1;

constexpr const char *auth_token = "0123456789ABCDEF";
constexpr const char *socket_path = "cpp_connect_socket.ipc";
}

#define GG_TEST_ASSERT_OK(expr) TEST_ASSERT_EQUAL(GG_ERR_OK, (expr))
#define GG_TEST_ASSERT_BAD(expr) TEST_ASSERT_NOT_EQUAL(GG_ERR_OK, (expr))

void suiteSetUp(void) {
std::ios_base::sync_with_stdio(false);
GgError ret
= gg_test_setup_ipc(socket_path, 0777, &server_handle, auth_token);
if ((ret != GG_ERR_OK) || (server_handle < 0)) {
_Exit(1);
}
}

void setUp(void) {
}

void tearDown(void) {
}

int suiteTearDown(int num_failures) {
gg_test_close(server_handle);
server_handle = -1;
return num_failures;
}

namespace tests {

namespace {
void connect_okay(void) {
GgipcPacketSequence seq
= gg_test_conn_ack_sequence(gg::Buffer { auth_token });

pid_t pid = fork();
if (pid < 0) {
TEST_IGNORE_MESSAGE("fork() failed.");
}
if (pid == 0) {
auto &client = gg::ipc::Client::get();
GG_TEST_ASSERT_OK(client.connect().value());
TEST_PASS();
}

GG_TEST_ASSERT_OK(
gg_test_expect_packet_sequence(seq, 30, server_handle)
);

bool clean_exit = false;
GG_TEST_ASSERT_OK(gg_process_wait(pid, &clean_exit));
TEST_ASSERT_TRUE(clean_exit);
}

void connect_with_token_okay(void) {
GgipcPacketSequence seq
= gg_test_conn_ack_sequence(gg::Buffer { auth_token });

pid_t pid = fork();
if (pid < 0) {
TEST_IGNORE_MESSAGE("fork() failed.");
}

if (pid == 0) {
// make it impossible for client to get these env variables
// done before SDK can make any threads
// NOLINTBEGIN(concurrency-mt-unsafe)
unsetenv("SVCUID");
unsetenv("AWS_GG_NUCLEUS_DOMAIN_SOCKET_FILEPATH_FOR_COMPONENT");
// NOLINTEND(concurrency-mt-unsafe)
auto &client = gg::ipc::Client::get();
GG_TEST_ASSERT_OK(
client.connect(socket_path, gg::ipc::AuthToken { auth_token })
.value()
);

TEST_PASS();
}

GG_TEST_ASSERT_OK(
gg_test_expect_packet_sequence(seq, 5, server_handle)
);

bool clean_exit = false;
GG_TEST_ASSERT_OK(gg_process_wait(pid, &clean_exit));
TEST_ASSERT_TRUE(clean_exit);
}

void connect_bad(void) {
GgipcPacketSequence seq
= gg_test_connect_hangup_sequence(gg::Buffer { auth_token });

pid_t pid = fork();
if (pid < 0) {
TEST_IGNORE_MESSAGE("fork() failed.");
}

if (pid == 0) {
auto &client = gg::ipc::Client::get();
GG_TEST_ASSERT_BAD(client.connect().value());
TEST_PASS();
}

GG_TEST_ASSERT_OK(
gg_test_expect_packet_sequence(seq, 30, server_handle)
);

/// TODO: verify Classic behavior
GG_TEST_ASSERT_OK(gg_test_disconnect(server_handle));

bool clean_exit = false;
GG_TEST_ASSERT_OK(gg_process_wait(pid, &clean_exit));
TEST_ASSERT_TRUE(clean_exit);
}
}
}

int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
suiteSetUp();

UNITY_BEGIN();
RUN_TEST(tests::connect_okay);
RUN_TEST(tests::connect_with_token_okay);
RUN_TEST(tests::connect_bad);
int num_failures = UNITY_END();

return suiteTearDown(num_failures);
}
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
./mock
./samples
./test
./unity_config
./cpp/CMakeLists.txt
./cpp/include
./cpp/priv_include
Expand Down
59 changes: 59 additions & 0 deletions mock/connect_packet_sequences.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "gg/ipc/packet_sequences.h"
#include <gg/ipc/mock.h>
#include <gg/map.h>
#include <gg/object.h>

GgipcPacket gg_test_connect_packet(GgBuffer auth_token) {
static EventStreamHeader headers[] = {
{ GG_STR(":message-type"),
{ EVENTSTREAM_INT32, .int32 = EVENTSTREAM_CONNECT } },
{ GG_STR(":message-flags"), { EVENTSTREAM_INT32, .int32 = 0 } },
{ GG_STR(":stream-id"), { EVENTSTREAM_INT32, .int32 = 0 } },
{ GG_STR(":version"),
{ EVENTSTREAM_STRING, .string = GG_STR("0.1.0") } },
};

static GgKV payload[1];
payload[0] = gg_kv(GG_STR("authToken"), gg_obj_buf(auth_token));
size_t payload_len = sizeof(payload) / sizeof(payload[0]);

uint32_t header_count = sizeof(headers) / sizeof(headers[0]);
return (GgipcPacket) { .direction = CLIENT_TO_SERVER,
.has_payload = true,
.payload = gg_obj_map((GgMap) {
.pairs = payload, .len = payload_len }),
.headers = headers,
.header_count = header_count };
}

GgipcPacket gg_test_connect_ack_packet(void) {
{
static EventStreamHeader headers[] = {
{ GG_STR(":message-type"),
{ EVENTSTREAM_INT32, .int32 = EVENTSTREAM_CONNECT_ACK } },
{ GG_STR(":message-flags"),
{ EVENTSTREAM_INT32, .int32 = EVENTSTREAM_CONNECTION_ACCEPTED } },
{ GG_STR(":stream-id"), { EVENTSTREAM_INT32, .int32 = 0 } },
};
uint32_t header_count = sizeof(headers) / sizeof(headers[0]);
return (GgipcPacket) { .direction = SERVER_TO_CLIENT,
.has_payload = false,
.headers = headers,
.header_count = header_count };
}
}

GgipcPacketSequence gg_test_conn_ack_sequence(GgBuffer auth_token) {
static GgipcPacket packets[2];
packets[0] = gg_test_connect_packet(auth_token);
packets[1] = gg_test_connect_ack_packet();
size_t len = sizeof(packets) / sizeof(packets[0]);
return (GgipcPacketSequence) { .packets = packets, .len = len };
}

GgipcPacketSequence gg_test_connect_hangup_sequence(GgBuffer auth_token) {
static GgipcPacket packets[1];
packets[0] = gg_test_connect_packet(auth_token);
size_t len = sizeof(packets) / sizeof(packets[0]);
return (GgipcPacketSequence) { .packets = packets, .len = len };
}
10 changes: 8 additions & 2 deletions mock/gg/ipc/mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
#define GG_IPC_MOCK_H

#include <gg/attr.h>
#include <sys/types.h>
#include <stddef.h>

#ifdef __cplusplus
#include <gg/ipc/mock_types.hpp>
#include <gg/types.hpp>
#else
#include <gg/eventstream/rpc.h>
#include <gg/eventstream/types.h>
#include <gg/object.h>
#include <sys/types.h>
#include <stddef.h>
#endif

typedef enum {
CLIENT_TO_SERVER = 0,
Expand Down
12 changes: 12 additions & 0 deletions mock/gg/ipc/packet_sequences.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef GG_IPC_PACKET_SEQUENCES_H
#define GG_IPC_PACKET_SEQUENCES_H

#include <gg/ipc/mock.h>

GgipcPacket gg_test_connect_packet(GgBuffer auth_token);
GgipcPacket gg_test_connect_ack_packet(void);

GgipcPacketSequence gg_test_conn_ack_sequence(GgBuffer auth_token);
GgipcPacketSequence gg_test_connect_hangup_sequence(GgBuffer auth_token);

#endif
15 changes: 15 additions & 0 deletions mock/gg/process_wait.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef GG_TEST_PROCESS_WAIT_H
#define GG_TEST_PROCESS_WAIT_H

#ifdef __cplusplus
#include <gg/error.hpp>
#else
#include <gg/error.h>
#endif

#include <sys/types.h>
#include <stdbool.h>

GgError gg_process_wait(pid_t pid, bool *exit_status);

#endif
15 changes: 15 additions & 0 deletions mock/ggl/process_wait.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef GG_TEST_PROCESS_WAIT_H
#define GG_TEST_PROCESS_WAIT_H

#ifdef __cplusplus
#include <gg/error.hpp>
#else
#include <gg/error.h>
#endif

#include <sys/types.h>
#include <stdbool.h>

GgError gg_process_wait(pid_t pid, bool *exit_status);

#endif
10 changes: 7 additions & 3 deletions mock/mock.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "gg/ipc/mock.h"
#include "gg/object_compare.h"
#include "inttypes.h"
#include "sys/epoll.h"
#include <assert.h>
#include <errno.h>
#include <gg/arena.h>
Expand All @@ -24,6 +22,8 @@
#include <gg/object_visit.h>
#include <gg/socket.h>
#include <gg/socket_epoll.h>
#include <inttypes.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
Expand Down Expand Up @@ -360,7 +360,7 @@ GgError gg_test_expect_packet_sequence(
assert(sock_fd >= 0);
assert(epoll_fd >= 0);

if (client_timeout < 5) {
if (client_timeout <= 0) {
client_timeout = 5;
}

Expand All @@ -377,6 +377,10 @@ GgError gg_test_expect_packet_sequence(
GG_LOGE("Failed to wait for test client connect (%d).", errno);
return GG_ERR_TIMEOUT;
}
if (epoll_ret == 0) {
GG_LOGE("Client exited before connecting.");
return GG_ERR_TIMEOUT;
}
struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = { 0 } };
socklen_t len = sizeof(addr);
client_fd = accept(sock_fd, &addr, &len);
Expand Down
36 changes: 36 additions & 0 deletions mock/process_wait.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "gg/process_wait.h"
#include <errno.h>
#include <gg/error.h>
#include <gg/log.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdbool.h>

GgError gg_process_wait(pid_t pid, bool *exit_status) {
while (true) {
siginfo_t info = { 0 };
int ret = waitid(P_PID, (id_t) pid, &info, WEXITED);
if (ret < 0) {
if (errno == EINTR) {
continue;
}
GG_LOGE("Err %d when calling waitid.", errno);
return GG_ERR_FAILURE;
}

switch (info.si_code) {
case CLD_EXITED:
if (exit_status != NULL) {
*exit_status = info.si_status == 0;
}
return GG_ERR_OK;
case CLD_KILLED:
case CLD_DUMPED:
if (exit_status != NULL) {
*exit_status = false;
}
return GG_ERR_OK;
default:;
}
}
}
Loading