unit_helper_v2.c

/*
 * Biblioteka pomocnicza dla testów jednostkowych systemu DANTE
 * Autorzy: Tomasz Jaworski, Piotr Duch, 2017-2019
 *
 * Wersja   Opis
 *
 * 1.01     Dodanie ograniczników pamięci
 * 1.00     Init
 */

#include <stdio.h>
#include <assert.h>
#include <stdarg.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <memory.h>
#include <stdlib.h>
#include <ctype.h>

#include <sys/resource.h>
#include <sys/errno.h>
#include <signal.h>



#include "unit_helper_v2.h"


struct test_statistics_t {
    int group_id;
    int current_index;
    enum TEST_RESULT last_result;
    int error_detected;
    int terminate;
    const char* unit_test_source;

    struct {
        int passed;
        int failed;
        int warnings;
        int leaks;
    } session, single;
};

static struct test_statistics_t test_statistics;

void unit_test_init(int gid, const char* unit_test_source)
{
    test_statistics.group_id = gid;
    test_statistics.unit_test_source = unit_test_source;
    test_statistics.current_index = 0;
    test_statistics.terminate = 0;

    test_statistics.last_result = TEST_NONE;

    memset(&test_statistics.session, 0, sizeof(test_statistics.session));
    memset(&test_statistics.single, 0, sizeof(test_statistics.single));
}

void test_title(const char* str)
{
    printf("<hr/>");
    printf("<h3>### %s ### </h3>", str);
}


void test_start(int test_index, const char* title, int line)
{
    test_statistics.current_index = test_index;
    test_statistics.last_result = TEST_NONE;

    test_statistics.single.passed = 0;
    test_statistics.single.failed = 0;
    test_statistics.single.warnings = 0;

    if (test_statistics.current_index > 1)
        printf("\n");

    const char* header = "<b><a href=\"source\\%s.html#line-%d\" class=\"utlink\">TEST %d</a></b>: %s\n";
    printf(header, test_statistics.unit_test_source, line, test_statistics.current_index, title);

}

void test_summary(int expected_positives)
{
    printf("   Testy dostępne: <b>%4d</b> (<b>AVAIL</b>)\n", expected_positives);
    printf("         Wykonane: <b>%4d</b> (<b>DONE</b>)\n", test_statistics.session.passed + test_statistics.session.failed);
    printf("   Testy poprawne: <span class=\"upassed\">%4d</span> (<span class=\"upassed\">PASSED</span>)\n", test_statistics.session.passed);
    printf("Testy niepoprawne: <span class=\"ufailed\">%4d</span> (<span class=\"ufailed\">FAILED</span>)\n", test_statistics.session.failed);
    printf("      Ostrzeżenia: <span class=\"uwarning\">%4d</span> (<span class=\"uwarning\">WARNINGS</span>)\n", test_statistics.session.warnings);
    printf("  Wycieki zasobów: <span class=\"uleak\">%4d</span> (<span class=\"uleak\">LEAKS</span>)\n", test_statistics.session.leaks);

    printf("<div style=\"display: none;\">status avail=%d done=%d failed=%d passed=%d warns=%d gid=%d leaks=%d</div>",
        expected_positives,
        test_statistics.session.passed + test_statistics.session.failed, test_statistics.session.failed ,
        test_statistics.session.passed, test_statistics.session.warnings, test_statistics.group_id,
        test_statistics.session.leaks);
}



void test_result_internal(enum TEST_RESULT result, int line, const char* message, ...)
{
    assert(result == TEST_FAILED || result == TEST_PASSED || result == TEST_WARNING || result == TEST_NONE);
    test_statistics.last_result = result;

    if (result == TEST_NONE) {
        return;
    } else if (result == TEST_FAILED) {

        printf("Wynik: <span class=\"ufailed\">PORAŻKA</span>: ");

        va_list ap;
        va_start(ap, message);
        vprintf(message, ap);
        va_end(ap);

        printf("\n");
        printf("       Sprawdź funkcję testującą <b>TEST%d(void)</b> z pliku <a href=\"source\\%s.html#line-%d\">%s</a>, w linii %d\n",
                test_statistics.current_index, test_statistics.unit_test_source, line, test_statistics.unit_test_source, line);

        test_statistics.single.failed++;
        test_statistics.session.failed++;
        return;

    } else if (result == TEST_PASSED) {

        printf("Wynik: <span class=\"upassed\">SUKCES</span>\n");
        test_statistics.single.passed++;
        test_statistics.session.passed++;
        return;

    } else if (result == TEST_WARNING) {
        printf("Wynik: <span class=\"uwarning\">OSTRZEŻENIE</span>: ");

        va_list ap;
        va_start(ap, message);
        vprintf(message, ap);
        va_end(ap);

        printf("\n");
        printf("       Sprawdź funkcję testującą <b>TEST%d(void)</b> z pliku <a href=\"source\\%s.html#line-%d\">%s</a>, w linii %d\n",
                test_statistics.current_index, test_statistics.unit_test_source, line, test_statistics.unit_test_source, line);
        test_statistics.single.warnings++;
        test_statistics.session.warnings++;
        return;
    }

    assert(0 && "???");
}

