From 9c1506a9b8d508e58d658053df103c4e13f2259b Mon Sep 17 00:00:00 2001 From: adamdebek Date: Mon, 22 May 2023 12:24:18 +0200 Subject: [PATCH] slibc: add exit, _exit, atexit tests CI-209 --- exit/Makefile | 1 + exit/test.yaml | 7 + exit/test_exit.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 518 insertions(+) create mode 100644 exit/Makefile create mode 100644 exit/test.yaml create mode 100644 exit/test_exit.c diff --git a/exit/Makefile b/exit/Makefile new file mode 100644 index 000000000..8d93e4571 --- /dev/null +++ b/exit/Makefile @@ -0,0 +1 @@ +$(eval $(call add_unity_test, test_exit)) diff --git a/exit/test.yaml b/exit/test.yaml new file mode 100644 index 000000000..52b832a0a --- /dev/null +++ b/exit/test.yaml @@ -0,0 +1,7 @@ +test: + type: unity + tests: + - name: unit + execute: test_exit + targets: + exclude: [armv7m7-imxrt106x-evk, armv7m7-imxrt117x-evk, armv7m4-stm32l4x6-nucleo] diff --git a/exit/test_exit.c b/exit/test_exit.c new file mode 100644 index 000000000..31f8d6ad0 --- /dev/null +++ b/exit/test_exit.c @@ -0,0 +1,510 @@ +/* + * Phoenix-RTOS + * + * POSIX.1-2017 standard library functions tests + * HEADER: + * - stdlib.h + * - unistd.h + * TESTED: + * - exit() + * - _exit() (_Exit equivalent) + * - atexit() + * + * Copyright 2023 Phoenix Systems + * Author: Adam Debek + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PATH "/tmp/testFile1234" +#define TEST_STR "test123" + + +/* SIGCHLD signal handler */ +static void test_handler(int signum) +{ + if (signum != SIGCHLD) { + TEST_MESSAGE("Handler received other signal than SIGCHLD"); + _exit(EXIT_FAILURE); + } + + if (creat(PATH, DEFFILEMODE) == -1) { + TEST_MESSAGE("Couldn't create file in handler"); + _exit(EXIT_FAILURE); + } +} + + +/* Functions to be registered by atexit */ +static void test_fun1(void) +{ + int fd, val = 1; + + fd = open(PATH, O_WRONLY | O_APPEND); + TEST_ASSERT_NOT_EQUAL_INT(-1, fd); + TEST_ASSERT_EQUAL_INT(sizeof(int), write(fd, &val, sizeof(int))); + close(fd); +} + + +static void test_fun2(void) +{ + int fd, val = 1234; + + fd = open(PATH, O_WRONLY | O_APPEND); + TEST_ASSERT_NOT_EQUAL_INT(-1, fd); + TEST_ASSERT_EQUAL_INT(sizeof(int), write(fd, &val, sizeof(int))); + close(fd); +} + + +TEST_GROUP(_exit); + + +TEST_SETUP(_exit) +{ +} + + +TEST_TEAR_DOWN(_exit) +{ +} + + +TEST(_exit, status_vals) +{ + /* Return all possible least significant byte values and check whether wait() works */ + pid_t pid; + int i; + int val[3]; + val[0] = 0x1 << 8; + val[1] = (0x1 << 16) + 1; + val[2] = (0x1 << 24) + 2; + + for (i = 0; i < 256; i++) { + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + _exit(i); + } + /* parent */ + else { + int status, ret; + + ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + TEST_ASSERT_EQUAL_INT(i, WEXITSTATUS(status)); + } + } + /* Some exceeding values */ + for (i = 0; i < 3; i++) { + pid = fork(); + if (pid < 0) { + TEST_ASSERT_LESS_THAN_INT(0, pid); + } + /* child */ + if (pid == 0) { + _exit(val[i]); + } + /* parent */ + else { + int status, ret; + + ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + TEST_ASSERT_EQUAL_INT(i, WEXITSTATUS(status)); + } + } +} + + +TEST(_exit, exit_status_waitpid) +{ + /* Check if after _exit() waitpid() works */ + pid_t pid; + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + int status, ret; + + ret = waitpid(pid, &status, 0); + TEST_ASSERT_EQUAL_INT(pid, ret); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + } +} + + +TEST(_exit, chk_if_exits) +{ + /* Check if process terminates after _exit call */ + pid_t pid; + int status; + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + int ret; + + wait(&status); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + /* Try to kill process which exited */ + errno = 0; + ret = kill(pid, SIGKILL); + TEST_ASSERT_EQUAL_INT(-1, ret); + TEST_ASSERT_EQUAL_INT(ESRCH, errno); + } +} + + +TEST(_exit, close_streams) +{ + /* + * Open pipe, when child exits one side of pipe should be closed, + * so that call to write should fail. + */ + pid_t pid; + int pipefd[2]; + int status; + + TEST_ASSERT_EQUAL_INT(0, pipe(pipefd)); + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + int ret; + + wait(&status); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + close(pipefd[0]); /* close read pipe end */ + errno = 0; + ret = write(pipefd[1], TEST_STR, sizeof(TEST_STR)); + TEST_ASSERT_EQUAL_INT(-1, ret); + TEST_ASSERT_EQUAL_INT(EPIPE, errno); + close(pipefd[1]); /* close write pipe end */ + } +} + + +TEST(_exit, orphaned_child) +{ + /* Test if parent _exit affect child process */ + pid_t pid; + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + if (pid == 0) { + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* grandchild */ + if (pid == 0) { + /* Sleep for a while to ensure that parent already exited */ + sleep(1); + creat(PATH, DEFFILEMODE); + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + /* parent exits right away */ + _exit(EXIT_SUCCESS); + } + } + /* grandparent */ + else { + int status; + + wait(&status); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + + /* Check if file exists, if so child sleep was interrupted */ + TEST_ASSERT_EQUAL_INT(-1, access(PATH, F_OK)); + + sleep(2); + /* At this moment if child wasn't affected file should exist */ + TEST_ASSERT_EQUAL_INT(0, access(PATH, F_OK)); + + remove(PATH); + } +} + + +TEST(_exit, new_parent_id) +{ + /* Test that child acquire new parent ID */ + pid_t pid; + int pipefd[2]; + + /* pipe needed for communication between grandparent and grandchild (can't use asserts in childs) */ + TEST_ASSERT_EQUAL_INT(0, pipe(pipefd)); + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + if (pid == 0) { + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* grandchild */ + if (pid == 0) { + pid_t old_ppid, new_ppid; + + close(pipefd[0]); + old_ppid = getppid(); + /* Sleep until parents exits */ + sleep(2); + /* Parent process id should be 1 (init pid) */ + new_ppid = getppid(); + write(pipefd[1], &old_ppid, sizeof(pid_t)); + write(pipefd[1], &new_ppid, sizeof(pid_t)); + close(pipefd[1]); + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + close(pipefd[0]); + close(pipefd[1]); + /* Sleep until child gets his parent ID */ + sleep(1); + _exit(EXIT_SUCCESS); + } + } + /* grandparent */ + else { + pid_t old_ppid, new_ppid; + int status; + + close(pipefd[1]); + wait(&status); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + sleep(3); + read(pipefd[0], &old_ppid, sizeof(pid_t)); + read(pipefd[0], &new_ppid, sizeof(pid_t)); + TEST_ASSERT_NOT_EQUAL_INT(old_ppid, new_ppid); + close(pipefd[0]); + } +} + + +TEST(_exit, SIGCHLD_sent) +{ + /* Test that SIGCHILD signal is sent after child exits */ + pid_t pid; + int status, ret; + + struct sigaction sa; + sa.sa_handler = test_handler; + TEST_ASSERT_EQUAL_INT(0, sigemptyset(&sa.sa_mask)); + sa.sa_flags = 0; + TEST_ASSERT_EQUAL_INT(0, sigaction(SIGCHLD, &sa, NULL)); + + /* File doesn't exist at this moment (SIGCHLD handler creates it) */ + TEST_ASSERT_EQUAL_INT(-1, access(PATH, F_OK)); + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + /* Exit right away */ + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + + TEST_ASSERT_EQUAL_INT(0, access(PATH, F_OK)); + TEST_ASSERT_EQUAL_INT(0, remove(PATH)); + + sa.sa_handler = SIG_DFL; + TEST_ASSERT_EQUAL(0, sigaction(SIGCHLD, &sa, NULL)); + } +} + + +TEST_GROUP(exit); + + +TEST_SETUP(exit) +{ +} + + +TEST_TEAR_DOWN(exit) +{ +} + + +TEST(exit, stream_flush) +{ + /* Test that exit() force unwritten buffered data to be flushed */ + pid_t pid; + int status; + + FILE *f = fopen(PATH, "w+"); + TEST_ASSERT_NOT_NULL(f); + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + fprintf(f, TEST_STR); + sleep(1); /* Give time */ + exit(EXIT_SUCCESS); + } + /* parent */ + else { + /* Get empty file length */ + int fd = open(PATH, O_RDWR); + TEST_ASSERT_NOT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL_INT(0, lseek(fd, 0, SEEK_END)); + + wait(&status); + + /* If buffered data was flushed file length should increase */ + TEST_ASSERT_EQUAL_INT(strlen(TEST_STR), lseek(fd, 0, SEEK_END)); + + fclose(f); + remove(PATH); + } +} + + +TEST(exit, atexit_few_calls) +{ + /* Test that exit() invokes funcs registered by atexit */ + pid_t pid; + int status, fd; + + fd = open(PATH, O_RDONLY | O_CREAT | O_TRUNC, S_IFREG); + TEST_ASSERT_NOT_EQUAL_INT(-1, fd); + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + atexit(test_fun1); + atexit(test_fun1); + atexit(test_fun2); + exit(EXIT_SUCCESS); + } + /* parent */ + else { + int buf, ret; + + ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + TEST_ASSERT_EQUAL_INT(0, lseek(fd, 0, SEEK_SET)); + + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(1234, buf); + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(1, buf); + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(1, buf); + + close(fd); + remove(PATH); + } +} + + +TEST(exit, atexit_over32_calls) +{ + /* Test that exit() invokes over 32 funcs registered by atexit, 32 is number of function slots in 1 node */ +#ifndef __phoenix__ + TEST_IGNORE(); +#else + pid_t pid; + int i, fd; + + fd = open(PATH, O_RDONLY | O_CREAT | O_TRUNC, S_IFREG); + TEST_ASSERT_NOT_EQUAL_INT(-1, fd); + + pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + /* child */ + if (pid == 0) { + for (i = 0; i < ATEXIT_MAX - 1; i++) { + atexit(test_fun1); + } + atexit(test_fun2); + atexit(test_fun1); + exit(EXIT_SUCCESS); + } + /* parent */ + else { + int buf, ret, status; + + ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + TEST_ASSERT_EQUAL_INT(0, lseek(fd, 0, SEEK_SET)); + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(1, buf); + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(1234, buf); + + for (i = 0; i < ATEXIT_MAX - 1; i++) { + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(1, buf); + } + + close(fd); + remove(PATH); + } +#endif +} + + +TEST_GROUP_RUNNER(_exit) +{ + RUN_TEST_CASE(_exit, status_vals); + RUN_TEST_CASE(_exit, exit_status_waitpid); + RUN_TEST_CASE(_exit, chk_if_exits); + RUN_TEST_CASE(_exit, close_streams); + RUN_TEST_CASE(_exit, orphaned_child); + RUN_TEST_CASE(_exit, new_parent_id); + RUN_TEST_CASE(_exit, SIGCHLD_sent); +} + +TEST_GROUP_RUNNER(exit) +{ + RUN_TEST_CASE(exit, stream_flush); + RUN_TEST_CASE(exit, atexit_few_calls); + RUN_TEST_CASE(exit, atexit_over32_calls); +} + +void runner(void) +{ + RUN_TEST_GROUP(_exit); + RUN_TEST_GROUP(exit); +} + + +int main(int argc, char *argv[]) +{ + UnityMain(argc, (const char **)argv, runner); +}