Blob Blame History Raw
/*
 * This code is based on CuTest by Asim Jalis
 * http://sourceforge.net/projects/cutest/
 *
 * The license for the original code is
 * LICENSE
 *
 * Copyright (c) 2003 Asim Jalis
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software in
 * a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <config.h>

#include <sys/wait.h>
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "cutest.h"
#include "memory.h"

#define HUGE_STRING_LEN 8192
#define STRING_MAX      256

#define asprintf_or_die(strp, fmt, args ...)                            \
    if (asprintf(strp, fmt, ## args) == -1) {                           \
        fprintf(stderr, "Fatal error (probably out of memory)\n");      \
        abort();                                                        \
    }

void die_oom(void) {
    printf("Ran out of memory. Send more\n");
    exit(2);
}

/*-------------------------------------------------------------------------*
 * CuTest
 *-------------------------------------------------------------------------*/

void CuTestInit(CuTest* t, const char* name, TestFunction function) {
    t->name = strdup(name);
    t->failed = 0;
    t->ran = 0;
    t->message = NULL;
    t->function = function;
    t->jumpBuf = NULL;
}

CuTest* CuTestNew(const char* name, TestFunction function) {
    CuTest* tc = NULL;
    if (ALLOC(tc) < 0)
        die_oom();
    CuTestInit(tc, name, function);
    return tc;
}

void CuTestRun(CuTest* tc, TestFunction setup, TestFunction teardown) {
    jmp_buf buf;

    if (getenv("CUTEST") && STRNEQ(getenv("CUTEST"), tc->name))
        return;
    tc->jumpBuf = &buf;
    if (setjmp(buf) == 0) {
        if (setup)
            (setup)(tc);
        tc->ran = 1;
        (tc->function)(tc);
    }
    if (teardown && setjmp(buf) == 0) {
        (teardown)(tc);
    }
    tc->jumpBuf = 0;
}

static void CuFailInternal(CuTest* tc, const char* file, int line,
                           const char *string) {
    char *buf = NULL;

    asprintf_or_die(&buf, "%s:%d: %s", file, line, string);

    tc->failed = 1;
    tc->message = buf;
    if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
}

void CuFail_Line(CuTest* tc, const char* file, int line,
                 const char* message2, const char* message) {
    char *string = NULL;

    if (message2 != NULL) {
        asprintf_or_die(&string, "%s:%s", message2, message);
    } else {
        string = strdup(message);
    }
    CuFailInternal(tc, file, line, string);
}

void CuAssert_Line(CuTest* tc, const char* file, int line,
                   const char* message, int condition) {
    if (condition) return;
    CuFail_Line(tc, file, line, NULL, message);
}

void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               const char* expected, const char* actual) {
    char *string = NULL;

    if ((expected == NULL && actual == NULL) ||
        (expected != NULL && actual != NULL &&
         strcmp(expected, actual) == 0))
        {
            return;
        }

    if (message != NULL) {
        asprintf_or_die(&string, "%s: expected <%s> but was <%s>", message,
                        expected, actual);
    } else {
        asprintf_or_die(&string, "expected <%s> but was <%s>", expected, actual);
    }
    CuFailInternal(tc, file, line, string);
}

void CuAssertStrNotEqual_LineMsg(CuTest* tc, const char* file, int line,
                                 const char* message,
                                 const char* expected, const char* actual) {
    char *string = NULL;

    if (expected != NULL && actual != NULL && strcmp(expected, actual) != 0)
        return;

    if (message != NULL) {
        asprintf_or_die(&string, "%s: expected <%s> but was <%s>", message,
                        expected, actual);
    } else {
        asprintf_or_die(&string, "expected <%s> but was <%s>", expected, actual);
    }
    CuFailInternal(tc, file, line, string);
}

void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               int expected, int actual) {
    char buf[STRING_MAX];
    if (expected == actual) return;
    sprintf(buf, "expected <%d> but was <%d>", expected, actual);
    CuFail_Line(tc, file, line, message, buf);
}

void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               double expected, double actual, double delta) {
    char buf[STRING_MAX];
    if (fabs(expected - actual) <= delta) return;
    sprintf(buf, "expected <%lf> but was <%lf>", expected, actual);
    CuFail_Line(tc, file, line, message, buf);
}

void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               const void* expected, const void* actual) {
    char buf[STRING_MAX];
    if (expected == actual) return;
    sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual);
    CuFail_Line(tc, file, line, message, buf);
}

void CuAssertPtrNotEqual_LineMsg(CuTest* tc, const char* file, int line,
                                 const char* message,
                                 const void* expected, const void* actual) {
    char buf[STRING_MAX];
    if (expected != actual) return;
    sprintf(buf, "expected pointer <0x%p> to be different from <0x%p>",
            expected, actual);
    CuFail_Line(tc, file, line, message, buf);
}


