Skip to content

Commit

Permalink
test FEATURE simple rpc test
Browse files Browse the repository at this point in the history
... with a test framework
  • Loading branch information
michalvasko committed Apr 1, 2021
1 parent b3dafae commit 466a0b3
Show file tree
Hide file tree
Showing 6 changed files with 485 additions and 6 deletions.
25 changes: 19 additions & 6 deletions CMakeLists.txt
Expand Up @@ -21,8 +21,9 @@ endif()

# set default build type if not specified by user
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE debug)
set(CMAKE_BUILD_TYPE Debug)
endif()
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)

add_compile_options(-Wall -Wextra -std=gnu99)
set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
Expand All @@ -39,12 +40,12 @@ set(NP2SRV_VERSION 1.1.71)
set(LIBYANG_DEP_SOVERSION_MAJOR 1)

# build options
if(CMAKE_BUILD_TYPE STREQUAL debug)
option(BUILD_TESTS "Build tests" ON)
option(VALGRIND_TESTS "Build tests with valgrind" ON)
if((CMAKE_BUILD_TYPE_LOWER STREQUAL debug) OR (CMAKE_BUILD_TYPE_LOWER STREQUAL package))
option(ENABLE_TESTS "Build tests" ON)
option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" ON)
else()
option(BUILD_TESTS "Build tests" OFF)
option(VALGRIND_TESTS "Build tests with valgrind" OFF)
option(ENABLE_TESTS "Build tests" OFF)
option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF)
endif()
option(BUILD_CLI "Build and install neotpeer2-cli" ON)
option(ENABLE_URL "Enable URL capability" ON)
Expand Down Expand Up @@ -218,6 +219,18 @@ list(APPEND CMAKE_REQUIRED_LIBRARIES ${SYSREPO_LIBRARIES})
configure_file("${PROJECT_SOURCE_DIR}/src/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ESCAPE_QUOTES @ONLY)
include_directories(${PROJECT_BINARY_DIR})

# tests
if(ENABLE_TESTS)
find_package(CMocka 1.0.0)
if(CMOCKA_FOUND)
enable_testing()
add_subdirectory(tests)
else()
message(STATUS "Disabling tests because of missing CMocka.")
set(ENABLE_TESTS NO)
endif()
endif()

# set script dir
set(SCRIPT_DIR "${PROJECT_SOURCE_DIR}/scripts/")

Expand Down
55 changes: 55 additions & 0 deletions tests/CMakeLists.txt
@@ -0,0 +1,55 @@
# correct RPATH usage on OS X
set(CMAKE_MACOSX_RPATH TRUE)

# update binary dir
set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}/tests")

# set ROOT_DIR to realpath
get_filename_component(ROOT_DIR "${CMAKE_SOURCE_DIR}" REALPATH)

# generate config
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/np_test_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/np_test_config.h" ESCAPE_QUOTES @ONLY)

# headers
include_directories(SYSTEM ${CMOCKA_INCLUDE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})

# base test source
set(test_sources "np_test.c")

# list of all the tests
set(tests test_rpc)

