Skip to content

Commit

Permalink
Merge pull request #46 from fischerling/runtests
Browse files Browse the repository at this point in the history
[RFC] add test runner and use it in the CI
  • Loading branch information
Galfurian committed Mar 5, 2024
2 parents fcafbb5 + b79fc67 commit 9c9b83e
Show file tree
Hide file tree
Showing 8 changed files with 597 additions and 4 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,20 @@ jobs:
run: |
cmake -B build
cmake --build build --parallel 2
test:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4

- name: Install test dependencies
run: sudo apt-get install -y nasm qemu-system-x86 mtools

- name: Run tests
run: |
cmake -B build -DEMULATOR_OUTPUT_TYPE=OUTPUT_LOG
cmake --build build --parallel 2 --target filesystem cdrom_test.iso qemu-test
- name: Check
run: |
cat build/test.log | scripts/tapview || cat build/test.log build/serial.log
26 changes: 25 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,28 @@ add_custom_target(
qemu-grub
COMMAND ${EMULATOR} ${EMULATOR_FLAGS} -boot d -cdrom ${CMAKE_BINARY_DIR}/cdrom.iso
DEPENDS cdrom.iso
)
)

# =============================================================================
# Booting with QEMU+GRUB for testing
# =============================================================================

# First, we need to build the ISO for the cdrom. It has a slightly different
# kernel command line including 'test'.
add_custom_target(
cdrom_test.iso
COMMAND cp -rf ${CMAKE_SOURCE_DIR}/iso .
COMMAND mv ${CMAKE_BINARY_DIR}/iso/boot/grub/grub.cfg.runtests ${CMAKE_BINARY_DIR}/iso/boot/grub/grub.cfg
COMMAND cp ${CMAKE_BINARY_DIR}/mentos/bootloader.bin ${CMAKE_BINARY_DIR}/iso/boot
COMMAND grub-mkrescue -o ${CMAKE_BINARY_DIR}/cdrom_test.iso ${CMAKE_BINARY_DIR}/iso
DEPENDS bootloader.bin
)

# This target runs the emulator, and executes the runtests binary as init process.
# Additionally it passes the '-device isa-debug-exit' option to shutdown qemu
# after the tests are done.
add_custom_target(
qemu-test
COMMAND ${EMULATOR} ${EMULATOR_FLAGS} -serial file:${CMAKE_BINARY_DIR}/test.log -nographic -device isa-debug-exit -boot d -cdrom ${CMAKE_BINARY_DIR}/cdrom_test.iso
DEPENDS cdrom_test.iso
)
7 changes: 7 additions & 0 deletions iso/boot/grub/grub.cfg.runtests
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set timeout=0
set default=0

menuentry "MentOS tests" {
multiboot /boot/bootloader.bin runtests
boot
}
22 changes: 19 additions & 3 deletions mentos/src/kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "process/scheduler.h"
#include "process/scheduler_feedback.h"
#include "stdio.h"
#include "string.h"
#include "sys/module.h"
#include "sys/msg.h"
#include "sys/sem.h"
Expand Down Expand Up @@ -78,6 +79,9 @@ uintptr_t initial_esp = 0;
/// The boot info.
boot_info_t boot_info;

/// Flag indicating if we are running tests instead of an interactive session
int runtests = 0;

