From 35cfbd16860d33cc1ec99ae19ed684f8d33573f2 Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Sat, 29 Jun 2019 23:50:44 +0100 Subject: [PATCH] Add runtime testing harness --- .gitignore | 2 ++ build.zig | 36 ++++++++++++------- src/kernel/arch/x86/gdt.zig | 1 + src/kernel/arch/x86/idt.zig | 1 + src/kernel/arch/x86/pit.zig | 2 ++ src/kernel/kmain.zig | 3 +- src/kernel/tty.zig | 1 + test/kernel/arch/x86/rt-test.py | 6 ++++ test/rt-test.py | 64 +++++++++++++++++++++++++++++++++ 9 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 test/kernel/arch/x86/rt-test.py create mode 100644 test/rt-test.py diff --git a/.gitignore b/.gitignore index 011a068..6b6d674 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ zig-cache # Build dir bin/* +*.pyc +__pycache__ diff --git a/build.zig b/build.zig index b0d6110..467efa4 100644 --- a/build.zig +++ b/build.zig @@ -25,7 +25,9 @@ pub fn build(b: *Builder) void { var build_path = b.option([]const u8, "build-path", "path to build to") orelse "bin"; var src_path = b.option([]const u8, "source-path", "path to source") orelse "src"; var target = b.option([]const u8, "target", "target to build/run for") orelse "x86"; + const rt_test = b.option(bool, "rt-test", "enable/disable runtime testing") orelse false; const builtin_target = if (mem.eql(u8, target, "x86")) builtin.Arch.i386 else unreachable; + const zig_path = b.option([]const u8, "zig-path", "the path to the zig binary to use for rt testing") orelse "/snap/bin/zig"; b.makePath(build_path) catch unreachable; var grub_path = concat(b.allocator, build_path, "/iso/boot/grub") catch unreachable; @@ -60,20 +62,25 @@ pub fn build(b: *Builder) void { b.default_step.dependOn(link_step); for (iso_step.toSlice()) |step| b.default_step.dependOn(step); - buildRun(b, builtin_target, build_path, iso_path.toSlice(), debug); + buildRun(b, builtin_target, build_path, iso_path.toSlice(), debug, rt_test); buildDebug(b); - buildTest(b, src_path); + buildTest(b, src_path, rt_test, target, zig_path); } -fn buildTest(b: *Builder, src_path: []const u8) void { - const step = b.step("test", "Run all tests"); - const src_path2 = concat(b.allocator, src_path, "/") catch unreachable; - for (src_files.toSlice()) |file| { - var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; - file_src.append(".zig") catch unreachable; - const tst = b.addTest(file_src.toSlice()); - tst.setMainPkgPath("."); - step.dependOn(&tst.step); +fn buildTest(b: *Builder, src_path: []const u8, rt_test: bool, target: []const u8, zig_path: []const u8) void { + const step = b.step("test", "Run tests"); + if (rt_test) { + const script = b.addSystemCommand([][]const u8{ "python3", "test/rt-test.py", target, zig_path}); + step.dependOn(&script.step); + } else { + const src_path2 = concat(b.allocator, src_path, "/") catch unreachable; + for (src_files.toSlice()) |file| { + var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; + file_src.append(".zig") catch unreachable; + const tst = b.addTest(file_src.toSlice()); + tst.setMainPkgPath("."); + step.dependOn(&tst.step); + } } } @@ -89,7 +96,7 @@ fn buildDebug(b: *Builder) void { step.dependOn(&cmd.step); } -fn buildRun(b: *Builder, target: builtin.Arch, build_path: []const u8, iso_path: []const u8, debug: bool) void { +fn buildRun(b: *Builder, target: builtin.Arch, build_path: []const u8, iso_path: []const u8, debug: bool, rt_test: bool) void { const step = b.step("run", "Run with qemu"); const qemu = if (target == builtin.Arch.i386) "qemu-system-i386" else unreachable; var qemu_flags = ArrayList([]const u8).init(b.allocator); @@ -107,6 +114,11 @@ fn buildRun(b: *Builder, target: builtin.Arch, build_path: []const u8, iso_path: "-s", "-S", }) catch unreachable; + if (rt_test) + qemu_flags.appendSlice([][]const u8{ + "-display", + "none" + }) catch unreachable; const cmd = b.addSystemCommand(qemu_flags.toSlice()); step.dependOn(&cmd.step); } diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig index 8e5111a..2875be0 100644 --- a/src/kernel/arch/x86/gdt.zig +++ b/src/kernel/arch/x86/gdt.zig @@ -305,4 +305,5 @@ pub fn init() void { // Load the TSS arch.ltr(); + log.logInfo("Done\n"); } diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig index 63f8ce1..8b56a3c 100644 --- a/src/kernel/arch/x86/idt.zig +++ b/src/kernel/arch/x86/idt.zig @@ -122,4 +122,5 @@ pub fn closeInterruptGate(index: u8) void { pub fn init() void { log.logInfo("Init idt\n"); arch.lidt(&idt_ptr); + log.logInfo("Done\n"); } diff --git a/src/kernel/arch/x86/pit.zig b/src/kernel/arch/x86/pit.zig index 6e61b12..65abbe4 100644 --- a/src/kernel/arch/x86/pit.zig +++ b/src/kernel/arch/x86/pit.zig @@ -273,6 +273,7 @@ pub fn getFrequency() u32 { /// Initialise the PIT with a handler to IRQ 0. /// pub fn init() void { + log.logInfo("Init pit\n"); // Set up counter 0 at 1000hz in a square wave mode counting in binary const f: u32 = 10000; setupCounter(OCW_SELECT_COUNTER_0, f, OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY); @@ -281,4 +282,5 @@ pub fn init() void { // Installs 'pitHandler' to IRQ0 (pic.IRQ_PIT) irq.registerIrq(pic.IRQ_PIT, pitHandler); + log.logInfo("Done\n"); } diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index a5c507e..8c2ca7b 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -32,10 +32,11 @@ pub export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n"); arch.init(&mem_profile, &fixed_allocator.allocator); + log.logInfo("Arch init done\n"); vga.init(); tty.init(); - log.logInfo("Finished init\n"); + log.logInfo("Init done\n"); tty.print("Hello Pluto from kernel :)\n"); } } diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index d0a99a0..3ebaceb 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -644,6 +644,7 @@ pub fn init() void { printLogo(); displayPageNumber(); updateCursor(); + log.logInfo("Done\n"); } fn resetGlobals() void { diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py new file mode 100644 index 0000000..0cc05cd --- /dev/null +++ b/test/kernel/arch/x86/rt-test.py @@ -0,0 +1,6 @@ +def getTestCases(TestCase): + return [ + TestCase("GDT init", [r"Init gdt", r"Done"]), + TestCase("IDT init", [r"Init idt", r"Done"]), + TestCase("PIT init", [r"Init pit", r".+", "Done"]) + ] diff --git a/test/rt-test.py b/test/rt-test.py new file mode 100644 index 0000000..f38a79c --- /dev/null +++ b/test/rt-test.py @@ -0,0 +1,64 @@ +import subprocess +import signal +import re +import sys +import datetime +import os +import importlib.util + +class TestCase: + def __init__(self, name, expected): + self.name = name + self.expected = expected + +def test_failure(case, exp, expected_idx, found): + print("FAILURE: %s #%d, expected '%s', found '%s'" %(case.name, expected_idx + 1, exp, found)) + sys.exit(1) + +def test_pass(case, exp, expected_idx, found): + print("PASS: %s #%d, expected '%s', found '%s'" %(case.name, expected_idx + 1, exp, found)) + +if __name__ == "__main__": + arch = sys.argv[1] + zig_path = sys.argv[2] + spec = importlib.util.spec_from_file_location("arch", "test/kernel/arch/" + arch + "/rt-test.py") + arch_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(arch_module) + + # The list of log statements to look for before arch init is called + pre_archinit_cases = [ + TestCase("Arch init starts", [r"Init arch \w+"]) + ] + # The list of log statements to look for after arch init is called + post_archinit_cases = [ + TestCase("Arch init finishes", [r"Arch init done"]), + TestCase("TTY init", [r"Init tty", r"Done"]), + TestCase("Init finishes", [r"Init done"]) + ] + # All log statements to look for, including the arch-specific ones + cases = pre_archinit_cases + arch_module.getTestCases(TestCase) + post_archinit_cases + + if len(cases) > 0: + proc = subprocess.Popen(zig_path + " build run -Drt-test=true", stdout=subprocess.PIPE, shell=True) + case_idx = 0 + # Go through the cases + while case_idx < len(cases): + case = cases[case_idx] + expected_idx = 0 + # Go through the expected log messages + while expected_idx < len(case.expected): + e = "\[INFO\] " + case.expected[expected_idx] + line = proc.stdout.readline().decode("utf-8") + if not line: + break + line = line.strip() + pattern = re.compile(e) + # Pass if the line matches the expected pattern, else fail + if pattern.fullmatch(line): + test_pass(case, e, expected_idx, line) + else: + test_failure(case, e, expected_idx, line) + expected_idx += 1 + case_idx += 1 + proc.kill() + sys.exit(0)