# build the executables
foreach(test_name IN LISTS tests)
add_executable(${test_name} ${test_sources} ${test_name}.c)
target_link_libraries(${test_name} ${CMOCKA_LIBRARIES} ${LIBNETCONF2_LIBRARIES} ${LIBYANG_LIBRARIES})
set_property(TARGET ${test_name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endforeach(test_name)

# add tests with their attributes
foreach(test_name IN LISTS tests)
add_test(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>)
set_property(TEST ${test_name} APPEND PROPERTY ENVIRONMENT
"MALLOC_CHECK_=3"
)
endforeach(test_name)

# valgrind tests
if(ENABLE_VALGRIND_TESTS)
find_program(VALGRIND_FOUND valgrind)
if(VALGRIND_FOUND)
foreach(test_name IN LISTS tests)
add_test(NAME ${test_name}_valgrind COMMAND valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ${CMAKE_CURRENT_BINARY_DIR}/${test_name})
endforeach(test_name)
else()
message(WARNING "valgrind executable not found! Disabling memory leak tests.")
endif()
endif()

# phony target for clearing all sysrepo test data
add_custom_target(test_clean
COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/repositories
COMMAND rm -rf /dev/shm/_tests_np_*
)
221 changes: 221 additions & 0 deletions tests/np_test.c
@@ -0,0 +1,221 @@
/**
* @file np_test.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief base source for netopeer2 testing
*
* @copyright
* Copyright 2021 Deutsche Telekom AG.
* Copyright 2021 CESNET, z.s.p.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#define _POSIX_C_SOURCE 200809L

#include "np_test.h"

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <nc_client.h>

#include "np_test_config.h"

static int
setup_server_socket_wait(void)
{
/* max sleep 5s */
const uint32_t sleep_count = 200;
const struct timespec ts = {.tv_sec = 0, .tv_nsec = 25000000};
uint32_t count = 0;

while (count < sleep_count) {
if (!access(NP_SOCKET_PATH, F_OK)) {
break;
}

nanosleep(&ts, NULL);
++count;
}

if (count == sleep_count) {
return 1;
}
return 0;
}

static int
setup_setenv_sysrepo(const char *test_name)
{
int ret = 1;
char *sr_repo_path = NULL, *sr_shm_prefix = NULL;

/* set sysrepo environment variables */
sr_repo_path = malloc(strlen(NP_SR_REPOS_DIR) + 1 + strlen(test_name) + 1);
if (!sr_repo_path) {
goto cleanup;
}
sprintf(sr_repo_path, "%s/%s", NP_SR_REPOS_DIR, test_name);
if (setenv("SYSREPO_REPOSITORY_PATH", sr_repo_path, 1)) {
goto cleanup;
}

sr_shm_prefix = malloc(strlen(NP_SR_SHM_PREFIX) + strlen(test_name) + 1);
if (!sr_shm_prefix) {
goto cleanup;
}
sprintf(sr_shm_prefix, "%s%s", NP_SR_SHM_PREFIX, test_name);
if (setenv("SYSREPO_SHM_PREFIX", sr_shm_prefix, 1)) {
goto cleanup;
}

ret = 0;

cleanup:
free(sr_repo_path);
free(sr_shm_prefix);
return ret;
}

int
_np_glob_setup(void **state, const char *test_name)
{
struct np_test *st;
pid_t pid;
int fd;

/* set sysrepo environment variables */
if (setup_setenv_sysrepo(test_name)) {
return 1;
}

/* install modules */
if (setenv("NP2_MODULE_DIR", NP_ROOT_DIR "/modules", 1)) {
return 1;
}
if (setenv("NP2_MODULE_PERMS", "600", 1)) {
return 1;
}
if (system(NP_ROOT_DIR "/scripts/setup.sh")) {
return 1;
}
if (unsetenv("NP2_MODULE_DIR")) {
return 1;
}
if (unsetenv("NP2_MODULE_PERMS")) {
return 1;
}

/* fork and start the server */
if (!(pid = fork())) {
/* open log file */
fd = open(NP_LOG_PATH, O_WRONLY | O_CREAT | O_TRUNC, 00600);
if (fd == -1) {
goto child_error;
}

/* redirect stdout and stderr */
dup2(fd, 1);
dup2(fd, 2);

close(fd);

/* exec server listening on a unix socket */
execl(NP_BINARY_DIR "/netopeer2-server", NP_BINARY_DIR "/netopeer2-server", "-d", "-v3", "-p" NP_PID_PATH,
"-U" NP_SOCKET_PATH, "-m 600", (char *)NULL);

child_error:
printf("Child execution failed\n");
exit(1);
} else if (pid == -1) {
return 1;
}

/* wait for the server, until it creates its socket */
if (setup_server_socket_wait()) {
return 1;
}

/* create test state structure, up to teardown now to free it */
st = calloc(1, sizeof *st);
if (!st) {
return 1;
}
*state = st;
st->server_pid = pid;

/* create NETCONF session */
st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL);
if (!st->nc_sess) {
return 1;
}

return 0;
}

int
np_glob_teardown(void **state)
{
struct np_test *st = *state;
int ret = 0, wstatus;

if (!st) {
return 0;
}

/* stop the NETCONF session */
nc_session_free(st->nc_sess, NULL);

/* terminate the server */
if (kill(st->server_pid, SIGTERM)) {
printf("kill() failed (%s)\n", strerror(errno));
ret = 1;
}

/* wait for it */
if (waitpid(st->server_pid, &wstatus, 0) != st->server_pid) {
printf("waitpid() failed (%s)\n", strerror(errno));
ret = 1;
} else if (!WIFEXITED(wstatus)) {
if (WIFSIGNALED(wstatus)) {
printf("Unexpected server exit (by signal %s)\n", strsignal(WTERMSIG(wstatus)));
} else {
printf("Unexpected server exit (unknown reason)\n");
}
ret = 1;
} else if (WEXITSTATUS(wstatus)) {
printf("Unexpected server exit status (%d)\n", WEXITSTATUS(wstatus));
ret = 1;
}

/* unset sysrepo environment variables */
if (unsetenv("SYSREPO_REPOSITORY_PATH")) {
ret = 1;
}
if (unsetenv("SYSREPO_SHM_PREFIX")) {
ret = 1;
}

free(st);
return ret;
}
52 changes: 52 additions & 0 deletions tests/np_test.h
@@ -0,0 +1,52 @@
/**
* @file np_test.h
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief base header for netopeer2 testing
*
* @copyright
* Copyright 2021 Deutsche Telekom AG.
* Copyright 2021 CESNET, z.s.p.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _NP_TEST_H_
#define _NP_TEST_H_

#include <string.h>
#include <unistd.h>

#include <nc_client.h>

/* global setup function specific for a test */
#define NP_GLOB_SETUP_FUNC \
static int \
np_glob_setup(void **state) \
{ \
char file[64]; \
\
strcpy(file, __FILE__); \
file[strlen(file) - 2] = '\0'; \
return _np_glob_setup(state, strrchr(file, '/') + 1); \
}

/* test state structure */
struct np_test {
pid_t server_pid;
struct nc_session *nc_sess;
};

int _np_glob_setup(void **state, const char *test_name);

int np_glob_teardown(void **state);

#endif /* _NP_TEST_H_ */

0 comments on commit 466a0b3

Please sign in to comment.