From b34af1c34767f7c61c8e31cd82731227ba13c2aa Mon Sep 17 00:00:00 2001 From: adamdebek Date: Tue, 23 May 2023 13:18:11 +0200 Subject: [PATCH] add exit() test CI-209 --- libc/exit.c | 421 +++++++++++++++++++++++++++++++++++++++++++++++++--- libc/main.c | 1 + 2 files changed, 402 insertions(+), 20 deletions(-) diff --git a/libc/exit.c b/libc/exit.c index 3826dc6a7..a6ffde231 100644 --- a/libc/exit.c +++ b/libc/exit.c @@ -6,6 +6,8 @@ * - stdlib.h * TESTED: * - exit() + * - _exit() (_Exit equivalent) + * - atexit() * * Copyright 2023 Phoenix Systems * Author: Adam Debek @@ -19,24 +21,63 @@ #include #include #include +#include +#include #include #include "common.h" +#define PATH "/tmp/testFile" +#define CREATEMODE 0666 +#define TEST_STR "test123" -TEST_GROUP(exit); +static int fd; +static int val = 0; -TEST_SETUP(exit) +/* SIGCHLD signal handler */ +static void handler(int signum) { + if (signum != SIGCHLD) { + TEST_MESSAGE("Handler received other signal than SIGCHLD"); + _exit(EXIT_FAILURE); + } + + if (creat(PATH, CREATEMODE) == -1) { + TEST_MESSAGE("Couldn't create file in handler"); + _exit(EXIT_FAILURE); + } } -TEST_TEAR_DOWN(exit) +/* Functions to be registered by atexit */ +static void fun1(void) +{ + val++; + write(fd, &val, sizeof(int)); +} + + +static void fun2(void) +{ + int val = 40; + write(fd, &val, sizeof(int)); +} + + +TEST_GROUP(_exit); + + +TEST_SETUP(_exit) +{ +} + + +TEST_TEAR_DOWN(_exit) { } -TEST(exit, status_vals) +TEST(_exit, status_vals) { /* Return all possible least significant byte values */ pid_t pid; @@ -49,11 +90,12 @@ TEST(exit, status_vals) for (i = 0; i < 256; i++) { pid = fork(); if (pid < 0) { - TEST_MESSAGE("fork failed"); + TEST_MESSAGE("fork failed in testcase: \"status_vals\""); + _exit(EXIT_FAILURE); } /* child */ else if (pid == 0) { - exit(i); + _exit(i); } /* parent */ else { @@ -65,11 +107,12 @@ TEST(exit, status_vals) for (i = 0; i < 3; i++) { pid = fork(); if (pid < 0) { - TEST_MESSAGE("fork failed"); + TEST_MESSAGE("fork failed in testcase: \"status_vals\""); + _exit(EXIT_FAILURE); } /* child */ else if (pid == 0) { - exit(val[i]); + _exit(val[i]); } /* parent */ else { @@ -80,7 +123,31 @@ TEST(exit, status_vals) } -TEST(exit, chk_if_exits) +TEST(_exit, exit_info) +{ + /* Check if exit() saves status for waitpid() */ + pid_t pid; + int status; + + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"exit_info\""); + _exit(EXIT_FAILURE); + } + /* child */ + else if (pid == 0) { + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + sleep(1); /* give child time to exit */ + waitpid(pid, &status, WNOHANG); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + } +} + + +TEST(_exit, chk_if_exits) { /* Check if process terminates after exit call */ pid_t pid; @@ -88,16 +155,17 @@ TEST(exit, chk_if_exits) pid = fork(); if (pid < 0) { - TEST_MESSAGE("fork failed"); + TEST_MESSAGE("fork failed in testcase: \"check_if_exits\""); + _exit(EXIT_FAILURE); } /* child */ else if (pid == 0) { - exit(0); + _exit(EXIT_SUCCESS); } /* parent */ else { wait(&status); - TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status)); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); /* Try to kill process which exited */ errno = 0; int ret = kill(pid, SIGKILL); @@ -107,7 +175,7 @@ TEST(exit, chk_if_exits) } -TEST(exit, close_streams) +TEST(_exit, close_streams) { /* * Open pipe, when child exits one side of pipe should be closed, @@ -120,16 +188,17 @@ TEST(exit, close_streams) TEST_ASSERT_EQUAL_INT(0, pipe(pipefd)); pid = fork(); if (pid < 0) { - TEST_MESSAGE("fork failed"); + TEST_MESSAGE("fork failed in testcase: \"close_streams\""); + _exit(EXIT_FAILURE); } /* child */ else if (pid == 0) { - exit(0); + _exit(EXIT_SUCCESS); } /* parent */ else { wait(&status); - TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status)); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); close(pipefd[0]); /* close read pipe end */ errno = 0; int ret = write(pipefd[1], "aa", 2); @@ -140,9 +209,321 @@ TEST(exit, close_streams) } +TEST(_exit, parent_wait) +{ + /* Check if child exit() cause wait in parent to work properly */ + pid_t pid; + int status; + + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"parent_wait\""); + _exit(EXIT_FAILURE); + } + /* child */ + else if (pid == 0) { + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + int ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + } +} + + +TEST(_exit, orphaned_child) +{ + /* Test if parent exit affect child process */ + pid_t pid, gpid; + int status; + + gpid = fork(); + if (gpid < 0) { + TEST_MESSAGE("fork failed in testcase: \"orphaned_child\" in grandparent"); + _exit(EXIT_FAILURE); + } + else if (gpid == 0) { + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"orphaned_child\" in parent"); + _exit(EXIT_FAILURE); + } + /* grandchild */ + else if (pid == 0) { + /* Sleep for a while to ensure that parent already exited */ + sleep(4); + TEST_ASSERT_NOT_EQUAL_INT(-1, creat(PATH, CREATEMODE)); + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + /* parent exits right away */ + _exit(EXIT_SUCCESS); + } + } + /* grandparent */ + else { + wait(&status); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + + sleep(2); + /* Check if file exists, if so child sleep was interrupted */ + errno = 0; + int ret = open(PATH, O_RDWR); + if (ret == -1) { + TEST_ASSERT_EQUAL_INT(ENOENT, errno); + } + + sleep(3); + /* At this moment if child wasn't affected file should exist */ + errno = 0; + ret = open(PATH, O_RDWR); + TEST_ASSERT_NOT_EQUAL_INT(-1, ret); + TEST_ASSERT_NOT_EQUAL_INT(ENOENT, errno); + + close(ret); + remove(PATH); + } +} + + +TEST(_exit, new_parent_id) +{ + /* Test that child acquire new parent ID */ + pid_t pid, gpid, ppid; + int status; + + gpid = fork(); + if (gpid < 0) { + TEST_MESSAGE("fork failed in testcase: \"new_parent_id\" in grandparent"); + _exit(EXIT_FAILURE); + } + else if (gpid == 0) { + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"new_parent_id\" in parent"); + _exit(EXIT_FAILURE); + } + /* grandchild */ + else if (pid == 0) { + ppid = getppid(); + /* Sleep until parents exits */ + sleep(2); + /* Parent process id should be 1 (init pid) */ + TEST_ASSERT_NOT_EQUAL_INT(ppid, getppid()); + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + /* Sleep until child gets his parent ID */ + sleep(1); + _exit(EXIT_SUCCESS); + } + } + /* grandparent */ + else { + wait(&status); + TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, WEXITSTATUS(status)); + sleep(3); + } +} + + +TEST(_exit, SIGCHLD_sent) +{ + /* Test that SIGCHILD signal is sent after child exits */ + pid_t pid; + int status, ret; + + static struct sigaction sa; + sa.sa_handler = handler; + TEST_ASSERT_EQUAL_INT(0, sigemptyset(&sa.sa_mask)); + sa.sa_flags = 0; + TEST_ASSERT_EQUAL_INT(0, sigaction(SIGCHLD, &sa, NULL)); + + errno = 0; + ret = open(PATH, O_RDWR); + if (ret == -1) { + TEST_ASSERT_EQUAL_INT(ENOENT, errno); + } + + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"SIGCHLD_sent\""); + _exit(EXIT_FAILURE); + } + /* child */ + else if (pid == 0) { + /* Exit right away */ + _exit(EXIT_SUCCESS); + } + /* parent */ + else { + ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + + /* Wait until handler end execution */ + sleep(1); + errno = 0; + ret = open(PATH, O_RDWR); + TEST_ASSERT_NOT_EQUAL_INT(-1, ret); + TEST_ASSERT_NOT_EQUAL_INT(ENOENT, errno); + + TEST_ASSERT_EQUAL_INT(0, close(ret)); + TEST_ASSERT_EQUAL_INT(0, remove(PATH)); + TEST_ASSERT_NOT_EQUAL(SIG_ERR, signal(SIGCHLD, SIG_DFL)); + } +} + + +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(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"stream_flush\""); + _exit(EXIT_FAILURE); + } + /* 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 = open(PATH, O_RDWR | O_CREAT | O_TRUNC); + TEST_ASSERT_NOT_EQUAL_INT(-1, fd); + + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"atexit_few_calls\""); + _exit(EXIT_FAILURE); + } + /* child */ + else if (pid == 0) { + TEST_ASSERT_EQUAL_INT(0, atexit(fun1)); + TEST_ASSERT_EQUAL_INT(0, atexit(fun1)); + TEST_ASSERT_EQUAL_INT(0, atexit(fun2)); + exit(EXIT_SUCCESS); + } + /* parent */ + else { + int ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + int buf; + 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(40, 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(2, buf); + close(fd); + remove(PATH); + } +} + + +TEST(exit, atexit_over32_calls) +{ + /* Test that exit() invokes funcs registered by atexit */ + pid_t pid; + int status; + fd = open(PATH, O_RDWR | O_CREAT | O_TRUNC); + TEST_ASSERT_NOT_EQUAL_INT(-1, fd); + + pid = fork(); + if (pid < 0) { + TEST_MESSAGE("fork failed in testcase: \"atexit_over32_calls\""); + _exit(EXIT_FAILURE); + } + /* child */ + else if (pid == 0) { + int i; + for (i = 0; i < ATEXIT_MAX - 1; i++) { + TEST_ASSERT_EQUAL_INT(0, atexit(fun1)); + } + TEST_ASSERT_EQUAL_INT(0, atexit(fun2)); + TEST_ASSERT_EQUAL_INT(0, atexit(fun1)); + exit(EXIT_SUCCESS); + } + /* parent */ + else { + int ret = wait(&status); + TEST_ASSERT_EQUAL_INT(pid, ret); + int buf, i; + 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(40, buf); + for (i = 0; i < ATEXIT_MAX - 1; i++) { + TEST_ASSERT_EQUAL_INT(sizeof(int), read(fd, &buf, sizeof(int))); + TEST_ASSERT_EQUAL_INT(i + 2, buf); + } + close(fd); + remove(PATH); + } +} + + +TEST_GROUP_RUNNER(_exit) +{ + RUN_TEST_CASE(_exit, status_vals); + RUN_TEST_CASE(_exit, exit_info); + RUN_TEST_CASE(_exit, chk_if_exits); + RUN_TEST_CASE(_exit, close_streams); + RUN_TEST_CASE(_exit, parent_wait); + 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, status_vals); - RUN_TEST_CASE(exit, chk_if_exits); - RUN_TEST_CASE(exit, close_streams); -} \ No newline at end of file + // RUN_TEST_CASE(exit, stream_flush); //error in system + RUN_TEST_CASE(exit, atexit_few_calls); + RUN_TEST_CASE(exit, atexit_over32_calls); +} diff --git a/libc/main.c b/libc/main.c index 6d2eab5dc..a88b5e958 100644 --- a/libc/main.c +++ b/libc/main.c @@ -54,6 +54,7 @@ void runner(void) RUN_TEST_GROUP(stdio_scanf_squareBrackets); RUN_TEST_GROUP(stdio_scanf_rest); RUN_TEST_GROUP(string_chr); + RUN_TEST_GROUP(_exit); RUN_TEST_GROUP(exit); }