void test_terminate_session(void)
{
    printf("<span class=\"ufailed\">*** Test przerwany ***</span>\n");
    test_statistics.terminate = 1;
}

void test_show_call(const char* call, int line)
{
    printf("Wywołanie: <b>%s</b> z pliku <a href=\"source\\%s.html#line-%d\">%s</a>, w linii %d\n",
        call, test_statistics.unit_test_source, line, test_statistics.unit_test_source, line);
}


int test_get_session_termination_flag(void)
{
    return test_statistics.terminate;
}

void test_set_session_leaks(int leaks)
{
    test_statistics.session.leaks = leaks;
}

int test_single_has_failed(void)
{
    return test_statistics.single.failed > 0;
}

int test_session_get_fail_count(void)
{
    return test_statistics.session.failed;
}

//
//
//
//


int mem_find_first_difference(const void* ptr1, const void* ptr2, ssize_t size)
{
    if (memcmp(ptr1, ptr2, size) == 0)
        return -1; // nie ma różnicy

    const uint8_t* bptr1 = (const uint8_t*)ptr1;
    const uint8_t* bptr2 = (const uint8_t*)ptr2;

    for (int i = 0; i < size; i++)
        if (*bptr1++ != *bptr2++)
            return i;

    assert(0 && "???");
}

uint8_t mem_get_byte(const void* ptr, int pos)
{
    const uint8_t* bptr = (const uint8_t*)ptr;
    return bptr[pos];
}

void mem_dump_diff(const void* p1, const void* p2, size_t size)
{
    const uint8_t* b1 = (const uint8_t*)p1;
    const uint8_t* b2 = (const uint8_t*)p2;
    const uint32_t ROW_LENGTH = 16;

    int diff = 0;
    for (uint32_t addr = 0; addr < size; addr += ROW_LENGTH) {
        printf("       %04x  ", addr);
        for (uint32_t pos = 0; pos < ROW_LENGTH; pos++)
            if (addr + pos < size) {
                if (b1[addr + pos] != b2[addr + pos] && !diff) {
                    diff = 1;
                    printf("<span style=\"color: red; font-weight:bold;\">");
                }

                if (b1[addr + pos] == b2[addr + pos] && diff) {
                    diff = 0;
                    printf("</span>");
                }

                printf("%02x ", b1[addr + pos]);
            } else
                printf("   ");

        if (diff) {
            diff = 0;
            printf("</span>");
        }

        printf(" ");

        for (uint32_t pos = 0; addr + pos < size && pos < ROW_LENGTH; pos++)
            putchar(isprint(b1[addr + pos]) ? b1[addr + pos] : '.');

        putchar('\n');
    }
}


void mem_compare(const void* reference, const void* tested, size_t size)
{
    printf("       Zrzut pamięci (dane oczekiwane):\n");
    mem_dump_diff(reference, tested, size);

    printf("       Zrzut pamięci (dane otrzymane):\n");
    mem_dump_diff(tested, reference, size);
}

/*
 ################################################################################################
 #
 # Narzędzia
 #
 ################################################################################################
 */

struct test_limit_t {
    struct rlimit memory_current, memory_old;
    struct rlimit file_write_current, file_write_old;

    int memory_set;
    int file_write_set;

    sighandler_t old_handler;
};

static struct test_limit_t test_limit;


void test_internal_error(int cond, const char* message)
{
    if (cond)
        return; // jest ok

    printf("\n<span style=\"font-weight:bold; color:red\">***\n"
           "   Bład %s\n"
           "   errno=%d; strerror(%d)=%s\n"
           "   Skontaktuj się z autorem testu!\n"
           "***</span>\n", message, errno, errno, strerror(errno));
    test_error(0, "Błąd wewnętrzny testu - skontaktuj się z autorem testu!");
    exit(42);
}


void test_limit_init(void)
{
    memset(&test_limit, 0, sizeof(struct test_limit_t));
}

void test_memory_limit_setup(int soft_limit, int hard_limit)
{
    struct rlimit current;

    //test_internal_error(!test_limit.memory_set, "Limit pamięci jest już aktywny!");


    if (getrlimit (RLIMIT_DATA, &current) != 0)
        test_internal_error(0, "getrlimit");

    memcpy(&test_limit.memory_current, &current, sizeof(struct rlimit));
    memcpy(&test_limit.memory_old, &current, sizeof(struct rlimit));

    test_limit.memory_current.rlim_cur = soft_limit;
    test_limit.memory_current.rlim_max = hard_limit;

    if (setrlimit(RLIMIT_DATA, &test_limit.memory_current) != 0)
        test_internal_error(0, "setrlimit(RLIMIT_DATA, &test_limit.memory_current)");

    test_limit.memory_set = 1;


    /*
    printf("# Limit pamięci: current[soft=%d; hard=%d]; old[soft=%d; hard=%d]\n",
        (int)test_limit.memory_current.rlim_cur, (int)test_limit.memory_current.rlim_max,
        (int)test_limit.memory_old.rlim_cur, (int)test_limit.memory_old.rlim_max);
    */
}

void test_memory_limit_restore(void)
{
/*
    test_internal_error(test_limit.memory_set, "Limit pamięci nie jest aktywny!");
    if (setrlimit(RLIMIT_DATA, &test_memory_limit.old) != 0)
        test_internal_error(0, "setrlimit(RLIMIT_DATA, &test_memory_limit.old)");

    test_limit.memory_set = 0;
*/
}


// ------------------------------

void test_signal_handler(int signo)
{
    switch (signo)
    {
        case SIGXFSZ:
            fprintf(stderr, "Przekroczono wielkość pliku podczas zapisywania (%d, %s)\n", signo, strsignal(signo));
            exit(128 + SIGXFSZ);
            break;
        default:
            assert(0);
    }
}


void test_file_write_limit_setup(int write_limit)
{

    if (getrlimit (RLIMIT_FSIZE, &test_limit.file_write_current) != 0)
        test_internal_error(0, "getrlimit (test_file_write_limit_setup)");

    test_limit.file_write_current.rlim_cur = write_limit;
    //test_limit.file_write_current.rlim_max = write_limit;

    if (setrlimit(RLIMIT_FSIZE, &test_limit.file_write_current) != 0)
        test_internal_error(0, "setrlimit(RLIMIT_FSIZE, &test_limit.file_write_current)");

    //test_internal_error(!test_limit.file_write_set, "Limit wielkości zapisywanego pliku jest już aktywny!");
    test_limit.file_write_set = 1;

    /*
    printf("# Limit pliku: current[soft=%d; hard=%d]; old[soft=%d; hard=%d]\n",
        (int)test_limit.file_write_current.rlim_cur, (int)test_limit.file_write_current.rlim_max,
        (int)test_limit.file_write_old.rlim_cur, (int)test_limit.file_write_old.rlim_max);
    */

    // ustawienie sygnałów
    sighandler_t old = signal(SIGXFSZ, test_signal_handler);
    test_internal_error(old != SIG_ERR, "signal(SIGXFSZ, test_signal_handler) ");
    test_limit.old_handler = old;
}

void test_file_write_limit_restore(void)
{

//    if (setrlimit(RLIMIT_FSIZE, &test_limit.file_write_old) != 0)
//        test_internal_error(0, "setrlimit(RLIMIT_FSIZE, &test_limit.file_write_old)");

    // przywracanie sygnałów
    sighandler_t old = signal(SIGXFSZ, test_limit.old_handler);
    test_internal_error(old != SIG_ERR, "signal(SIGXFSZ, test_limit.old_handler) ");
    test_limit.old_handler = NULL;

    //test_internal_error(test_limit.file_write_set, "Limit wielkości zapisywanego pliku nie jest aktywny!");
    test_limit.file_write_set = 0;
}