/*-------------------------------------------------------------------------*
 * CuSuite
 *-------------------------------------------------------------------------*/

void CuSuiteInit(CuSuite* testSuite) {
    testSuite->count = 0;
    testSuite->failCount = 0;
}

CuSuite* CuSuiteNew(void) {
    CuSuite* testSuite = NULL;
    if (ALLOC(testSuite) < 0)
        die_oom();
    CuSuiteInit(testSuite);
    return testSuite;
}

void CuSuiteSetup(CuSuite *testSuite,
                  TestFunction setup, TestFunction teardown) {
    testSuite->setup = setup;
    testSuite->teardown = teardown;
}

void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase) {
    assert(testSuite->count < MAX_TEST_CASES);
    testSuite->list[testSuite->count] = testCase;
    testSuite->count++;
}

void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) {
    int i;
    for (i = 0 ; i < testSuite2->count ; ++i)
        {
            CuTest* testCase = testSuite2->list[i];
            CuSuiteAdd(testSuite, testCase);
        }
}

void CuSuiteRun(CuSuite* testSuite) {
    int i;
    for (i = 0 ; i < testSuite->count ; ++i)
        {
            CuTest* testCase = testSuite->list[i];
            CuTestRun(testCase, testSuite->setup, testSuite->teardown);
            if (testCase->failed) { testSuite->failCount += 1; }
        }
}

static void string_append(char **s, const char *p) {
    if (*s == NULL) {
        *s = strdup(p);
    } else {
        int len = strlen(*s) + strlen(p) + 1;
        *s = realloc(*s, len);
        if (*s == NULL)
            die_oom();
        strcat(*s, p);
    }
}

void CuSuiteSummary(CuSuite* testSuite, char **summary) {
    int i;

    for (i = 0 ; i < testSuite->count ; ++i)
        {
            CuTest* testCase = testSuite->list[i];
            string_append(summary, testCase->failed ? "F" : ".");
        }
    string_append(summary, "\n\n");
}

void CuSuiteDetails(CuSuite* testSuite, char **details) {
    int i;
    int failCount = 0;
    char *s = NULL;

    if (testSuite->failCount == 0)
        {
            int passCount = testSuite->count - testSuite->failCount;
            const char* testWord = passCount == 1 ? "test" : "tests";
            asprintf_or_die(&s, "OK (%d %s)\n", passCount, testWord);
            string_append(details, s);
            free(s);
        } else {
        if (testSuite->failCount == 1)
            string_append(details, "There was 1 failure:\n");
        else {
            asprintf_or_die(&s, "There were %d failures:\n",
                            testSuite->failCount);
            string_append(details, s);
            free(s);
        }
        for (i = 0 ; i < testSuite->count ; ++i) {
            CuTest* testCase = testSuite->list[i];
            if (testCase->failed) {
                failCount++;
                asprintf_or_die(&s, "%d) %s:\n%s\n",
                         failCount, testCase->name, testCase->message);
                string_append(details, s);
                free(s);
            }
        }
        string_append(details, "\n!!!FAILURES!!!\n");

        asprintf_or_die(&s, "Runs: %d ",   testSuite->count);
        string_append(details, s);
        free(s);

        asprintf_or_die(&s, "Passes: %d ",
                        testSuite->count - testSuite->failCount);
        string_append(details, s);
        free(s);

        asprintf_or_die(&s, "Fails: %d\n",  testSuite->failCount);
        string_append(details, s);
        free(s);
    }
}

void CuSuiteFree(CuSuite *suite) {
    for (int i=0; i < suite->count; i++) {
        CuTest *test = suite->list[i];
        free(test->name);
        free(test->message);
        free(test);
    }
    free(suite);
}
/*
 * Test utilities
 */
void run(CuTest *tc, const char *format, ...) {
    char *command;
    va_list args;
    int r;

    va_start(args, format);
    r = vasprintf(&command, format, args);
    va_end (args);
    if (r < 0)
        CuFail(tc, "Failed to format command (out of memory)");
    r = system(command);
    if (r < 0 || (WIFEXITED(r) && WEXITSTATUS(r) != 0)) {
        char *msg;
        r = asprintf(&msg, "Command %s failed with status %d\n",
                     command, WEXITSTATUS(r));
        CuFail(tc, msg);
        free(msg);
    }
    free(command);
}

int should_run(const char *name, int argc, char **argv) {
    if (argc == 0)
        return 1;
    for (int i=0; i < argc; i++)
        if (STREQ(argv[i], name))
            return 1;
    return 0;
}

/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */