Skip to content

Commit

Permalink
Add ktest kernel testing framework
Browse files Browse the repository at this point in the history
ktest is pretty simple and based off of some of my other C unit testing
frameworks. it is based off of two structures, the `struct ktest_module`
and `struct ktest_unit`. Every module has an array of `struct
ktest_unit` entrise, which are called in series. The `struct ktest_unit`
has a callback to run that set of tests, and then returns the number of
failures. All the failures are collected together and reported at the
end.

It also includes some convience macros in the form of `ktest_assert_*`
macros, which use _Geenric to detect the type of the input and do the
correct comparison.

We make use of a new `.ktest` section in the binary to find all of our
`struct ktest_module` entries at runtime. The macro __ktest can be used
to place a module into that section.

Testing can be turned on or off via the `CONFIG_KERNEL_TESTS` config
option.
  • Loading branch information
mkilgore committed Sep 21, 2019
1 parent 717fa99 commit f20b659
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 17 deletions.
1 change: 1 addition & 0 deletions arch/x86/boot/link_multiboot.ldS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ SECTIONS
*(.text)
*(.rodata)
*(.rodata.*)
KTEST_SECTION
}

/* Read-write data (initialized) */
Expand Down
3 changes: 3 additions & 0 deletions include/protura/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#define __align(size) __attribute__((__aligned__(size)))

#define __unused __attribute__((unused))
#define __used __attribute__((used))

#define __section(s) __attribute__((section(s)))

#define unlikely(cond) (__builtin_expect(!!(cond), 0))
#define likely(cond) (__builtin_expect(!!(cond), 1))
Expand Down
111 changes: 111 additions & 0 deletions include/protura/ktest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#ifndef INCLUDE_PROTURA_KTEST_H
#define INCLUDE_PROTURA_KTEST_H

#include <protura/types.h>

struct ktest;

struct ktest_unit {
int (*test) (struct ktest *);
const char *name;
};

#define KTEST_UNIT_INIT(nm, t) \
{ \
.test = (t), \
.name = (nm), \
}

struct ktest_module {
const char *name;
const struct ktest_unit *tests;
size_t test_count;
};

#define __ktest __used __section(".ktest")

enum ktest_value_type {
KTEST_VALUE_INT64,
KTEST_VALUE_UINT64,
KTEST_VALUE_PTR,
KTEST_VALUE_STR,
KTEST_VALUE_MEM,
};

struct ktest_value {
enum ktest_value_type type;
const char *value_string;
union {
int64_t int64;
uint64_t uint64;
struct {
const char *ptr;
size_t len;
};
};
};

static inline struct ktest_value __ktest_make_int64(const char *str, int64_t int64)
{
return (struct ktest_value) { .type = KTEST_VALUE_INT64, .value_string = str, .int64 = int64 };
}

static inline struct ktest_value __ktest_make_uint64(const char *str, uint64_t uint64)
{
return (struct ktest_value) { .type = KTEST_VALUE_UINT64, .value_string = str, .uint64 = uint64 };
}

static inline struct ktest_value __ktest_make_ptr(const char *str, const void *ptr)
{
return (struct ktest_value) { .type = KTEST_VALUE_PTR, .value_string = str, .ptr = ptr };
}

static inline struct ktest_value __ktest_make_str(const char *str, const void *ptr)
{
return (struct ktest_value) { .type = KTEST_VALUE_STR, .value_string = str, .ptr = ptr };
}

static inline struct ktest_value __ktest_make_mem(const char *str, size_t len, const void *ptr)
{
return (struct ktest_value) { .type = KTEST_VALUE_MEM, .value_string = str, .len = len, .ptr = ptr };
}

#define __ktest_make_value(vs, v) \
_Generic((v), \
int64_t: __ktest_make_int64, \
uint64_t: __ktest_make_uint64, \
char *: __ktest_make_str, \
const char *: __ktest_make_str, \
default: _Generic((v - v), \
ptrdiff_t: __ktest_make_ptr, \
default: __ktest_make_int64 \
) \
) (vs, v)

#define ktest_assert_equal(kt, expect, act) \
({ \
struct ktest_value v1 = __ktest_make_value(#expect, expect); \
struct ktest_value v2 = __ktest_make_value(#act, act); \
ktest_assert_equal_value_func((kt), &v1, &v2, __func__); \
})

#define ktest_assert_notequal(kt, expect, act) \
({ \
struct ktest_value v1 = __ktest_make_value(#expect, expect); \
struct ktest_value v2 = __ktest_make_value(#act, act); \
ktest_assert_notequal_value_func((kt), &v1, &v2, __func__); \
})

#define ktest_assert_mem(kt, expect, act, len) \
({ \
struct ktest_value v1 = __ktest_make_mem(#expect, (expect), (len)); \
struct ktest_value v2 = __ktest_make_mem(#act, (act), (len)); \
ktest_assert_notequal_value_func((kt), &v1, &v2, __func__); \
})

int ktest_assert_equal_value_func(struct ktest *, struct ktest_value *expected, struct ktest_value *actual, const char *func);
int ktest_assert_notequal_value_func(struct ktest *, struct ktest_value *expected, struct ktest_value *actual, const char *func);

void ktest_init(void);

#endif
5 changes: 5 additions & 0 deletions include/protura/mm/memlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@

#include <arch/memlayout.h>

#define KTEST_SECTION \
__ktest_start = .; \
KEEP(*(.ktest)); \
__ktest_end = .;

#endif
37 changes: 20 additions & 17 deletions include/protura/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ typedef __kint32_t __kgid_t;
typedef char * __kcaddr_t;
typedef __kuintptr_t __kdaddr_t;

typedef long __kptrdiff_t;

#ifdef __KERNEL__

#include <protura/compiler.h>
Expand All @@ -63,26 +65,27 @@ typedef __kumode_t umode_t;
typedef __ktime_t time_t;
typedef __kuseconds_t useconds_t;
typedef __ksuseconds_t suseconds_t;
// typedef __kn16 n16;
// typedef __kn32 n32;
typedef __kuid_t uid_t;
typedef __kgid_t gid_t;

#define tolower(c) \
({ \
typeof(c) ____ctmp = (c); \
if (____ctmp >= 'A' && ____ctmp <= 'Z') \
____ctmp |= 0x20; \
____ctmp; \
})

#define toupper(c) \
({ \
typeof(c) ____ctmp = (c); \
if (____ctmp >= 'a' && ____ctmp <= 'z') \
____ctmp &= ~0x20; \
____ctmp; \
})
typedef __kptrdiff_t ptrdiff_t;

static inline char __tolower(char c)
{
if (c >= 'A' && c <= 'Z')
c |= 0x20;
return c;
}

static inline char __toupper(char c)
{
if (c >= 'a' && c <= 'z')
c &= ~0x20;
return c;
}

#define tolower(c) __tolower((c))
#define toupper(c) __toupper((c))

#endif

Expand Down
2 changes: 2 additions & 0 deletions protura.conf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ KERNEL_LOG_ICMP = y
KERNEL_LOG_UDP = y
KERNEL_LOG_TCP = y

KERNEL_TESTS = y

# If set, then the source location of the kp() is recorded in the log.
KERNEL_LOG_SRC_LINE = n

Expand Down
5 changes: 5 additions & 0 deletions src/init/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <protura/fs/vfs.h>
#include <protura/fs/sync.h>
#include <protura/drivers/term.h>
#include <protura/ktest.h>

#include <protura/init/init_basic.h>

Expand All @@ -38,6 +39,10 @@ static int start_user_init(void *unused)
(sys->init) ();
}

#ifdef CONFIG_KERNEL_TESTS
ktest_init();
#endif

kp(KP_NORMAL, "Kernel is done booting!\n");

kp_output_unregister(term_printfv);
Expand Down
1 change: 1 addition & 0 deletions src/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ objs-y += time.o
objs-y += ktimer.o
objs-y += sys_user.o
objs-y += uname.o
objs-$(CONFIG_KERNEL_TESTS) += ktest.o
objs-y += cmdline.o

subdir-y += str
Expand Down
157 changes: 157 additions & 0 deletions src/kernel/ktest.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2019 Matt Kilgore
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation.
*/

#include <protura/types.h>
#include <protura/debug.h>
#include <protura/snprintf.h>
#include <protura/string.h>
#include <protura/cmdline.h>
#include <protura/ktest.h>

#include <arch/reset.h>

struct ktest {
int cur_test;
int total_tests;
};

extern struct ktest_module __ktest_start;
extern struct ktest_module __ktest_end;

static int run_module(struct ktest_module *module)
{
char buf[256];
const struct ktest_unit *tests = module->tests;
struct ktest ktest;
memset(&ktest, 0, sizeof(ktest));
int error_count = 0;

kp(KP_NORMAL, "==== Starting tests for %s ====\n", module->name);

int i;
for (i = 0; i < module->test_count; i++) {
ktest.cur_test = 0;

kp(KP_NORMAL, "== #%d: %s ==\n", i, tests[i].name);

int errs = (tests + i)->test (&ktest);

if (errs != 0)
snprintf(buf, sizeof(buf), "FAIL -> %d", errs);
else
snprintf(buf, sizeof(buf), "PASS");

kp(KP_NORMAL, "== Result: %s ==\n", buf);

error_count += errs;
}

kp(KP_NORMAL, "==== Finished tests for %s ====\n", module->name);

if (error_count == 0)
snprintf(buf, sizeof(buf), "PASS");
else
snprintf(buf, sizeof(buf), "FAIL -> %d ", error_count);
kp(KP_NORMAL, "==== Result: %s ====\n", buf);

return error_count;
}

static void run_ktest_modules(void)
{
int errs = 0;
struct ktest_module *start = &__ktest_start;
struct ktest_module *end = &__ktest_end;

for (; start < end; start++)
errs += run_module(start);

char buf[255];

if (!errs)
snprintf(buf, sizeof(buf), "PASS");
else
snprintf(buf, sizeof(buf), "FAIL -> %d", errs);

kp(KP_NORMAL, "==== Full test run: %s ====\n", buf);
}


static int ktest_value_comp(struct ktest_value *v1, struct ktest_value *v2)
{
if (v1->type != v2->type)
return 1;

switch (v1->type) {
case KTEST_VALUE_INT64:
return v1->int64 == v2->int64;

case KTEST_VALUE_UINT64:
return v1->uint64 == v2->uint64;

case KTEST_VALUE_PTR:
return v1->ptr == v2->ptr;

case KTEST_VALUE_STR:
return !strcmp(v1->ptr, v2->ptr);

case KTEST_VALUE_MEM:
return !memcmp(v1->ptr, v2->ptr, v1->len);
}

return 1;
}

#if 0
static void ktest_value_show(const char *prefix, const char *asdf, struct ktest_value *v)
{

}
#endif

int ktest_assert_equal_value_func(struct ktest *ktest, struct ktest_value *expected, struct ktest_value *actual, const char *func)
{
char buf[255];
ktest->total_tests++;
ktest->cur_test++;

int result = ktest_value_comp(expected, actual);

if (result)
snprintf(buf, sizeof(buf), "PASS");
else
snprintf(buf, sizeof(buf), "FAIL");

kp(KP_NORMAL, " [%02d:%03d] %s: %s == %s: %s\n", ktest->total_tests, ktest->cur_test, func, expected->value_string, actual->value_string, buf);

return !result;
}

int ktest_assert_notequal_value_func(struct ktest *ktest, struct ktest_value *expected, struct ktest_value *actual, const char *func)
{
char buf[255];
ktest->total_tests++;
ktest->cur_test++;

int result = ktest_value_comp(expected, actual) == 0;

if (result)
snprintf(buf, sizeof(buf), "PASS");
else
snprintf(buf, sizeof(buf), "FAIL");

kp(KP_NORMAL, " [%02d:%03d] %s: %s != %s: %s\n", ktest->total_tests, ktest->cur_test, func, expected->value_string, actual->value_string, buf);

return !result;
}

void ktest_init(void)
{
run_ktest_modules();
}

0 comments on commit f20b659

Please sign in to comment.