/// @brief Prints [OK] at the current row and column 60.
static inline void print_ok(void)
{
Expand Down Expand Up @@ -403,9 +407,21 @@ int kmain(boot_info_t *boot_informations)
print_ok();

//==========================================================================
pr_notice("Creating init process...\n");
printf("Creating init process...");
task_struct *init_p = process_create_init("/bin/init");
// TODO: fix the hardcoded check for the flags set by GRUB
runtests = boot_info.multiboot_header->flags == 0x1a67 &&
bitmask_check(boot_info.multiboot_header->flags, MULTIBOOT_FLAG_CMDLINE) &&
strcmp((char *)boot_info.multiboot_header->cmdline, "runtests") == 0;

task_struct *init_p;
if (runtests) {
pr_notice("Creating runtests process...\n");
printf("Creating runtests process...");
init_p = process_create_init("/bin/runtests");
} else {
pr_notice("Creating init process...\n");
printf("Creating init process...");
init_p = process_create_init("/bin/init");
}
if (!init_p) {
print_fail();
return 1;
Expand Down
5 changes: 5 additions & 0 deletions mentos/src/system/panic.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@

#include "system/panic.h"
#include "io/debug.h"
#include "io/port_io.h"

#define SHUTDOWN_PORT 0x604
extern int runtests;

void kernel_panic(const char *msg)
{
pr_emerg("\nPANIC:\n%s\n\nWelcome to Kernel Debugging Land...\n\n", msg);
pr_emerg("\n");
__asm__ __volatile__("cli"); // Disable interrupts
if (runtests) { outports(SHUTDOWN_PORT, 0x2000); } // Terminate qemu running the tests
for (;;) __asm__ __volatile__("hlt"); // Decrease power consumption with hlt
}
1 change: 1 addition & 0 deletions programs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# List of programs.
set(PROGRAM_LIST
runtests.c
more.c
chmod.c
chown.c
Expand Down
233 changes: 233 additions & 0 deletions programs/runtests.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/// @file runtests.c
/// @brief
/// @copyright (c) 2024 This file is distributed under the MIT License.
/// See LICENSE.md for details.

#include <fcntl.h>
#include <io/port_io.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <strerror.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/unistd.h>
#include <sys/wait.h>

#define SHUTDOWN_PORT 0x604
/// Second serial port for QEMU.
#define SERIAL_COM2 0x02F8

static char *all_tests[] = {
"t_abort",
"t_alarm",
"t_creat",
"t_dup",
"t_exec execl",
"t_exec execlp",
"t_exec execle",
"t_exec execlpe",
"t_exec execv",
"t_exec execvp",
"t_exec execve",
"t_exec execvpe",
"t_fork 10",
"t_gid",
"t_groups",
"t_itimer",
"t_kill",
/* "t_mem", */
"t_msgget",
/* "t_periodic1", */
/* "t_periodic2", */
/* "t_periodic3", */
"t_schedfb",
"t_semflg",
"t_semget",
"t_semop",
"t_setenv",
"t_shmget",
/* "t_shm_read", */
/* "t_shm_write", */
"t_sigaction",
"t_sigfpe",
"t_siginfo",
"t_sigmask",
"t_sigusr",
"t_sleep",
"t_stopcont",
"t_write_read",
};

static char **tests = &all_tests[0];
static int testsc = sizeof(all_tests) / sizeof(all_tests[0]) ;

static char buf[4096];
static char* bufpos = buf;

static int test_out_fd;
static int test_err_fd;

static int init;

#define append(...) \
do { \
bufpos += sprintf(bufpos, __VA_ARGS__); \
if (bufpos >= buf + sizeof(buf)) { \
return -1; \
} \
} while(0);

static int test_out_flush(void) {
int ret = 0;
if (!init) {
ret = printf("%s\n", buf);
} else {
char *s = buf;
while((*s) != 0)
outportb(SERIAL_COM2, *s++);
outportb(SERIAL_COM2, '\n');
}
bufpos = buf;
*bufpos = 0;
return ret;
}

static int test_out(const char *restrict format, ...) {
va_list ap;
va_start(ap, format);
bufpos += vsprintf(bufpos, format, ap);
if (bufpos >= buf + sizeof(buf)) {
return -1;
}
va_end(ap);

return test_out_flush();
}

static int test_ok(int test, int success, const char *restrict format, ...) {
if (!success) {
append("not ");
}
append("ok %d - %s", test, tests[test - 1]);
if (format) {
append(": ");
va_list ap;
va_start(ap, format);
bufpos += vsprintf(bufpos, format, ap);
if (bufpos >= buf + sizeof(buf)) {
return -1;
}
va_end(ap);
}

return test_out_flush();
}

static void exec_test(char *test_cmd_line) {
// Setup the childs stdout, stderr streams
close(STDOUT_FILENO);
dup(test_out_fd);
close(STDERR_FILENO);
dup(test_err_fd);

// Build up the test argv vector
char *test_argv[32];
char *arg = strtok(test_cmd_line, " ");
test_argv[0] = arg;

int t_argc = 1;
while ((arg = strtok(NULL, " "))) {
if (t_argc >= sizeof(test_argv) / sizeof(test_argv[0])) {
exit(126);
}
test_argv[t_argc] = arg;
t_argc++;
}

test_argv[t_argc] = NULL;

char test_abspath[PATH_MAX];
sprintf(test_abspath, "/bin/tests/%s", test_argv[0]);
execvp(test_abspath, test_argv);
}

static void run_test(int n, char *test_cmd_line) {
int child = fork();
if (child == 0) {
exec_test(test_cmd_line);
// If the exec returns something went wrong
exit(127);
}

if (child < 0) {
fprintf(STDERR_FILENO, "fork: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}

int status;
waitpid(child, &status, 0);

int success = WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (success) {
test_ok(n, success, NULL);
} else {
if (WIFSIGNALED(status)) {
test_ok(n, success, "Signal: %d", WSTOPSIG(status));
} else {
test_ok(n, success, "Exit: %d", WEXITSTATUS(status));
}
}
}

int runtests_main(int argc, char **argv) {
for (int i = 1; i < argc; i++) {
if (strncmp(argv[i], "--help", 6) == 0) {
printf("Usage: %s [--help] [TEST]...\n", argv[0]);
printf("Run one, more, or all available tests\n");
printf(" --help display this help and exit\n");
exit(EXIT_SUCCESS);
}
}

// TODO: capture test output
int devnull = open("/dev/null", O_RDONLY, 0);
if (devnull < 0) {
fprintf(STDERR_FILENO, "open: /dev/null: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
test_out_fd = test_err_fd = devnull;

if (argc > 1) {
tests = argv + 1;
testsc = argc - 1;
}

test_out("1..%d", testsc);

char *test_argv[32];
for (int i = 0; i < testsc; i++) {
run_test(i + 1, tests[i]);
}

// We are running as init
if (init) {
outports(SHUTDOWN_PORT, 0x2000);
}

return 0;
}

int main(int argc, char **argv)
{
// Are we the init process
init = getpid() == 1;
if (init) {
pid_t runtests = fork();
if (runtests) {
while (1) { wait(NULL); }
}
}

return runtests_main(argc, argv);
}
Loading

0 comments on commit 9c9b83e

Please sign in to comment.