root/src/tests/system/kernel/unit/kernel_unit_tests.cpp
/*
 * Copyright 2009-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <ctype.h>
#include <string.h>

#include <Drivers.h>
#include <KernelExport.h>

#include <AutoDeleter.h>

#include <kernel.h>
#include <thread.h>

#include "TestContext.h"
#include "TestManager.h"
#include "TestOutput.h"

#include "lock/LockTestSuite.h"


int32 api_version = B_CUR_DRIVER_API_VERSION;

static const char* sDeviceNames[] = {
        "kernel_unit_tests",
        NULL
};

static const char* const kUsage =
        "Usage:\n"
        "  help\n"
        "    Print usage info.\n"
        "\n"
        "  list\n"
        "    Print available tests.\n"
        "\n"
        "  run [ <options> ] [ <tests> ]\n"
        "    Run tests. The tests to be run can be given as arguments. When no "
                "tests\n"
        "        are specified, all tests are run.\n"
        "\n"
        "    Options:\n"
        "         -p    - panic(), when a test check fails.\n"
        "         -q    - Don't run any further tests after the first test failure.\n";

static TestManager* sTestManager = NULL;


// #pragma mark -


static bool
parse_command_line(char* buffer, char**& _argv, int& _argc)
{
        // Process the argument string. We split at whitespace, heeding quotes and
        // escaped characters. The processed arguments are written to the given
        // buffer, separated by single null chars.
        char* start = buffer;
        char* out = buffer;
        bool pendingArgument = false;
        int argc = 0;
        while (*start != '\0') {
                // ignore whitespace
                if (isspace(*start)) {
                        if (pendingArgument) {
                                *out = '\0';
                                out++;
                                argc++;
                                pendingArgument = false;
                        }
                        start++;
                        continue;
                }

                pendingArgument = true;

                if (*start == '"' || *start == '\'') {
                        // quoted text -- continue until closing quote
                        char quote = *start;
                        start++;
                        while (*start != '\0' && *start != quote) {
                                if (*start == '\\' && quote == '"') {
                                        start++;
                                        if (*start == '\0')
                                                break;
                                }
                                *out = *start;
                                start++;
                                out++;
                        }

                        if (*start != '\0')
                                start++;
                } else {
                        // unquoted text
                        if (*start == '\\') {
                                // escaped char
                                start++;
                                if (start == '\0')
                                        break;
                        }

                        *out = *start;
                        start++;
                        out++;
                }
        }

        if (pendingArgument) {
                *out = '\0';
                argc++;
        }

        // allocate argument vector
        char** argv = new(std::nothrow) char*[argc + 1];
        if (argv == NULL)
                return false;

        // fill vector
        start = buffer;
        for (int i = 0; i < argc; i++) {
                argv[i] = start;
                start += strlen(start) + 1;
        }
        argv[argc] = NULL;

        _argv = argv;
        _argc = argc;
        return true;
}


// #pragma mark - device hooks


static status_t
device_open(const char* name, uint32 openMode, void** _cookie)
{
        *_cookie = NULL;
        return B_OK;
}


static status_t
device_close(void* cookie)
{
        return B_OK;
}


static status_t
device_free(void* cookie)
{
        return B_OK;
}


static status_t
device_control(void* cookie, uint32 op, void* arg, size_t length)
{
        return B_BAD_VALUE;
}


static status_t
device_read(void* cookie, off_t position, void* data, size_t* numBytes)
{
        *numBytes = 0;
        return B_OK;
}


static status_t
device_write(void* cookie, off_t position, const void* data, size_t* numBytes)
{
        if (position != 0)
                return B_BAD_VALUE;

        // copy data to stack buffer
        char* buffer = (char*)malloc(*numBytes + 1);
        if (buffer == NULL)
                return B_NO_MEMORY;
        MemoryDeleter bufferDeleter(buffer);

        Thread* thread = thread_get_current_thread();
        if ((thread->flags & THREAD_FLAGS_SYSCALL) != 0) {
                if (!IS_USER_ADDRESS(data)
                        || user_memcpy(buffer, data, *numBytes) != B_OK) {
                        return B_BAD_ADDRESS;
                }
        } else
                memcpy(buffer, data, *numBytes);

        buffer[*numBytes] = '\0';

        // create an output
        TestOutput* output = new(std::nothrow) DebugTestOutput;
        if (output == NULL)
                return B_NO_MEMORY;
        ObjectDeleter<TestOutput> outputDeleter(output);

        // parse arguments
        char** argv;
        int argc;
        if (!parse_command_line(buffer, argv, argc))
                return B_NO_MEMORY;
        ArrayDeleter<char*> argvDeleter(argv);

        if (argc == 0)
                return B_BAD_VALUE;

        // execute command
        if (strcmp(argv[0], "help") == 0) {
                // print usage -- print individual lines to avoid dprintf() buffer
                // overflows
                const char* usage = kUsage;
                while (*usage != '\0') {
                        const char* lineEnd = strchr(usage, '\n');
                        if (lineEnd != NULL)
                                lineEnd++;
                        else
                                lineEnd = usage + strlen(usage);

                        output->Print("%.*s", (int)(lineEnd - usage), usage);
                        usage = lineEnd;
                }
        } else if (strcmp(argv[0], "list") == 0) {
                sTestManager->ListTests(*output);
        } else if (strcmp(argv[0], "run") == 0) {
                // parse options
                TestOptions options;
                int argi = 1;
                while (argi < argc) {
                        const char* arg = argv[argi];
                        if (*arg == '-') {
                                for (arg++; *arg != '\0'; arg++) {
                                        switch (*arg) {
                                                case 'p':
                                                        options.panicOnFailure = true;
                                                        break;
                                                case 'q':
                                                        options.quitAfterFailure = true;
                                                        break;
                                                default:
                                                        output->Print("Invalid option: \"-%c\"\n", *arg);
                                                        return B_BAD_VALUE;
                                        }
                                }

                                argi++;
                        } else
                                break;
                }

                GlobalTestContext globalContext(*output, options);
                sTestManager->RunTests(globalContext, argv + argi, argc - argi);
        } else {
                output->Print("Invalid command \"%s\"!\n", argv[0]);
                return B_BAD_VALUE;
        }

        return B_OK;
}


// #pragma mark - driver interface


static device_hooks sDeviceHooks = {
        device_open,
        device_close,
        device_free,
        device_control,
        device_read,
        device_write
};


status_t
init_hardware(void)
{
        return B_OK;
}


status_t
init_driver(void)
{
        sTestManager = new(std::nothrow) TestManager;
        if (sTestManager == NULL)
                return B_NO_MEMORY;

        // register test suites
        sTestManager->AddTest(create_lock_test_suite());

        return B_OK;
}


void
uninit_driver(void)
{
        delete sTestManager;
}


const char**
publish_devices(void)
{
        return sDeviceNames;
}


device_hooks*
find_device(const char* name)
{
        return strcmp(name, sDeviceNames[0]) == 0 ? &sDeviceHooks : NULL;
}