Add runtime testing harness
This commit is contained in:
		
							parent
							
								
									f8f7e40535
								
							
						
					
					
						commit
						35cfbd1686
					
				
					 9 changed files with 103 additions and 13 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -43,3 +43,5 @@ zig-cache | |||
| 
 | ||||
| # Build dir | ||||
| bin/* | ||||
| *.pyc | ||||
| __pycache__ | ||||
|  |  | |||
							
								
								
									
										22
									
								
								build.zig
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								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,13 +62,17 @@ 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"); | ||||
| 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; | ||||
|  | @ -75,6 +81,7 @@ fn buildTest(b: *Builder, src_path: []const u8) void { | |||
|             tst.setMainPkgPath("."); | ||||
|             step.dependOn(&tst.step); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn buildDebug(b: *Builder) void { | ||||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -305,4 +305,5 @@ pub fn init() void { | |||
| 
 | ||||
|     // Load the TSS | ||||
|     arch.ltr(); | ||||
|     log.logInfo("Done\n"); | ||||
| } | ||||
|  |  | |||
|  | @ -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"); | ||||
| } | ||||
|  |  | |||
|  | @ -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"); | ||||
| } | ||||
|  |  | |||
|  | @ -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"); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -644,6 +644,7 @@ pub fn init() void { | |||
|     printLogo(); | ||||
|     displayPageNumber(); | ||||
|     updateCursor(); | ||||
|     log.logInfo("Done\n"); | ||||
| } | ||||
| 
 | ||||
| fn resetGlobals() void { | ||||
|  |  | |||
							
								
								
									
										6
									
								
								test/kernel/arch/x86/rt-test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/kernel/arch/x86/rt-test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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"]) | ||||
|         ] | ||||
							
								
								
									
										64
									
								
								test/rt-test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								test/rt-test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sam Tebbs
						Sam Tebbs