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 | # Build dir | ||||||
| bin/* | bin/* | ||||||
|  | *.pyc | ||||||
|  | __pycache__ | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								build.zig
									
										
									
									
									
								
							
							
						
						
									
										36
									
								
								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 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 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"; |     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 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; |     b.makePath(build_path) catch unreachable; | ||||||
|     var grub_path = concat(b.allocator, build_path, "/iso/boot/grub") 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); |     b.default_step.dependOn(link_step); | ||||||
|     for (iso_step.toSlice()) |step| b.default_step.dependOn(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); |     buildDebug(b); | ||||||
|     buildTest(b, src_path); |     buildTest(b, src_path, rt_test, target, zig_path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn buildTest(b: *Builder, src_path: []const u8) void { | 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 all tests"); |     const step = b.step("test", "Run tests"); | ||||||
|     const src_path2 = concat(b.allocator, src_path, "/") catch unreachable; |     if (rt_test) { | ||||||
|     for (src_files.toSlice()) |file| { |         const script = b.addSystemCommand([][]const u8{ "python3", "test/rt-test.py", target, zig_path}); | ||||||
|         var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; |         step.dependOn(&script.step); | ||||||
|         file_src.append(".zig") catch unreachable; |     } else { | ||||||
|         const tst = b.addTest(file_src.toSlice()); |         const src_path2 = concat(b.allocator, src_path, "/") catch unreachable; | ||||||
|         tst.setMainPkgPath("."); |         for (src_files.toSlice()) |file| { | ||||||
|         step.dependOn(&tst.step); |             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); |     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 step = b.step("run", "Run with qemu"); | ||||||
|     const qemu = if (target == builtin.Arch.i386) "qemu-system-i386" else unreachable; |     const qemu = if (target == builtin.Arch.i386) "qemu-system-i386" else unreachable; | ||||||
|     var qemu_flags = ArrayList([]const u8).init(b.allocator); |     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", | ||||||
|             "-S", |             "-S", | ||||||
|         }) catch unreachable; |         }) catch unreachable; | ||||||
|  |     if (rt_test) | ||||||
|  |         qemu_flags.appendSlice([][]const u8{ | ||||||
|  |             "-display", | ||||||
|  |             "none" | ||||||
|  |         }) catch unreachable; | ||||||
|     const cmd = b.addSystemCommand(qemu_flags.toSlice()); |     const cmd = b.addSystemCommand(qemu_flags.toSlice()); | ||||||
|     step.dependOn(&cmd.step); |     step.dependOn(&cmd.step); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -305,4 +305,5 @@ pub fn init() void { | ||||||
| 
 | 
 | ||||||
|     // Load the TSS |     // Load the TSS | ||||||
|     arch.ltr(); |     arch.ltr(); | ||||||
|  |     log.logInfo("Done\n"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -122,4 +122,5 @@ pub fn closeInterruptGate(index: u8) void { | ||||||
| pub fn init() void { | pub fn init() void { | ||||||
|     log.logInfo("Init idt\n"); |     log.logInfo("Init idt\n"); | ||||||
|     arch.lidt(&idt_ptr); |     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. | /// Initialise the PIT with a handler to IRQ 0. | ||||||
| /// | /// | ||||||
| pub fn init() void { | pub fn init() void { | ||||||
|  |     log.logInfo("Init pit\n"); | ||||||
|     // Set up counter 0 at 1000hz in a square wave mode counting in binary |     // Set up counter 0 at 1000hz in a square wave mode counting in binary | ||||||
|     const f: u32 = 10000; |     const f: u32 = 10000; | ||||||
|     setupCounter(OCW_SELECT_COUNTER_0, f, OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY); |     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) |     // Installs 'pitHandler' to IRQ0 (pic.IRQ_PIT) | ||||||
|     irq.registerIrq(pic.IRQ_PIT, pitHandler); |     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"); |         log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n"); | ||||||
|         arch.init(&mem_profile, &fixed_allocator.allocator); |         arch.init(&mem_profile, &fixed_allocator.allocator); | ||||||
|  |         log.logInfo("Arch init done\n"); | ||||||
|         vga.init(); |         vga.init(); | ||||||
|         tty.init(); |         tty.init(); | ||||||
| 
 | 
 | ||||||
|         log.logInfo("Finished init\n"); |         log.logInfo("Init done\n"); | ||||||
|         tty.print("Hello Pluto from kernel :)\n"); |         tty.print("Hello Pluto from kernel :)\n"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -644,6 +644,7 @@ pub fn init() void { | ||||||
|     printLogo(); |     printLogo(); | ||||||
|     displayPageNumber(); |     displayPageNumber(); | ||||||
|     updateCursor(); |     updateCursor(); | ||||||
|  |     log.logInfo("Done\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn resetGlobals() void { | 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