diff --git a/README.md b/README.md index 1a5ce71..795bc17 100644 --- a/README.md +++ b/README.md @@ -7,48 +7,59 @@ Pluto is a kernel written almost entirely in [Zig](https://github.com/ziglang/zi ![Hello image](hello.jpg) ## Goals + * **Should be written in Zig as much as possible**. Assembly should only be used where required for functionality or performance reasons. * **Light and performant**. The kernel should be usable both on embedded and desktop class CPUs, made possible by it being lightweight and modular. -* **Basic utilities will be written in Zig**. This includes a basic text editor and shell, and will be part of the filsystem external to the kernel itself. -* **Easy to port**. The kernel is oblivous to the underlying architecture, meaning that ports only need to implement the defined interface and they should work without a hitch. +* **Basic utilities will be written in Zig**. This includes a basic text editor and shell, and will be part of the filesystem external to the kernel itself. +* **Easy to port**. The kernel is oblivious to the underlying architecture, meaning that ports only need to implement the defined interface and they should work without a hitch. All of these goals will benefit from the features of Zig. ## Build + Requires a master build of Zig ([downloaded](https://ziglang.org/download) or [built from source](https://github.com/ziglang/zig#building-from-source)) *xorriso* and the grub tools (such as *grub-mkrescue*). A gdb binary compatible with your chosen target is required to run the kernel (e.g. *qemu-system-i386*). -``` + +```Shell zig build ``` ## Run -``` + +```Shell zig build run ``` ## Debug + Launch a gdb instance and connect to qemu. -``` + +```Shell zig build debug ``` ## Test -Run the unitests or runtime tests. -``` + +Run the unit tests or runtime tests. + +```Shell zig build test ``` ## Options + * `-Ddebug=`: Boolean (default `false`). - * **build**: Build with debug info included or stripped (see #70 for planned changes). - * **run**: Wait for a gdb connection before executing. + * **build**: Build with debug info included or stripped (see #70 for planned changes). + * **run**: Wait for a gdb connection before executing. * `-Drt-test=`: Boolean (default `false`). - * **build**: Build with runtime testing enabled. Makes the kernel bigger and slower but tests important functionality. - * **test**: Run the runtime testing script instead of the unittests. Checks for the expected log statements and fails if any are missing. + * **build**: Build with runtime testing enabled. Makes the kernel bigger and slower but tests important functionality. + * **test**: Run the runtime testing script instead of the unittests. Checks for the expected log statements and fails if any are missing. ## Contribution + We welcome all contributions, be it bug reports, feature suggestions or pull requests. We follow the style mandated by zig fmt so make sure you've run `zig fmt` on your code before submitting it. We also like to order a file's members (public after non-public): + 1. imports 2. type definitions 3. constants diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2ce5eba..e98b03e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,15 +27,14 @@ steps: - script: zig*/zig build displayName: 'Build kernel' -# Uncomment once mock testing is finished -#- script: zig*/zig build test -# displayName: 'Mocked tests' +- script: zig*/zig build test + displayName: 'Unit tests' - script: | sudo apt-get update sudo apt-get install qemu qemu-system --fix-missing displayName: 'Download qemu' - + - script: | zig*/zig build test -Drt-test=true displayName: 'Runtime tests' diff --git a/build.zig b/build.zig index 435fc96..41e3375 100644 --- a/build.zig +++ b/build.zig @@ -4,6 +4,7 @@ const Builder = std.build.Builder; const Step = std.build.Step; const Target = std.build.Target; const fs = std.fs; +const Mode = builtin.Mode; pub fn build(b: *Builder) !void { const target = Target{ @@ -13,12 +14,19 @@ pub fn build(b: *Builder) !void { .abi = .gnu, }, }; + + const target_str = switch (target.getArch()) { + builtin.Arch.i386 => "x86", + else => unreachable, + }; const debug = b.option(bool, "debug", "build with debug symbols / make qemu wait for a debug connection") orelse false; const rt_test = b.option(bool, "rt-test", "enable/disable runtime testing") orelse false; const main_src = "src/kernel/kmain.zig"; const exec = b.addExecutable("pluto", main_src); exec.setMainPkgPath("."); + const const_path = try fs.path.join(b.allocator, [_][]const u8{ "src/kernel/arch/", target_str, "/constants.zig" }); + exec.addPackagePath("constants", const_path); exec.addBuildOption(bool, "rt_test", rt_test); exec.setLinkerScriptPath("link.ld"); exec.setTheTarget(target); @@ -72,10 +80,17 @@ pub fn build(b: *Builder) !void { const script = b.addSystemCommand([_][]const u8{ "python3", "test/rt-test.py", "x86", b.zig_exe }); test_step.dependOn(&script.step); } else { - const unit_tests = b.addTest(main_src); - unit_tests.setMainPkgPath("."); - unit_tests.addBuildOption(bool, "rt_test", rt_test); - test_step.dependOn(&unit_tests.step); + inline for ([_]Mode{ Mode.Debug, Mode.ReleaseFast, Mode.ReleaseSafe, Mode.ReleaseSmall }) |test_mode| { + const mode_str = comptime modeToString(test_mode); + const unit_tests = b.addTest("test/unittests/test_all.zig"); + unit_tests.setBuildMode(test_mode); + unit_tests.setMainPkgPath("."); + unit_tests.setNamePrefix(mode_str ++ " - "); + unit_tests.addPackagePath("mocking", "test/mock/kernel/mocking.zig"); + unit_tests.addPackagePath("constants", const_path); + unit_tests.addBuildOption(bool, "rt_test", rt_test); + test_step.dependOn(&unit_tests.step); + } } const debug_step = b.step("debug", "Debug with gdb"); @@ -91,3 +106,12 @@ pub fn build(b: *Builder) !void { }); debug_step.dependOn(&debug_cmd.step); } + +fn modeToString(comptime mode: Mode) []const u8 { + return switch (mode) { + Mode.Debug => "debug", + Mode.ReleaseFast => "release-fast", + Mode.ReleaseSafe => "release-safe", + Mode.ReleaseSmall => "release-small", + }; +} diff --git a/src/kernel/arch.zig b/src/kernel/arch.zig index 316f47b..d8d6781 100644 --- a/src/kernel/arch.zig +++ b/src/kernel/arch.zig @@ -1,6 +1,6 @@ const builtin = @import("builtin"); -pub const internals = switch (builtin.arch) { +pub const internals = if (builtin.is_test) @import("mocking").arch else switch (builtin.arch) { builtin.Arch.i386 => @import("arch/x86/arch.zig"), else => unreachable, }; diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index c0583e8..a4e3eab 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -1,5 +1,6 @@ // Zig version: 0.4.0 +const std = @import("std"); const builtin = @import("builtin"); const gdt = @import("gdt.zig"); const idt = @import("idt.zig"); @@ -7,6 +8,8 @@ const irq = @import("irq.zig"); const isr = @import("isr.zig"); const log = @import("../../log.zig"); const pit = @import("pit.zig"); +const paging = @import("paging.zig"); +const MemProfile = @import("../../mem.zig").MemProfile; const syscalls = @import("syscalls.zig"); pub const InterruptContext = struct { @@ -39,9 +42,6 @@ pub const InterruptContext = struct { user_esp: u32, ss: u32, }; -const paging = @import("paging.zig"); -const std = @import("std"); -const MemProfile = @import("../../mem.zig").MemProfile; /// /// Initialise the architecture diff --git a/src/kernel/arch/x86/boot.zig b/src/kernel/arch/x86/boot.zig index a2f9576..c0d61de 100644 --- a/src/kernel/arch/x86/boot.zig +++ b/src/kernel/arch/x86/boot.zig @@ -1,4 +1,4 @@ -const constants = @import("constants.zig"); +const constants = @import("constants"); const ALIGN = 1 << 0; const MEMINFO = 1 << 1; diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index 150d972..8e994e3 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -7,7 +7,8 @@ const MemProfile = @import("../../mem.zig").MemProfile; const testing = @import("std").testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -const constants = @import("constants.zig"); + +extern var KERNEL_ADDR_OFFSET: *u32; const ENTRIES_PER_DIRECTORY = 1024; @@ -34,7 +35,7 @@ const ENTRY_AVAILABLE = 0xE00; const ENTRY_PAGE_ADDR = 0xFFC00000; const Directory = packed struct { - entries: [ENTRIES_PER_DIRECTORY]DirectoryEntry + entries: [ENTRIES_PER_DIRECTORY]DirectoryEntry, }; const PagingError = error { @@ -42,7 +43,7 @@ const PagingError = error { InvalidVirtAddresses, PhysicalVirtualMismatch, UnalignedPhysAddresses, - UnalignedVirtAddresses + UnalignedVirtAddresses, }; /// @@ -140,7 +141,7 @@ pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator) void @memset(@ptrCast([*]u8, kernel_directory), 0, @sizeOf(Directory)); mapDir(kernel_directory, p_start, p_end, v_start, v_end, allocator) catch unreachable; - const dir_physaddr = @ptrToInt(kernel_directory) - constants.KERNEL_ADDR_OFFSET; + const dir_physaddr = @ptrToInt(kernel_directory) - @ptrToInt(&KERNEL_ADDR_OFFSET); asm volatile ("mov %[addr], %%cr3" :: [addr] "{eax}" (dir_physaddr)); isr.registerIsr(14, pageFault) catch unreachable; } diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 9d05d46..2034986 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -2,13 +2,13 @@ const std = @import("std"); const builtin = @import("builtin"); -const arch = if (builtin.is_test) @import("../../test/kernel/arch_mock.zig") else @import("arch.zig").internals; +const arch = @import("arch.zig").internals; const multiboot = @import("multiboot.zig"); const tty = @import("tty.zig"); const vga = @import("vga.zig"); const log = @import("log.zig"); const serial = @import("serial.zig"); -const mem = @import("mem.zig"); +const mem = if (builtin.is_test) @import("mocking").mem else @import("mem.zig"); const options = @import("build_options"); comptime { @@ -18,9 +18,13 @@ comptime { } } +// This is for unit testing as we need to export KERNEL_ADDR_OFFSET as it is no longer available +// from the linker script +export var KERNEL_ADDR_OFFSET: u32 = if (builtin.is_test) 0xC0000000 else undefined; + // Need to import this as we need the panic to be in the root source file, or zig will just use the // builtin panic and just loop, which is what we don't want -const panic_root = @import("panic.zig"); +const panic_root = if (builtin.is_test) @import("mocking").panic else @import("panic.zig"); // Just call the panic function, as this need to be in the root source file pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { diff --git a/src/kernel/log.zig b/src/kernel/log.zig index 05f4902..8db6b7c 100644 --- a/src/kernel/log.zig +++ b/src/kernel/log.zig @@ -5,7 +5,7 @@ pub const Level = enum { INFO, DEBUG, WARNING, - ERROR + ERROR, }; fn logCallback(context: void, str: []const u8) anyerror!void { @@ -23,9 +23,11 @@ pub fn logInfo(comptime format: []const u8, args: ...) void { pub fn logDebug(comptime format: []const u8, args: ...) void { log(Level.DEBUG, format, args); } + pub fn logWarning(comptime format: []const u8, args: ...) void { log(Level.WARNING, format, args); } + pub fn logError(comptime format: []const u8, args: ...) void { log(Level.ERROR, format, args); } diff --git a/src/kernel/mem.zig b/src/kernel/mem.zig index 08637c4..ccc1504 100644 --- a/src/kernel/mem.zig +++ b/src/kernel/mem.zig @@ -6,7 +6,7 @@ pub const MemProfile = struct { physaddr_end: [*]u8, physaddr_start: [*]u8, mem_kb: u32, - fixed_alloc_size: u32 + fixed_alloc_size: u32, }; // The virtual/physical start/end of the kernel code diff --git a/src/kernel/panic.zig b/src/kernel/panic.zig index 5dcd7e9..8ba90fa 100644 --- a/src/kernel/panic.zig +++ b/src/kernel/panic.zig @@ -2,7 +2,7 @@ const builtin = @import("builtin"); const tty = @import("tty.zig"); -const arch = if (builtin.is_test) @import("../../test/kernel/arch_mock.zig") else @import("arch.zig").internals; +const arch = @import("arch.zig").internals; pub fn panicFmt(trace: ?*builtin.StackTrace, comptime format: []const u8, args: ...) noreturn { @setCold(true); diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index f11c842..7f127be 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -1,13 +1,13 @@ -// Zig version: 0.4.0 +const is_test = @import("builtin").is_test; -const vga = @import("vga.zig"); -const log = @import("log.zig"); +const std = @import("std"); +const fmt = std.fmt; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; -const expectEqual = @import("std").testing.expectEqual; -const expectError = @import("std").testing.expectError; -const expect = @import("std").testing.expect; -const warn = @import("std").debug.warn; -const fmt = @import("std").fmt; +const vga = if (is_test) @import("mocking").vga else @import("vga.zig"); +const log = if (is_test) @import("mocking").log else @import("log.zig"); /// The number of rows down from the top (row 0) where the displayable region starts. Above is /// where the logo and time is printed @@ -29,8 +29,12 @@ const START_OF_DISPLAYABLE_REGION: u16 = vga.WIDTH * ROW_MIN; /// The total number of VGA elements (or characters) the video buffer can display const VIDEO_BUFFER_SIZE: u16 = vga.WIDTH * vga.HEIGHT; +/// The error set for if there is an error whiles printing. const PrintError = error{OutOfBounds}; +/// The location of the kernel in virtual memory so can calculate the address of the VGA buffer +extern var KERNEL_ADDR_OFFSET: *u32; + /// The current x position of the cursor. var column: u8 = 0; @@ -49,7 +53,6 @@ var blank: u16 = undefined; /// A total of TOTAL_NUM_PAGES pages that can be saved and restored to from and to the video buffer var pages: [TOTAL_NUM_PAGES][TOTAL_CHAR_ON_PAGE]u16 = init: { - //@setEvalBranchQuota(TOTAL_NUM_PAGES * TOTAL_CHAR_ON_PAGE + TOTAL_CHAR_ON_PAGE); var p: [TOTAL_NUM_PAGES][TOTAL_CHAR_ON_PAGE]u16 = undefined; for (p) |*page| { @@ -65,9 +68,6 @@ var page_index: u8 = 0; /// /// Copies data into the video buffer. This is used for copying a page into the video buffer. /// -/// Global changes: -/// video_buffer -/// /// Arguments: /// IN video_buf_offset: u16 - The offset into the video buffer to start copying to. /// IN data: []const u16 - The data to copy into the video buffer. @@ -130,9 +130,6 @@ fn pageMove(dest: []u16, src: []u16, size: usize) PrintError!void { /// /// Clears a region of the video buffer to a VGA entry from the beginning. /// -/// Global changes: -/// video_buffer -/// /// Arguments: /// IN c: u16 - VGA entry to set the video buffer to. /// IN size: u16 - The number to VGA entries to set from the beginning of the video buffer. @@ -153,10 +150,6 @@ fn updateCursor() void { /// /// Get the hardware cursor to set the current column and row (x, y). /// -/// Global changes: -/// column -/// row -/// fn getCursor() void { var cursor: u16 = vga.getCursor(); @@ -167,10 +160,6 @@ fn getCursor() void { /// /// Display the current page number at the bottom right corner. /// -/// Global changes: -/// video_buffer -/// pages -/// fn displayPageNumber() void { const column_temp: u8 = column; const row_temp: u8 = row; @@ -190,11 +179,6 @@ fn displayPageNumber() void { /// Put a character at a specific column and row position on the screen. This will use the current /// colour. /// -/// Global changes: -/// video_buffer -/// pages -/// page_index -/// /// Arguments: /// IN char: u8 - The character to print. This will be combined with the current colour. /// IN x: u8 - The x position (column) to put the character at. @@ -234,9 +218,6 @@ fn putEntryAt(char: u8, x: u8, y: u8) PrintError!void { /// Move rows up pages across multiple pages leaving the last rows blank. /// -/// Global changes: -/// pages -/// /// Arguments: /// IN rows: u16 - The number of rows to move up. /// @@ -274,11 +255,6 @@ fn pagesMoveRowsUp(rows: u16) PrintError!void { /// When the text/terminal gets to the bottom of the screen, then move all line up by the amount /// that are below the bottom of the screen. Usually moves up by one line. /// -/// Global changes: -/// pages -/// video_buffer -/// row -/// fn scroll() void { // Added the condition in the if from pagesMoveRowsUp as don't need to move all rows if (row >= vga.HEIGHT and (row - vga.HEIGHT + 1) <= ROW_TOTAL) { @@ -309,12 +285,6 @@ fn scroll() void { /// update the cursor once. This will also print the special characters: \n, \r, \t and \b. (\b is /// not a valid character so use \x08 which is the hex value). /// -/// Global changes: -/// pages -/// video_buffer -/// row -/// column -/// /// Arguments: /// IN char: u8 - The character to print. /// @@ -374,12 +344,6 @@ fn putChar(char: u8) PrintError!void { /// /// Print a string to the TTY. This also updates to hardware cursor. /// -/// Global changes: -/// video_buffer -/// pages -/// column -/// row -/// /// Arguments: /// IN str: []const u8 - The string to print. /// @@ -397,12 +361,6 @@ fn writeString(str: []const u8) PrintError!void { /// /// A call back function for use in the formation of a string. This calls writeString normally. /// -/// Global changes: -/// video_buffer -/// pages -/// column -/// row -/// /// Arguments: /// IN context: void - The context of the printing. This will be empty. /// IN string: []const u8 - The string to print. @@ -418,9 +376,6 @@ fn printCallback(context: void, string: []const u8) PrintError!void { /// Print a character without updating the cursor. For speed when printing a string as only need to /// update the cursor once. This will also print the special characters: \n, \r, \t and \b /// -/// Global changes: -/// video_buffer -/// /// Arguments: /// IN char: u8 - The character to print. /// @@ -446,12 +401,6 @@ fn printLogo() void { /// Print a formatted string to the terminal in the current colour. This used the standard zig /// formatting. /// -/// Global changes: -/// pages -/// video_buffer -/// column -/// row -/// /// Arguments: /// IN format: []const u8 - The format string to print /// IN args: ... - The arguments to be used in the formatted string @@ -465,11 +414,6 @@ pub fn print(comptime format: []const u8, args: ...) void { /// Move up a page. This will copy the page above to the video buffer. Will keep trace of which /// page is being displayed. /// -/// Global changes: -/// video_buffer -/// pages -/// page_index -/// pub fn pageUp() void { if (page_index < TOTAL_NUM_PAGES - 1) { // Copy page to display @@ -484,11 +428,6 @@ pub fn pageUp() void { /// Move down a page. This will copy the page bellow to the video buffer. Will keep trace of which /// page is being displayed. /// -/// Global changes: -/// video_buffer -/// pages -/// page_index -/// pub fn pageDown() void { if (page_index > 0) { // Copy page to display @@ -508,9 +447,6 @@ pub fn pageDown() void { /// This clears the entire screen with blanks using the current colour. This will also save the /// screen to the pages so can scroll back down. /// -/// Global changes: -/// video_buffer -/// pub fn clearScreen() void { // Move all the rows up //row = ROW_MIN - 1 + vga.HEIGHT; // 42 @@ -529,10 +465,6 @@ pub fn clearScreen() void { /// /// This moves the software and hardware cursor to the left by one. /// -/// Global changes: -/// column -/// row -/// pub fn moveCursorLeft() void { if (column == 0) { if (row != 0) { @@ -548,10 +480,6 @@ pub fn moveCursorLeft() void { /// /// This moves the software and hardware cursor to the right by one. /// -/// Global changes: -/// column -/// row -/// pub fn moveCursorRight() void { if (column == (vga.WIDTH - 1)) { if (row != (vga.HEIGHT - 1)) { @@ -568,10 +496,6 @@ pub fn moveCursorRight() void { /// This will set a new colour for the screen. It will only become effective when printing new /// characters. Use vga.colourEntry and the colour enums to set the colour. /// -/// Global changes: -/// colour -/// blank -/// /// Arguments: /// new_colour: u8 - The new foreground and background colour of the screen. /// @@ -580,23 +504,23 @@ pub fn setColour(new_colour: u8) void { blank = vga.entry(0, colour); } +fn getVideoBufferAddress() usize { + return @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000; +} + /// /// Initialise the tty. This will keep the bootloaders output and set the software cursor to where /// the bootloader left it. Will copy the current screen to the pages, set the colour and blank /// entry, print the logo and display the 0'th page. /// -/// Global changes: -/// pages -/// video_buffer -/// column -/// row -/// colour -/// blank -/// pub fn init() void { log.logInfo("Init tty\n"); // Video buffer in higher half - video_buffer = @intToPtr([*]volatile u16, 0xC00B8000)[0..VIDEO_BUFFER_SIZE]; + if (is_test) { + video_buffer = @intToPtr([*]volatile u16, mock_getVideoBufferAddress())[0..VIDEO_BUFFER_SIZE]; + } else { + video_buffer = @intToPtr([*]volatile u16, getVideoBufferAddress())[0..VIDEO_BUFFER_SIZE]; + } setColour(vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK)); // Enable and get the hardware cursor to set the software cursor @@ -611,14 +535,14 @@ pub fn init() void { row_offset = u16(ROW_MIN - (vga.HEIGHT - 1 - row)); } - // Make a copy into the terminal_pages + // Make a copy into the pages // Assuming that there is only one page var i: u16 = 0; while (i < row * vga.WIDTH) : (i += 1) { pages[0][i] = video_buffer[i]; } - // Move terminal_row rows down 7 + // Move 7 rows down i = 0; if (@ptrToInt(&video_buffer[ROW_MIN * vga.WIDTH]) < @ptrToInt(&video_buffer[row_offset * vga.WIDTH])) { while (i != row * vga.WIDTH) : (i += 1) { @@ -638,6 +562,8 @@ pub fn init() void { } else { // Clear the screen setVideoBuffer(blank, VIDEO_BUFFER_SIZE); + // Set the row to below the logo + row = ROW_MIN; } printLogo(); @@ -646,36 +572,40 @@ pub fn init() void { log.logInfo("Done\n"); } +const test_colour: u8 = u8(vga.COLOUR_LIGHT_GREY) | u8(vga.COLOUR_BLACK) << 4; +var test_video_buffer = [_]u16{0} ** VIDEO_BUFFER_SIZE; + +fn mock_getVideoBufferAddress() usize { + return @ptrToInt(&test_video_buffer); +} + fn resetGlobals() void { column = 0; row = 0; page_index = 0; colour = undefined; - video_buffer = @intToPtr([*]volatile u16, 0xB8000)[0..VIDEO_BUFFER_SIZE]; + video_buffer = undefined; blank = undefined; pages = init: { var p: [TOTAL_NUM_PAGES][TOTAL_CHAR_ON_PAGE]u16 = undefined; for (p) |*page| { - page.* = []u16{0} ** TOTAL_CHAR_ON_PAGE; + page.* = [_]u16{0} ** TOTAL_CHAR_ON_PAGE; } break :init p; }; } -const test_colour: u8 = vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK); -var test_video_buffer = []volatile u16{0} ** VIDEO_BUFFER_SIZE; - fn _setVideoBuffer() void { // Change to a stack location video_buffer = test_video_buffer[0..VIDEO_BUFFER_SIZE]; expectEqual(@ptrToInt(video_buffer.ptr), @ptrToInt(&test_video_buffer[0])); - expect(@typeOf(video_buffer) == []volatile u16); - setColour(test_colour); + colour = test_colour; + blank = vga.mock_entry(0, test_colour); // Set pages to blank var i: u16 = 0; @@ -714,7 +644,7 @@ fn setPagesIncrementing() void { fn defaultVariablesTesting(p_i: u8, r: u8, c: u8) void { expectEqual(test_colour, colour); - expectEqual(vga.entry(0, test_colour), blank); + expectEqual(u16(test_colour) << 8, blank); expectEqual(p_i, page_index); expectEqual(r, row); expectEqual(c, column); @@ -749,7 +679,7 @@ fn incrementingVideoBufferTesting() void { fn defaultVideoBufferTesting() void { for (video_buffer) |b| { - expectEqual(vga.entry(0, test_colour), b); + expectEqual(vga.mock_entry(0, test_colour), b); } } @@ -761,47 +691,93 @@ fn defaultAllTesting(p_i: u8, r: u8, c: u8) void { test "updateCursor" { // Set up - // setVideoBufferBlankPages(); + + // Mocking out the vga.updateCursor call for updating the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("updateCursor", u16(0), u16(0)); + // Pre testing - // - defaultAllTesting(0, 0, 0); + // Call function - // - updateCursor(); + // Post test - // - defaultAllTesting(0, 0, 0); - // Tear down - // + // Tear down resetGlobals(); } -test "getCursor all" { - warn(" Waiting for mocking "); +test "getCursor zero" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.getCursor call for getting the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", u16(0)); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + getCursor(); + + // Post test + defaultAllTesting(0, 0, 0); + + // Tear down + resetGlobals(); +} + +test "getCursor EEF" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.getCursor call for getting the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", u16(0x0EEF)); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + getCursor(); + + // Post test + defaultAllTesting(0, 47, 63); + + // Tear down + resetGlobals(); } test "displayPageNumber column and row is reset" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // - column = 5; row = 6; + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing defaultAllTesting(0, 6, 5); + // Call function - // - displayPageNumber(); - // Post test - // + // Post test defaultVariablesTesting(0, 6, 5); const text: [11]u8 = "Page 0 of 4"; @@ -816,91 +792,93 @@ test "displayPageNumber column and row is reset" { expectEqual(blank, b); } } - // Tear down - // + // Tear down resetGlobals(); } test "putEntryAt out of bounds" { // Set up - // setVideoBufferBlankPages(); + // Pre testing - // - defaultAllTesting(0, 0, 0); + // Call function - // - expectError(PrintError.OutOfBounds, putEntryAt('A', 100, 100)); + // Post test - // - defaultAllTesting(0, 0, 0); - // Tear down (resets globals) - // + // Tear down resetGlobals(); } test "putEntryAt not in displayable region" { // Set up - // setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Enable and update cursor is only called once, can can use the consume function call + //vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + // Pre testing - // - defaultAllTesting(0, 0, 0); - // Call function - // + // Call function const x: u8 = 0; const y: u8 = 0; const char: u8 = 'A'; putEntryAt(char, x, y) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); blankPagesTesting(); for (video_buffer) |b, i| { if (i == y * vga.WIDTH + x) { - expectEqual(vga.entry(char, test_colour), b); + expectEqual(vga.mock_entry(char, test_colour), b); } else { - expectEqual(vga.entry(0, test_colour), b); + expectEqual(vga.mock_entry(0, test_colour), b); } } - // Tear down (resets globals) - // + // Tear down resetGlobals(); } test "putEntryAt in displayable region page_index is 0" { // Set up - // setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + // Pre testing - // - defaultAllTesting(0, 0, 0); - // Call function - // + // Call function const x: u16 = 0; const y: u16 = ROW_MIN; const char: u8 = 'A'; putEntryAt(char, x, y) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); for (pages) |page, i| { for (page) |c, j| { if ((i == page_index) and (j == (y * vga.WIDTH + x) - START_OF_DISPLAYABLE_REGION)) { - expectEqual(vga.entry(char, test_colour), c); + expectEqual(vga.mock_entry(char, test_colour), c); } else { expectEqual(blank, c); } @@ -909,24 +887,32 @@ test "putEntryAt in displayable region page_index is 0" { for (video_buffer) |b, i| { if (i == y * vga.WIDTH + x) { - expectEqual(vga.entry(char, test_colour), b); + expectEqual(vga.mock_entry(char, test_colour), b); } else { - expectEqual(vga.entry(0, test_colour), b); + expectEqual(vga.mock_entry(0, test_colour), b); } } - // Tear down (resets globals) - // + // Tear down resetGlobals(); } test "putEntryAt in displayable region page_index is not 0" { // Set up - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Enable and update cursor is only called once, can can use the consume function call + vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + setVideoBufferBlankPages(); // Fill the 1'nd page (index 1) will all 1's - const ones: u16 = vga.entry('1', test_colour); + const ones: u16 = vga.mock_entry('1', test_colour); for (pages) |*page, i| { for (page) |*char| { if (i == 0) { @@ -938,9 +924,8 @@ test "putEntryAt in displayable region page_index is not 0" { } page_index = 1; - // Pre testing - // + // Pre testing defaultVariablesTesting(1, 0, 0); defaultVideoBufferTesting(); @@ -953,16 +938,14 @@ test "putEntryAt in displayable region page_index is not 0" { } } } - // Call function - // + // Call function const x: u16 = 0; const y: u16 = ROW_MIN; const char: u8 = 'A'; putEntryAt(char, x, y) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); const text: []const u8 = "Page 0 of 4"; @@ -970,7 +953,7 @@ test "putEntryAt in displayable region page_index is not 0" { for (pages) |page, i| { for (page) |c, j| { if (i == 0 and j == 0) { - expectEqual(vga.entry(char, test_colour), c); + expectEqual(vga.mock_entry(char, test_colour), c); } else if (i == 0) { expectEqual(ones, c); } else { @@ -984,94 +967,79 @@ test "putEntryAt in displayable region page_index is not 0" { if (i < START_OF_DISPLAYABLE_REGION - 11) { expectEqual(blank, b); } else if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + expectEqual(vga.mock_entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); } else if (i == y * vga.WIDTH + x) { - expectEqual(vga.entry(char, test_colour), b); + expectEqual(vga.mock_entry(char, test_colour), b); } else { expectEqual(ones, b); } } - // Tear down - // + // Tear down resetGlobals(); } test "pagesMoveRowsUp out of bounds" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Pre testing defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Call function - // + // Call function const rows_to_move: u16 = ROW_TOTAL + 1; expectError(PrintError.OutOfBounds, pagesMoveRowsUp(rows_to_move)); - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Tear down - // + // Tear down resetGlobals(); } test "pagesMoveRowsUp 0 rows" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Pre testing defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Call function - // + // Call function const rows_to_move: u16 = 0; pagesMoveRowsUp(rows_to_move) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Tear down - // + // Tear down resetGlobals(); } test "pagesMoveRowsUp 1 rows" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Pre testing defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Call function - // + // Call function const rows_to_move: u16 = 1; pagesMoveRowsUp(rows_to_move) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); @@ -1093,31 +1061,26 @@ test "pagesMoveRowsUp 1 rows" { } } } - // Tear down - // + // Tear down resetGlobals(); } test "pagesMoveRowsUp ROW_TOTAL - 1 rows" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Pre testing defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Call function - // + // Call function const rows_to_move: u16 = ROW_TOTAL - 1; pagesMoveRowsUp(rows_to_move) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); @@ -1139,31 +1102,26 @@ test "pagesMoveRowsUp ROW_TOTAL - 1 rows" { } } } - // Tear down - // + // Tear down resetGlobals(); } test "pagesMoveRowsUp ROW_TOTAL rows" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Pre testing defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Call function - // + // Call function const rows_to_move: u16 = ROW_TOTAL; pagesMoveRowsUp(rows_to_move) catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); @@ -1179,61 +1137,51 @@ test "pagesMoveRowsUp ROW_TOTAL rows" { } } } - // Tear down - // + // Tear down resetGlobals(); } test "scroll row is less then max height" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); + // Pre testing - // - defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); + // Call function - // - scroll(); - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); defaultVideoBufferTesting(); incrementingPagesTesting(); - // Tear down - // + // Tear down resetGlobals(); } test "scroll row is equal to height" { // Set up - // setVideoBufferIncrementingBlankPages(); setPagesIncrementing(); const row_test: u16 = vga.HEIGHT; row = row_test; - // Pre testing - // + // Pre testing defaultVariablesTesting(0, row_test, 0); incrementingPagesTesting(); incrementingVideoBufferTesting(); - // Call function - // + // Call function // Rows move up one scroll(); - // Post test - // + // Post test defaultVariablesTesting(0, vga.HEIGHT - 1, 0); var i: u16 = 0; @@ -1265,34 +1213,29 @@ test "scroll row is equal to height" { expectEqual(k + to_add, video_buffer[k]); } } - // Tear down - // + // Tear down resetGlobals(); } test "scroll row is more than height" { // Set up - // setVideoBufferIncrementingBlankPages(); setPagesIncrementing(); const row_test: u16 = vga.HEIGHT + 5; row = row_test; - // Pre testing - // + // Pre testing defaultVariablesTesting(0, row_test, 0); incrementingPagesTesting(); incrementingVideoBufferTesting(); - // Call function - // + // Call function // Rows move up 5 scroll(); - // Post test - // + // Post test defaultVariablesTesting(0, vga.HEIGHT - 1, 0); var i: u16 = 0; @@ -1324,272 +1267,229 @@ test "scroll row is more than height" { expectEqual(k + to_add, video_buffer[k]); } } - // Tear down - // + // Tear down resetGlobals(); } test "putChar new line within screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = 5; row = 5; defaultAllTesting(0, 5, 5); + // Call function - // - putChar('\n') catch unreachable; + // Post test - // - defaultAllTesting(0, 6, 0); - // Tear down - // + // Tear down resetGlobals(); } test "putChar new line outside screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = 5; row = vga.HEIGHT - 1; defaultAllTesting(0, vga.HEIGHT - 1, 5); + // Call function - // - putChar('\n') catch unreachable; + // Post test - // - defaultAllTesting(0, vga.HEIGHT - 1, 0); - // Tear down - // + // Tear down resetGlobals(); } test "putChar tab within line" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = 5; row = 6; defaultAllTesting(0, 6, 5); + // Call function - // - putChar('\t') catch unreachable; + // Post test - // - defaultAllTesting(0, 6, 9); - // Tear down - // + // Tear down resetGlobals(); } test "putChar tab end of line" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = vga.WIDTH - 1; row = 6; defaultAllTesting(0, 6, vga.WIDTH - 1); + // Call function - // - putChar('\t') catch unreachable; + // Post test - // - defaultAllTesting(0, 7, 3); - // Tear down - // + // Tear down resetGlobals(); } test "putChar tab end of screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = vga.WIDTH - 1; row = vga.HEIGHT - 1; defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + // Call function - // - putChar('\t') catch unreachable; + // Post test - // - defaultAllTesting(0, vga.HEIGHT - 1, 3); - // Tear down - // + // Tear down resetGlobals(); } test "putChar line feed" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = vga.WIDTH - 1; row = vga.HEIGHT - 1; defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + // Call function - // - putChar('\r') catch unreachable; + // Post test - // - defaultAllTesting(0, vga.HEIGHT - 1, 0); - // Tear down - // + // Tear down resetGlobals(); } test "putChar back char top left of screen" { // Set up - // setVideoBufferBlankPages(); + // Pre testing - // - defaultAllTesting(0, 0, 0); + // Call function - // - putChar('\x08') catch unreachable; + // Post test - // - defaultAllTesting(0, 0, 0); - // Tear down - // + // Tear down resetGlobals(); } test "putChar back char top row" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing column = 8; defaultAllTesting(0, 0, 8); + // Call function - // - putChar('\x08') catch unreachable; + // Post test - // - defaultAllTesting(0, 0, 7); - // Tear down - // + // Tear down resetGlobals(); } test "putChar back char beginning of row" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Pre testing row = 1; defaultAllTesting(0, 1, 0); + // Call function - // - putChar('\x08') catch unreachable; + // Post test - // - defaultAllTesting(0, 0, vga.WIDTH - 1); - // Tear down - // + // Tear down resetGlobals(); } test "putChar any char in row" { // Set up - // setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + // Pre testing - // - defaultAllTesting(0, 0, 0); + // Call function - // - putChar('A') catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 0, 1); blankPagesTesting(); var k: u16 = 0; while (k < VIDEO_BUFFER_SIZE) : (k += 1) { if (k == 0) { - expectEqual(vga.entry('A', colour), video_buffer[k]); + expectEqual(vga.mock_entry('A', colour), video_buffer[k]); } else { expectEqual(blank, video_buffer[k]); } } - // Tear down - // + // Tear down resetGlobals(); } test "putChar any char end of row" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + + // Pre testing column = vga.WIDTH - 1; defaultAllTesting(0, 0, vga.WIDTH - 1); + // Call function - // - putChar('A') catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, 1, 0); blankPagesTesting(); @@ -1601,29 +1501,30 @@ test "putChar any char end of row" { expectEqual(blank, video_buffer[k]); } } - // Tear down - // + // Tear down resetGlobals(); } test "putChar any char end of screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + + // Pre testing row = vga.HEIGHT - 1; column = vga.WIDTH - 1; defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + // Call function - // - putChar('A') catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, vga.HEIGHT - 1, 0); for (pages) |page, i| { for (page) |c, j| { @@ -1643,30 +1544,32 @@ test "putChar any char end of screen" { expectEqual(blank, video_buffer[k]); } } - // Tear down - // + // Tear down resetGlobals(); } test "printLogo all" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing column = 0; row = ROW_MIN; defaultAllTesting(0, ROW_MIN, 0); + // Call function - // - printLogo(); - // Post test - // + // Post test defaultVariablesTesting(0, ROW_MIN, 0); blankPagesTesting(); @@ -1679,59 +1582,58 @@ test "printLogo all" { expectEqual(blank, video_buffer[k]); } } - // Tear down - // + // Tear down resetGlobals(); } test "pageUp top page" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Pre testing page_index = TOTAL_NUM_PAGES - 1; defaultVariablesTesting(TOTAL_NUM_PAGES - 1, 0, 0); incrementingPagesTesting(); defaultVideoBufferTesting(); + // Call function - // - pageUp(); - // Post test - // + // Post test defaultVariablesTesting(TOTAL_NUM_PAGES - 1, 0, 0); incrementingPagesTesting(); defaultVideoBufferTesting(); - // Tear down - // + // Tear down resetGlobals(); } test "pageUp bottom page" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("disableCursor", vga.mock_disableCursor); + + // Pre testing defaultVariablesTesting(0, 0, 0); incrementingPagesTesting(); defaultVideoBufferTesting(); + // Call function - // - pageUp(); - // Post test - // + // Post test defaultVariablesTesting(1, 0, 0); incrementingPagesTesting(); @@ -1743,64 +1645,63 @@ test "pageUp bottom page" { if (i < START_OF_DISPLAYABLE_REGION - 11) { expectEqual(blank, b); } else if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + expectEqual(vga.mock_entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); } else { expectEqual(i - START_OF_DISPLAYABLE_REGION + TOTAL_CHAR_ON_PAGE, b); } } - // Tear down - // + // Tear down resetGlobals(); } test "pageDown bottom page" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); + // Pre testing - // - defaultVariablesTesting(0, 0, 0); incrementingPagesTesting(); defaultVideoBufferTesting(); + // Call function - // - pageDown(); - // Post test - // + // Post test defaultVariablesTesting(0, 0, 0); incrementingPagesTesting(); defaultVideoBufferTesting(); - // Tear down - // + // Tear down resetGlobals(); } test "pageDown top page" { // Set up - // setVideoBufferBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("disableCursor", vga.mock_disableCursor); + + // Pre testing page_index = TOTAL_NUM_PAGES - 1; defaultVariablesTesting(TOTAL_NUM_PAGES - 1, 0, 0); incrementingPagesTesting(); defaultVideoBufferTesting(); + // Call function - // - pageDown(); - // Post test - // + // Post test defaultVariablesTesting(TOTAL_NUM_PAGES - 2, 0, 0); incrementingPagesTesting(); @@ -1812,35 +1713,36 @@ test "pageDown top page" { if (i < START_OF_DISPLAYABLE_REGION - 11) { expectEqual(blank, b); } else if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + expectEqual(vga.mock_entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); } else { expectEqual((i - START_OF_DISPLAYABLE_REGION) + (TOTAL_CHAR_ON_PAGE * page_index), b); } } - // Tear down - // + // Tear down resetGlobals(); } test "clearScreen all" { // Set up - // setVideoBufferIncrementingBlankPages(); setPagesIncrementing(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing defaultVariablesTesting(0, 0, 0); incrementingVideoBufferTesting(); incrementingPagesTesting(); + // Call function - // - clearScreen(); - // Post test - // + // Post test defaultVariablesTesting(0, ROW_MIN, 0); var k: u16 = 0; while (k < VIDEO_BUFFER_SIZE) : (k += 1) { @@ -1863,198 +1765,207 @@ test "clearScreen all" { } } } - // Tear down - // + // Tear down resetGlobals(); } test "moveCursorLeft top left of screen" { // Set up - // setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + // Pre testing - // - defaultAllTesting(0, 0, 0); + // Call function - // - moveCursorLeft(); + // Post test - // - defaultAllTesting(0, 0, 0); - // Tear down - // + // Tear down resetGlobals(); } test "moveCursorLeft top screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing column = 5; defaultAllTesting(0, 0, 5); + // Call function - // - moveCursorLeft(); + // Post test - // - defaultAllTesting(0, 0, 4); - // Tear down - // + // Tear down resetGlobals(); } test "moveCursorLeft start of row" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing row = 5; defaultAllTesting(0, 5, 0); + // Call function - // - moveCursorLeft(); + // Post test - // - defaultAllTesting(0, 4, vga.WIDTH - 1); - // Tear down - // + // Tear down resetGlobals(); } test "moveCursorRight bottom right of screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing row = vga.HEIGHT - 1; column = vga.WIDTH - 1; defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + // Call function - // - moveCursorRight(); + // Post test - // - defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); - // Tear down - // + // Tear down resetGlobals(); } test "moveCursorRight top screen" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing column = 5; defaultAllTesting(0, 0, 5); + // Call function - // - moveCursorRight(); + // Post test - // - defaultAllTesting(0, 0, 6); - // Tear down - // + // Tear down resetGlobals(); } test "moveCursorRight end of row" { // Set up - // setVideoBufferBlankPages(); - // Pre testing - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing row = 5; column = vga.WIDTH - 1; defaultAllTesting(0, 5, vga.WIDTH - 1); + // Call function - // - moveCursorRight(); + // Post test - // - defaultAllTesting(0, 6, 0); - // Tear down - // + // Tear down resetGlobals(); } test "setColour all" { // Set up - // + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addConsumeFunction("entry", vga.mock_entry); - // // Pre testing - // - // // Call function - // - const new_colour: u8 = vga.entryColour(vga.COLOUR_WHITE, vga.COLOUR_WHITE); + const new_colour: u8 = vga.mock_entryColour(vga.COLOUR_WHITE, vga.COLOUR_WHITE); setColour(new_colour); - // Post test - // + // Post test expectEqual(new_colour, colour); - expectEqual(vga.entry(0, new_colour), blank); + expectEqual(vga.mock_entry(0, new_colour), blank); + // Tear down - // resetGlobals(); } test "writeString all" { // Set up - // setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.mock_entry); + + vga.addConsumeFunction("updateCursor", vga.mock_updateCursor); + // Pre testing - // row = ROW_MIN; defaultAllTesting(0, ROW_MIN, 0); + // Call function - // - writeString("ABC") catch unreachable; - // Post test - // + // Post test defaultVariablesTesting(0, ROW_MIN, 3); for (pages) |page, i| { for (page) |c, j| { if ((i == 0) and (j == 0)) { - expectEqual(vga.entry('A', colour), c); + expectEqual(vga.mock_entry('A', colour), c); } else if ((i == 0) and (j == 1)) { - expectEqual(vga.entry('B', colour), c); + expectEqual(vga.mock_entry('B', colour), c); } else if ((i == 0) and (j == 2)) { - expectEqual(vga.entry('C', colour), c); + expectEqual(vga.mock_entry('C', colour), c); } else { expectEqual(blank, c); } @@ -2064,17 +1975,96 @@ test "writeString all" { var k: u16 = 0; while (k < VIDEO_BUFFER_SIZE) : (k += 1) { if (k == START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.entry('A', colour), video_buffer[k]); + expectEqual(vga.mock_entry('A', colour), video_buffer[k]); } else if (k == START_OF_DISPLAYABLE_REGION + 1) { - expectEqual(vga.entry('B', colour), video_buffer[k]); + expectEqual(vga.mock_entry('B', colour), video_buffer[k]); } else if (k == START_OF_DISPLAYABLE_REGION + 2) { - expectEqual(vga.entry('C', colour), video_buffer[k]); + expectEqual(vga.mock_entry('C', colour), video_buffer[k]); } else { expectEqual(blank, video_buffer[k]); } } - // Tear down - // + // Tear down + resetGlobals(); +} + +test "init 0,0" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.updateCursor call for updating the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", u16(0)); + + vga.addRepeatFunction("entryColour", vga.mock_entryColour); + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + init(); + + // Post test + defaultVariablesTesting(0, ROW_MIN, 0); + blankPagesTesting(); + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k < START_OF_DISPLAYABLE_REGION) { + // This is where the logo will be, but is a complex string so no testing + // Just take my word it works :P + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // Tear down + resetGlobals(); +} + +test "init not 0,0" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.updateCursor call for updating the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", vga.WIDTH); + + vga.addRepeatFunction("entryColour", vga.mock_entryColour); + vga.addRepeatFunction("entry", vga.mock_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + init(); + + // Post test + defaultVariablesTesting(0, ROW_MIN + 1, 0); + blankPagesTesting(); + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k < START_OF_DISPLAYABLE_REGION) { + // This is where the logo will be, but is a complex string so no testing + // Just take my word it works :P + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // Tear down resetGlobals(); } diff --git a/src/kernel/vga.zig b/src/kernel/vga.zig index c2c9d4d..244ad37 100644 --- a/src/kernel/vga.zig +++ b/src/kernel/vga.zig @@ -1,70 +1,64 @@ -// Zig version: 0.4.0 - -const builtin = @import("builtin"); const arch = @import("arch.zig").internals; -const expectEqual = @import("std").testing.expectEqual; -const warn = @import("std").debug.warn; - /// The port address for the VGA register selection. -const PORT_ADDRESS: u16 = 0x03D4; +pub const PORT_ADDRESS: u16 = 0x03D4; /// The port address for the VGA data. -const PORT_DATA: u16 = 0x03D5; +pub const PORT_DATA: u16 = 0x03D5; -// The indexes that is passed to the address port to select the register for the data to be -// read or written to. -const REG_HORIZONTAL_TOTAL: u8 = 0x00; -const REG_HORIZONTAL_DISPLAY_ENABLE_END: u8 = 0x01; -const REG_START_HORIZONTAL_BLINKING: u8 = 0x02; -const REG_END_HORIZONTAL_BLINKING: u8 = 0x03; -const REG_START_HORIZONTAL_RETRACE_PULSE: u8 = 0x04; -const REG_END_HORIZONTAL_RETRACE_PULSE: u8 = 0x05; -const REG_VERTICAL_TOTAL: u8 = 0x06; -const REG_OVERFLOW: u8 = 0x07; -const REG_PRESET_ROW_SCAN: u8 = 0x08; -const REG_MAXIMUM_SCAN_LINE: u8 = 0x09; +/// The indexes that is passed to the address port to select the register for the data to be +/// read or written to. +pub const REG_HORIZONTAL_TOTAL: u8 = 0x00; +pub const REG_HORIZONTAL_DISPLAY_ENABLE_END: u8 = 0x01; +pub const REG_START_HORIZONTAL_BLINKING: u8 = 0x02; +pub const REG_END_HORIZONTAL_BLINKING: u8 = 0x03; +pub const REG_START_HORIZONTAL_RETRACE_PULSE: u8 = 0x04; +pub const REG_END_HORIZONTAL_RETRACE_PULSE: u8 = 0x05; +pub const REG_VERTICAL_TOTAL: u8 = 0x06; +pub const REG_OVERFLOW: u8 = 0x07; +pub const REG_PRESET_ROW_SCAN: u8 = 0x08; +pub const REG_MAXIMUM_SCAN_LINE: u8 = 0x09; -/// The command for setting the start of the cursor scan line. -const REG_CURSOR_START: u8 = 0x0A; +/// The register select for setting the cursor scan lines. +pub const REG_CURSOR_START: u8 = 0x0A; +pub const REG_CURSOR_END: u8 = 0x0B; +pub const REG_START_ADDRESS_HIGH: u8 = 0x0C; +pub const REG_START_ADDRESS_LOW: u8 = 0x0D; -/// The command for setting the end of the cursor scan line. -const REG_CURSOR_END: u8 = 0x0B; -const REG_START_ADDRESS_HIGH: u8 = 0x0C; -const REG_START_ADDRESS_LOW: u8 = 0x0D; +/// The command for setting the cursor's linear location. +pub const REG_CURSOR_LOCATION_HIGH: u8 = 0x0E; +pub const REG_CURSOR_LOCATION_LOW: u8 = 0x0F; -/// The command for setting the upper byte of the cursor's linear location. -const REG_CURSOR_LOCATION_HIGH: u8 = 0x0E; +/// Other VGA registers. +pub const REG_VERTICAL_RETRACE_START: u8 = 0x10; +pub const REG_VERTICAL_RETRACE_END: u8 = 0x11; +pub const REG_VERTICAL_DISPLAY_ENABLE_END: u8 = 0x12; +pub const REG_OFFSET: u8 = 0x13; +pub const REG_UNDERLINE_LOCATION: u8 = 0x14; +pub const REG_START_VERTICAL_BLINKING: u8 = 0x15; +pub const REG_END_VERTICAL_BLINKING: u8 = 0x16; +pub const REG_CRT_MODE_CONTROL: u8 = 0x17; +pub const REG_LINE_COMPARE: u8 = 0x18; -/// The command for setting the lower byte of the cursor's linear location. -const REG_CURSOR_LOCATION_LOW: u8 = 0x0F; -const REG_VERTICAL_RETRACE_START: u8 = 0x10; -const REG_VERTICAL_RETRACE_END: u8 = 0x11; -const REG_VERTICAL_DISPLAY_ENABLE_END: u8 = 0x12; -const REG_OFFSET: u8 = 0x13; -const REG_UNDERLINE_LOCATION: u8 = 0x14; -const REG_START_VERTICAL_BLINKING: u8 = 0x15; -const REG_END_VERTICAL_BLINKING: u8 = 0x16; -const REG_CRT_MODE_CONTROL: u8 = 0x17; -const REG_LINE_COMPARE: u8 = 0x18; +/// The start of the cursor scan line, the very beginning. +pub const CURSOR_SCANLINE_START: u8 = 0x0; +/// The scan line for use in the underline cursor shape. +pub const CURSOR_SCANLINE_MIDDLE: u8 = 0xE; -///The start of the cursor scan line, the very beginning. -const CURSOR_SCANLINE_START: u8 = 0x0; - -///The scan line for use in the underline cursor shape. -const CURSOR_SCANLINE_MIDDLE: u8 = 0xE; - -///The end of the cursor scan line, the very end. -const CURSOR_SCANLINE_END: u8 = 0xF; +/// The end of the cursor scan line, the very end. +pub const CURSOR_SCANLINE_END: u8 = 0xF; /// If set, disables the cursor. -const CURSOR_DISABLE: u8 = 0x20; +pub const CURSOR_DISABLE: u8 = 0x20; +/// The number of characters wide the screen is. pub const WIDTH: u16 = 80; + +/// The number of characters heigh the screen is. pub const HEIGHT: u16 = 25; -// The set of colours that VGA supports and can display for the foreground and background. +/// The set of colours that VGA supports and can display for the foreground and background. pub const COLOUR_BLACK: u4 = 0x00; pub const COLOUR_BLUE: u4 = 0x01; pub const COLOUR_GREEN: u4 = 0x02; @@ -83,7 +77,7 @@ pub const COLOUR_LIGHT_BROWN: u4 = 0x0E; pub const COLOUR_WHITE: u4 = 0x0F; /// The set of shapes that can be displayed. -pub const CursorShape = enum(u1) { +pub const CursorShape = enum { /// The cursor has the underline shape. UNDERLINE, @@ -97,6 +91,28 @@ var cursor_scanline_start: u8 = undefined; /// The cursor scan line end so to know whether is in block or underline mode. var cursor_scanline_end: u8 = undefined; +/// A inline function for setting the VGA register port to read from or write to. +inline fn sendPort(port: u8) void { + arch.outb(PORT_ADDRESS, port); +} + +/// A inline function for sending data to the set VGA register port. +inline fn sendData(data: u8) void { + arch.outb(PORT_DATA, data); +} + +/// A inline function for setting the VGA register port to read from or write toa and sending data +/// to the set VGA register port. +inline fn sendPortData(port: u8, data: u8) void { + sendPort(port); + sendData(data); +} + +/// A inline function for getting data from a set VGA register port. +inline fn getData() u8 { + return arch.inb(PORT_DATA); +} + /// /// Takes two 4 bit values that represent the foreground and background colour of the text and /// returns a 8 bit value that gives both to be displayed. @@ -105,7 +121,7 @@ var cursor_scanline_end: u8 = undefined; /// IN fg: u4 - The foreground colour. /// IN bg: u4 - The background colour. /// -/// Return: +/// Return: u8 /// Both combined into 1 byte for the colour to be displayed. /// pub fn entryColour(fg: u4, bg: u4) u8 { @@ -117,30 +133,25 @@ pub fn entryColour(fg: u4, bg: u4) u8 { /// background colour. /// /// Arguments: -/// IN uc: u8 - The character. +/// IN char: u8 - The character ro display. /// IN colour: u8 - The foreground and background colour. /// -/// Return: -/// The VGA entry. +/// Return: u16 +/// A VGA entry. /// -pub fn entry(uc: u8, colour: u8) u16 { - return u16(uc) | u16(colour) << 8; +pub fn entry(char: u8, colour: u8) u16 { + return u16(char) | u16(colour) << 8; } /// /// Update the hardware on screen cursor. /// /// Arguments: -/// IN x: u16 - The horizontal position of the cursor. -/// IN y: u16 - The vertical position of the cursor. -/// -/// Return: -/// The VGA entry. +/// IN x: u16 - The horizontal position of the cursor (column). +/// IN y: u16 - The vertical position of the cursor (row). /// pub fn updateCursor(x: u16, y: u16) void { var pos: u16 = undefined; - var pos_upper: u16 = undefined; - var pos_lower: u16 = undefined; // Make sure new cursor position is within the screen if (x < WIDTH and y < HEIGHT) { @@ -150,31 +161,28 @@ pub fn updateCursor(x: u16, y: u16) void { pos = (HEIGHT - 1) * WIDTH + (WIDTH - 1); } - pos_upper = (pos >> 8) & 0x00FF; - pos_lower = pos & 0x00FF; + const pos_upper = (pos >> 8) & 0x00FF; + const pos_lower = pos & 0x00FF; // Set the cursor position - arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_LOW); - arch.outb(PORT_DATA, @truncate(u8, pos_lower)); - - arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH); - arch.outb(PORT_DATA, @truncate(u8, pos_upper)); + sendPortData(REG_CURSOR_LOCATION_LOW, @truncate(u8, pos_lower)); + sendPortData(REG_CURSOR_LOCATION_HIGH, @truncate(u8, pos_upper)); } /// -/// Get the hardware cursor position. +/// Get the linear position of the hardware cursor. /// -/// Return: +/// Return: u16 /// The linear cursor position. /// pub fn getCursor() u16 { var cursor: u16 = 0; - arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_LOW); - cursor |= u16(arch.inb(PORT_DATA)); + sendPort(REG_CURSOR_LOCATION_LOW); + cursor |= u16(getData()); - arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH); - cursor |= u16(arch.inb(PORT_DATA)) << 8; + sendPort(REG_CURSOR_LOCATION_HIGH); + cursor |= u16(getData()) << 8; return cursor; } @@ -183,50 +191,37 @@ pub fn getCursor() u16 { /// Enables the blinking cursor to that is is visible. /// pub fn enableCursor() void { - arch.outb(PORT_ADDRESS, REG_CURSOR_START); - arch.outb(PORT_DATA, cursor_scanline_start); - - arch.outb(PORT_ADDRESS, REG_CURSOR_END); - arch.outb(PORT_DATA, cursor_scanline_end); + sendPortData(REG_CURSOR_START, cursor_scanline_start); + sendPortData(REG_CURSOR_END, cursor_scanline_end); } /// /// Disables the blinking cursor to that is is visible. /// pub fn disableCursor() void { - arch.outb(PORT_ADDRESS, REG_CURSOR_START); - arch.outb(PORT_DATA, CURSOR_DISABLE); + sendPortData(REG_CURSOR_START, CURSOR_DISABLE); } /// /// Set the shape of the cursor. This can be and underline or block shape. /// /// Arguments: -/// IN shape: CURSOR_SHAPE - The enum CURSOR_SHAPE that selects which shape to use. +/// IN shape: CursorShape - The enum CursorShape that selects which shape to use. /// pub fn setCursorShape(shape: CursorShape) void { switch (shape) { CursorShape.UNDERLINE => { - arch.outb(PORT_ADDRESS, REG_CURSOR_START); - arch.outb(PORT_DATA, CURSOR_SCANLINE_MIDDLE); - - arch.outb(PORT_ADDRESS, REG_CURSOR_END); - arch.outb(PORT_DATA, CURSOR_SCANLINE_END); - cursor_scanline_start = CURSOR_SCANLINE_MIDDLE; cursor_scanline_end = CURSOR_SCANLINE_END; }, CursorShape.BLOCK => { - arch.outb(PORT_ADDRESS, REG_CURSOR_START); - arch.outb(PORT_DATA, CURSOR_SCANLINE_START); - - arch.outb(PORT_ADDRESS, REG_CURSOR_END); - arch.outb(PORT_DATA, CURSOR_SCANLINE_END); - cursor_scanline_start = CURSOR_SCANLINE_START; cursor_scanline_end = CURSOR_SCANLINE_END; }, } + + sendPortData(REG_CURSOR_START, cursor_scanline_start); + sendPortData(REG_CURSOR_END, cursor_scanline_end); } /// @@ -234,176 +229,8 @@ pub fn setCursorShape(shape: CursorShape) void { /// pub fn init() void { // Set the maximum scan line to 0x0F - arch.outb(PORT_ADDRESS, REG_MAXIMUM_SCAN_LINE); - arch.outb(PORT_DATA, CURSOR_SCANLINE_END); + sendPortData(REG_MAXIMUM_SCAN_LINE, CURSOR_SCANLINE_END); // Set by default the underline cursor setCursorShape(CursorShape.UNDERLINE); } - -test "entryColour" { - var fg: u4 = COLOUR_BLACK; - var bg: u4 = COLOUR_BLACK; - var res: u8 = entryColour(fg, bg); - expectEqual(u8(0x00), res); - - fg = COLOUR_LIGHT_GREEN; - bg = COLOUR_BLACK; - res = entryColour(fg, bg); - expectEqual(u8(0x0A), res); - - fg = COLOUR_BLACK; - bg = COLOUR_LIGHT_GREEN; - res = entryColour(fg, bg); - expectEqual(u8(0xA0), res); - - fg = COLOUR_BROWN; - bg = COLOUR_LIGHT_GREEN; - res = entryColour(fg, bg); - expectEqual(u8(0xA6), res); -} - -test "entry" { - var colour: u8 = entryColour(COLOUR_BROWN, COLOUR_LIGHT_GREEN); - expectEqual(u8(0xA6), colour); - - // Character '0' is 0x30 - var video_entry: u16 = entry('0', colour); - expectEqual(u16(0xA630), video_entry); - - video_entry = entry(0x55, colour); - expectEqual(u16(0xA655), video_entry); -} - -fn testOutOfBounds(x: u16, y: u16) bool { - if (x < HEIGHT and y < WIDTH) { - return true; - } - return false; -} - -fn testUpperVal(x: u16, y: u16) u16 { - const pos: u16 = x * WIDTH + y; - const pos_upper: u16 = (pos >> 8) & 0x00FF; - return pos_upper; -} - -fn testLowerVal(x: u16, y: u16) u16 { - const pos: u16 = x * WIDTH + y; - const pos_lower: u16 = pos & 0x00FF; - return pos_lower; -} - -test "updateCursor out of bounds" { - var x: u16 = 0; - var y: u16 = 0; - var res: bool = testOutOfBounds(x, y); - expectEqual(true, res); - - x = HEIGHT - 1; - res = testOutOfBounds(x, y); - expectEqual(true, res); - - y = WIDTH - 1; - res = testOutOfBounds(x, y); - expectEqual(true, res); - - x = HEIGHT; - y = WIDTH; - res = testOutOfBounds(x, y); - expectEqual(false, res); - - x = HEIGHT - 1; - y = WIDTH; - res = testOutOfBounds(x, y); - expectEqual(false, res); - - x = HEIGHT; - y = WIDTH - 1; - res = testOutOfBounds(x, y); - expectEqual(false, res); -} - -test "updateCursor lower values" { - var x: u16 = 0x0000; - var y: u16 = 0x0000; - var res: u16 = testLowerVal(x, y); - var expected: u16 = 0x0000; - expectEqual(expected, res); - - x = 0x0000; - y = 0x000A; - res = testLowerVal(x, y); - expected = 0x000A; - expectEqual(expected, res); - - x = 0x000A; - y = 0x0000; - res = testLowerVal(x, y); - expected = 0x0020; - expectEqual(expected, res); - - x = 0x000A; - y = 0x000A; - res = testLowerVal(x, y); - expected = 0x002A; - expectEqual(expected, res); -} - -test "updateCursor upper values" { - var x: u16 = 0x0000; - var y: u16 = 0x0000; - var res: u16 = testUpperVal(x, y); - var expected: u16 = 0x0000; - expectEqual(expected, res); - - x = 0x0000; - y = 0x000A; - res = testUpperVal(x, y); - expected = 0x0000; - expectEqual(expected, res); - - x = 0x000A; - y = 0x0000; - res = testUpperVal(x, y); - expected = 0x0003; - expectEqual(expected, res); - - x = 0x000A; - y = 0x000A; - res = testUpperVal(x, y); - expected = 0x0003; - expectEqual(expected, res); -} - -test "getCursor all" { - warn(" Waiting for mocking "); - var res = getCursor(); -} - -test "enableCursor all" { - warn(" Waiting for mocking "); - enableCursor(); -} - -test "disableCursor all" { - warn(" Waiting for mocking "); - disableCursor(); -} - -test "setCursorShape all" { - setCursorShape(CursorShape.UNDERLINE); - expectEqual(CURSOR_SCANLINE_MIDDLE, cursor_scanline_start); - expectEqual(CURSOR_SCANLINE_END, cursor_scanline_end); - - setCursorShape(CursorShape.BLOCK); - expectEqual(CURSOR_SCANLINE_START, cursor_scanline_start); - expectEqual(CURSOR_SCANLINE_END, cursor_scanline_end); -} - -test "init all" { - warn(" Waiting for mocking "); - init(); - expectEqual(CURSOR_SCANLINE_MIDDLE, cursor_scanline_start); - expectEqual(CURSOR_SCANLINE_END, cursor_scanline_end); -} diff --git a/test/kernel/arch_mock.zig b/test/kernel/arch_mock.zig deleted file mode 100644 index 854dc3b..0000000 --- a/test/kernel/arch_mock.zig +++ /dev/null @@ -1,41 +0,0 @@ -// Zig version: 0.4.0 - -/// -/// Initialise the architecture -/// -pub fn init() void {} - -/// -/// Inline assembly to write to a given port with a byte of data. -/// -/// Arguments: -/// IN port: u16 - The port to write to. -/// IN data: u8 - The byte of data that will be sent. -/// -pub fn outb(port: u16, data: u8) void {} - -/// -/// Inline assembly that reads data from a given port and returns its value. -/// -/// Arguments: -/// IN port: u16 - The port to read data from. -/// -/// Return: -/// The data that the port returns. -/// -pub fn inb(port: u16) u8 {return 0;} - -/// -/// A simple way of waiting for I/O event to happen by doing an I/O event to flush the I/O -/// event being waited. -/// -pub fn ioWait() void {} - -/// -/// Register an interrupt handler. The interrupt number should be the arch-specific number. -/// -/// Arguments: -/// IN int: u16 - The arch-specific interrupt number to register for. -/// IN handler: fn (ctx: *InterruptContext) void - The handler to assign to the interrupt. -/// -pub fn registerInterruptHandler(int: u16, ctx: fn (ctx: *InterruptContext) void) void {} diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig new file mode 100644 index 0000000..c2652d5 --- /dev/null +++ b/test/mock/kernel/arch_mock.zig @@ -0,0 +1,94 @@ +const std = @import("std"); +const MemProfile = @import("mem_mock.zig").MemProfile; +const expect = std.testing.expect; +const warn = std.debug.warn; + +const mock_framework = @import("mock_framework.zig"); +pub const initTest = mock_framework.initTest; +pub const freeTest = mock_framework.freeTest; +pub const addTestParams = mock_framework.addTestParams; +pub const addConsumeFunction = mock_framework.addConsumeFunction; +pub const addRepeatFunction = mock_framework.addRepeatFunction; + +pub const InterruptContext = struct { + // Extra segments + gs: u32, + fs: u32, + es: u32, + ds: u32, + + // Destination, source, base pointer + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + + // General registers + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + + // Interrupt number and error code + int_num: u32, + error_code: u32, + + // Instruction pointer, code segment and flags + eip: u32, + cs: u32, + eflags: u32, + user_esp: u32, + ss: u32, +}; + +pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator, comptime options: type) void { + //return mock_framework.performAction("init", void, mem_profile, allocator); +} + +pub fn outb(port: u16, data: u8) void { + return mock_framework.performAction("outb", void, port, data); +} + +pub fn inb(port: u16) u8 { + return mock_framework.performAction("inb", u8, port); +} + +pub fn ioWait() void { + return mock_framework.performAction("ioWait", void); +} + +pub fn registerInterruptHandler(int: u16, ctx: fn (ctx: *InterruptContext) void) void { + return mock_framework.performAction("registerInterruptHandler", void, int, ctx); +} + +pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void { + return mock_framework.performAction("lgdt", void, gdt_ptr.*); +} + +pub fn ltr() void { + return mock_framework.performAction("ltr", void); +} + +pub fn lidt(idt_ptr: *const idt.IdtPtr) void { + return mock_framework.performAction("lidt", void, idt_ptr.*); +} + +pub fn enableInterrupts() void { + return mock_framework.performAction("enableInterrupts", void); +} + +pub fn disableInterrupts() void { + return mock_framework.performAction("disableInterrupts", void); +} + +pub fn halt() void { + return mock_framework.performAction("halt", void); +} + +pub fn spinWait() noreturn { + while (true) {} +} + +pub fn haltNoInterrupts() noreturn { + while (true) {} +} diff --git a/test/mock/kernel/log_mock.zig b/test/mock/kernel/log_mock.zig new file mode 100644 index 0000000..7c58ab4 --- /dev/null +++ b/test/mock/kernel/log_mock.zig @@ -0,0 +1,50 @@ +const mock_framework = @import("mock_framework.zig"); + +pub const Level = enum { + INFO, + DEBUG, + WARNING, + ERROR +}; + +fn logCallback(context: void, str: []const u8) anyerror!void {} + +pub fn log(comptime level: Level, comptime format: []const u8, args: ...) void { + //return mock_framework.performAction("log", void, level, format, args); +} + +pub fn logInfo(comptime format: []const u8, args: ...) void { + //return mock_framework.performAction("logInfo", void, format, args); +} + +pub fn logDebug(comptime format: []const u8, args: ...) void { + //return mock_framework.performAction("logDebug", void, format, args); +} + +pub fn logWarning(comptime format: []const u8, args: ...) void { + //return mock_framework.performAction("logWarning", void, format, args); +} + +pub fn logError(comptime format: []const u8, args: ...) void { + //return mock_framework.performAction("logError", void, format, args); +} + +pub fn addRepeatFunction(comptime fun_name: []const u8, function: var) void { + mock_framework.addRepeatFunction(fun_name, function); +} + +pub fn addTestFunction(comptime fun_name: []const u8, function: var) void { + mock_framework.addRepeatFunction(fun_name, function); +} + +pub fn addTestParams(comptime fun_name: []const u8, params: ...) void { + mock_framework.addTestParams(fun_name, params); +} + +pub fn initTest() void { + mock_framework.initTest(); +} + +pub fn freeTest() void { + mock_framework.freeTest(); +} \ No newline at end of file diff --git a/test/mock/kernel/mem_mock.zig b/test/mock/kernel/mem_mock.zig new file mode 100644 index 0000000..78e6c40 --- /dev/null +++ b/test/mock/kernel/mem_mock.zig @@ -0,0 +1,32 @@ +const multiboot = @import("../../../src/kernel/multiboot.zig"); + +pub const MemProfile = struct { + vaddr_end: [*]u8, + vaddr_start: [*]u8, + physaddr_end: [*]u8, + physaddr_start: [*]u8, + mem_kb: u32, + fixed_alloc_size: u32 +}; + +// The virtual/physical start/end of the kernel code +var KERNEL_PHYSADDR_START: u32 = 0x00100000; +var KERNEL_PHYSADDR_END: u32 = 0x01000000; +var KERNEL_VADDR_START: u32 = 0xC0100000; +var KERNEL_VADDR_END: u32 = 0xC1100000; +var KERNEL_ADDR_OFFSET: u32 = 0xC0000000; + +// The size of the fixed allocator used before the heap is set up. Set to 1MiB. +const FIXED_ALLOC_SIZE = 1024 * 1024; + +pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile { + return MemProfile{ + .vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END), + .vaddr_start = @ptrCast([*]u8, &KERNEL_VADDR_START), + .physaddr_end = @ptrCast([*]u8, &KERNEL_PHYSADDR_END), + .physaddr_start = @ptrCast([*]u8, &KERNEL_PHYSADDR_START), + // Total memory available including the initial 1MiB that grub doesn't include + .mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024, + .fixed_alloc_size = FIXED_ALLOC_SIZE + }; +} diff --git a/test/mock/kernel/mock_framework.zig b/test/mock/kernel/mock_framework.zig new file mode 100644 index 0000000..a331517 --- /dev/null +++ b/test/mock/kernel/mock_framework.zig @@ -0,0 +1,589 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const StringHashMap = std.StringHashMap; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const GlobalAllocator = std.debug.global_allocator; +const TailQueue = std.TailQueue; +const warn = std.debug.warn; + +/// +/// The enumeration of types that the mocking framework supports. These include basic types like u8 +/// and function types like fn () void +/// +const DataElementType = enum { + U4, + U8, + U16, + U32, + FN_OVOID, + FN_OUSIZE, + FN_OU16, + FN_IU16_OU8, + FN_IU4_IU4_OU8, + FN_IU8_IU8_OU16, + FN_IU16_IU8_OVOID, + FN_IU16_IU16_OVOID, +}; + +/// +/// A tagged union of all the data elements that the mocking framework can work with. This can be +/// expanded to add new types. This is needed as need a list of data that all have different types, +/// so this wraps the data into a union, (which is of one type) so can have a list of them. +/// +const DataElement = union(DataElementType) { + U4: u4, + U8: u8, + U16: u16, + U32: u32, + FN_OVOID: fn () void, + FN_OUSIZE: fn () usize, + FN_OU16: fn () u16, + FN_IU16_OU8: fn (u16) u8, + FN_IU4_IU4_OU8: fn (u4, u4) u8, + FN_IU8_IU8_OU16: fn (u8, u8) u16, + FN_IU16_IU8_OVOID: fn (u16, u8) void, + FN_IU16_IU16_OVOID: fn (u16, u16) void, +}; + +/// +/// The type of actions that the mocking framework can perform. +/// +const ActionType = enum { + /// This will test the parameters passed to a function. It will test the correct types and + /// value of each parameter. This is also used to return a specific value from a function so + /// can test for returns from a function. + TestValue, + + /// This action is to replace a function call to be mocked with another function the user + /// chooses to be replaced. This will consume the function call. This will allow the user to + /// check that the function is called once or multiple times by added a function to be mocked + /// multiple times. This also allows the ability for a function to be mocked by different + /// functions each time it is called. + ConsumeFunctionCall, + + /// This is similar to the ConsumeFunctionCall action, but will call the mocked function + /// repeatedly until the mocking is done. + RepeatFunctionCall, + + // Other actions that could be used + + // This will check that a function isn't called. + //NoFunctionCall + + // This is a generalisation of ConsumeFunctionCall and RepeatFunctionCall but can specify how + // many times a function can be called. + //FunctionCallN +}; + +/// +/// This is a pair of action and data to be actioned on. +/// +const Action = struct { + action: ActionType, + data: DataElement, +}; + +/// +/// The type for a queue of actions using std.TailQueue. +/// +const ActionList = TailQueue(Action); + +/// +/// The type for linking the function name to be mocked and the action list to be acted on. +/// +const NamedActionMap = StringHashMap(ActionList); + +/// +/// The mocking framework. +/// +/// Return: type +/// This returns a struct for adding and acting on mocked functions. +/// +fn Mock() type { + return struct { + const Self = @This(); + + /// The map of function name and action list. + named_actions: NamedActionMap, + + /// + /// Create a DataElement from data. This wraps data into a union. This allows the ability + /// to have a list of different types. + /// + /// Arguments: + /// IN arg: var - The data, this can be a function or basic type value. + /// + /// Return: DataElement + /// A DataElement with the data wrapped. + /// + fn createDataElement(arg: var) DataElement { + return switch (@typeOf(arg)) { + u4 => DataElement{ .U4 = arg }, + u8 => DataElement{ .U8 = arg }, + u16 => DataElement{ .U16 = arg }, + u32 => DataElement{ .U32 = arg }, + fn () void => DataElement{ .FN_OVOID = arg }, + fn () usize => DataElement{ .FN_OUSIZE = arg }, + fn () u16 => DataElement{ .FN_OU16 = arg }, + fn (u16) u8 => DataElement{ .FN_IU16_OU8 = arg }, + fn (u4, u4) u8 => DataElement{ .FN_IU4_IU4_OU8 = arg }, + fn (u8, u8) u16 => DataElement{ .FN_IU8_IU8_OU16 = arg }, + fn (u16, u8) void => DataElement{ .FN_IU16_IU8_OVOID = arg }, + fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg }, + else => @compileError("Type not supported: " ++ @typeName(@typeOf(arg))), + }; + } + + /// + /// Get the enum that represents the type given. + /// + /// Arguments: + /// IN T: type - A type. + /// + /// Return: DataElementType + /// The DataElementType that represents the type given. + /// + fn getDataElementType(comptime T: type) DataElementType { + return switch (T) { + u4 => DataElementType.U4, + u8 => DataElementType.U8, + u16 => DataElementType.U16, + u32 => DataElementType.U32, + fn () void => DataElementType.FN_OVOID, + fn () u16 => DataElementType.FN_OU16, + fn (u16) u8 => DataElementType.FN_IU16_OU8, + fn (u4, u4) u8 => DataElementType.FN_IU4_IU4_OU8, + fn (u8, u8) u16 => DataElementType.FN_IU8_IU8_OU16, + fn (u16, u8) void => DataElementType.FN_IU16_IU8_OVOID, + fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID, + else => @compileError("Type not supported: " ++ @typeName(T)), + }; + } + + /// + /// Get the data out of the tagged union + /// + /// Arguments: + /// IN T: type - The type of the data to extract. Used to switch on the + /// tagged union. + /// IN element: DataElement - The data element to unwrap the data from. + /// + /// Return: T + /// The data of type T from the DataElement. + /// + fn getDataValue(comptime T: type, element: DataElement) T { + return switch (T) { + u4 => element.U4, + u8 => element.U8, + u16 => element.U16, + u32 => element.U32, + fn () void => element.FN_OVOID, + fn () u16 => element.FN_OU16, + fn (u16) u8 => element.FN_IU16_OU8, + fn (u4, u4) u8 => element.FN_IU4_IU4_OU8, + fn (u8, u8) u16 => element.FN_IU8_IU8_OU16, + fn (u16, u8) void => element.FN_IU16_IU8_OVOID, + fn (u16, u16) void => element.FN_IU16_IU16_OVOID, + else => @compileError("Type not supported: " ++ @typeName(T)), + }; + } + + /// + /// Create a function type from a return type and its arguments. Waiting for + /// https://github.com/ziglang/zig/issues/313. TODO: Tidy mocking framework #69 + /// + /// Arguments: + /// IN RetType: type - The return type of the function. + /// IN params: arglist - The argument list for the function. + /// + /// Return: type + /// A function type that represents the return type and its arguments. + /// + fn getFunctionType(comptime RetType: type, params: ...) type { + return switch (params.len) { + 0 => fn () RetType, + 1 => fn (@typeOf(params[0])) RetType, + 2 => fn (@typeOf(params[0]), @typeOf(params[1])) RetType, + else => @compileError("Couldn't generate function type for " ++ params.len ++ "parameters\n"), + }; + } + + /// + /// This tests a value passed to a function. + /// + /// Arguments: + /// IN ExpectedType: type - The expected type of the value to be tested. + /// IN expected_value: ExpectedType - The expected value to be tested. This is what was + /// passed to the functions. + /// IN elem: DataElement - The wrapped data element to test against the + /// expected value. + /// + fn expectTest(comptime ExpectedType: type, expected_value: ExpectedType, elem: DataElement) void { + if (ExpectedType == void) { + // Can't test void as it has no value + warn("Can not test a value for void\n"); + expect(false); + } + + // Test that the types match + const expect_type = comptime getDataElementType(ExpectedType); + expectEqual(expect_type, DataElementType(elem)); + + // Types match, so can use the expected type to get the actual data + const actual_value = getDataValue(ExpectedType, elem); + + // Test the values + expectEqual(expected_value, actual_value); + } + + /// + /// This returns a value from the wrapped data element. This will be a test value to be + /// returned by a mocked function. + /// + /// Arguments: + /// IN fun_name: []const u8 - The function name to be used to tell the user if + /// there is no return value set up. + /// IN/OUT action_list: *ActionList - The action list to extract the return value from. + /// IN DataType: type - The type of the return value. + /// + fn expectGetValue(comptime fun_name: []const u8, action_list: *ActionList, comptime DataType: type) DataType { + if (DataType == void) { + return; + } + + if (action_list.*.popFirst()) |action_node| { + const action = action_node.data; + const expect_type = getDataElementType(DataType); + + const ret = getDataValue(DataType, action.data); + + expectEqual(DataElementType(action.data), expect_type); + + // Free the node + action_list.*.destroyNode(action_node, GlobalAllocator); + + return ret; + } else { + warn("No more test values for the return of function: " ++ fun_name ++ "\n"); + expect(false); + unreachable; + } + } + + /// + /// This adds a action to the action list with ActionType provided. It will create a new + /// mapping if one doesn't exist for a function name. + /// + /// Arguments: + /// IN/OUT self: *Self - Self. This is the mocking object to be modified to add + /// the test data. + /// IN fun_name: []const u8 - The function name to add the test parameters to. + /// IN data: var - The data to add. + /// IN action_type: ActionType - The action type to add. + /// + pub fn addAction(self: *Self, comptime fun_name: []const u8, data: var, action_type: ActionType) void { + // Add a new mapping if one doesn't exist. + if (!self.named_actions.contains(fun_name)) { + expect(self.named_actions.put(fun_name, TailQueue(Action).init()) catch unreachable == null); + } + + // Get the function mapping to add the parameter to. + if (self.named_actions.get(fun_name)) |actions_kv| { + var action_list = actions_kv.value; + const action = Action{ + .action = action_type, + .data = createDataElement(data), + }; + var a = action_list.createNode(action, GlobalAllocator) catch unreachable; + action_list.append(a); + // Need to re-assign the value as it isn't updated when we just append + actions_kv.value = action_list; + } else { + // Shouldn't get here as we would have just added a new mapping + // But just in case ;) + warn("No function name: " ++ fun_name ++ "\n"); + expect(false); + unreachable; + } + } + + /// + /// Perform an action on a function. This can be one of ActionType. + /// + /// Arguments: + /// IN/OUT self: *Self - Self. This is the mocking object to be modified to + /// perform a action. + /// IN fun_name: []const u8 - The function name to act on. + /// IN RetType: type - The return type of the function being mocked. + /// IN params: arglist - The list of parameters of the mocked function. + /// + /// Return: RetType + /// The return value of the mocked function. This can be void. + /// + pub fn performAction(self: *Self, comptime fun_name: []const u8, comptime RetType: type, params: ...) RetType { + if (self.named_actions.get(fun_name)) |kv_actions_list| { + var action_list = kv_actions_list.value; + // Peak the first action to test the action type + if (action_list.first) |action_node| { + const action = action_node.data; + const ret = switch (action.action) { + ActionType.TestValue => ret: { + comptime var i = 0; + inline while (i < params.len) : (i += 1) { + // Now pop the action as we are going to use it + // Have already checked that it is not null + const test_node = action_list.popFirst().?; + const test_action = test_node.data; + const param = params[i]; + const param_type = @typeOf(params[i]); + + expectTest(param_type, param, test_action.data); + + // Free the node + action_list.destroyNode(test_node, GlobalAllocator); + } + break :ret expectGetValue(fun_name, &action_list, RetType); + }, + ActionType.ConsumeFunctionCall => ret: { + // Now pop the action as we are going to use it + // Have already checked that it is not null + const test_node = action_list.popFirst().?; + const test_element = test_node.data.data; + + // Work out the type of the function to call from the params and return type + // At compile time + //const expected_function = getFunctionType(RetType, params); + // Waiting for this: + // error: compiler bug: unable to call var args function at compile time. https://github.com/ziglang/zig/issues/313 + // to be resolved + const expected_function = switch (params.len) { + 0 => fn () RetType, + 1 => fn (@typeOf(params[0])) RetType, + 2 => fn (@typeOf(params[0]), @typeOf(params[1])) RetType, + else => @compileError("Couldn't generate function type for " ++ params.len ++ "parameters\n"), + }; + + // Get the corresponding DataElementType + const expect_type = comptime getDataElementType(expected_function); + + // Test that the types match + expectEqual(expect_type, DataElementType(test_element)); + + // Types match, so can use the expected type to get the actual data + const actual_function = getDataValue(expected_function, test_element); + + // Free the node + action_list.destroyNode(test_node, GlobalAllocator); + + // The data element will contain the function to call + const r = switch (params.len) { + 0 => @noInlineCall(actual_function), + 1 => @noInlineCall(actual_function, params[0]), + 2 => @noInlineCall(actual_function, params[0], params[1]), + else => @compileError(params.len ++ " or more parameters not supported"), + }; + + break :ret r; + }, + ActionType.RepeatFunctionCall => ret: { + // Do the same for ActionType.ConsumeFunctionCall but instead of + // popping the function, just peak + const test_element = action.data; + const expected_function = switch (params.len) { + 0 => fn () RetType, + 1 => fn (@typeOf(params[0])) RetType, + 2 => fn (@typeOf(params[0]), @typeOf(params[1])) RetType, + else => @compileError("Couldn't generate function type for " ++ params.len ++ "parameters\n"), + }; + + // Get the corresponding DataElementType + const expect_type = comptime getDataElementType(expected_function); + + // Test that the types match + expectEqual(expect_type, DataElementType(test_element)); + + // Types match, so can use the expected type to get the actual data + const actual_function = getDataValue(expected_function, test_element); + + // The data element will contain the function to call + const r = switch (params.len) { + 0 => @noInlineCall(actual_function), + 1 => @noInlineCall(actual_function, params[0]), + 2 => @noInlineCall(actual_function, params[0], params[1]), + else => @compileError(params.len ++ " or more parameters not supported"), + }; + + break :ret r; + }, + }; + + // Re-assign the action list as this would have changed + kv_actions_list.value = action_list; + return ret; + } else { + warn("No action list elements for function: " ++ fun_name ++ "\n"); + expect(false); + unreachable; + } + } else { + warn("No function name: " ++ fun_name ++ "\n"); + expect(false); + unreachable; + } + } + + /// + /// Initialise the mocking framework. + /// + /// Return: Self + /// An initialised mocking framework. + /// + pub fn init() Self { + return Self{ + .named_actions = StringHashMap(ActionList).init(GlobalAllocator), + }; + } + + /// + /// End the mocking session. This will check all test parameters and consume functions are + /// consumed. Any repeat functions are deinit. + /// + /// Arguments: + /// IN/OUT self: *Self - Self. This is the mocking object to be modified to finished + /// the mocking session. + /// + pub fn finish(self: *Self) void { + // Make sure the expected list is empty + var it = self.named_actions.iterator(); + while (it.next()) |next| { + var action_list = next.value; + if (action_list.popFirst()) |action_node| { + const action = action_node.data; + switch (action.action) { + ActionType.TestValue, ActionType.ConsumeFunctionCall => { + // These need to be all consumed + warn("Unused testing value: Type: {}, value: {} for function '{}'\n", action.action, DataElementType(action.data), next.key); + expect(false); + unreachable; + }, + ActionType.RepeatFunctionCall => { + // As this is a repeat action, the function will still be here + // So need to free it + action_list.destroyNode(action_node, GlobalAllocator); + next.value = action_list; + }, + } + } + } + + // Free the function mapping + self.named_actions.deinit(); + } + }; +} + +/// The global mocking object that is used for a mocking session. Maybe in the future, we can have +/// local mocking objects so can run the tests in parallel. +var mock: ?Mock() = null; + +/// +/// Get the mocking object and check we have one initialised. +/// +/// Return: *Mock() +/// Pointer to the global mocking object so can be modified. +/// +fn getMockObject() *Mock() { + // Make sure we have a mock object + if (mock) |*m| { + return m; + } else { + warn("MOCK object doesn't exists, please initiate this test\n"); + expect(false); + unreachable; + } +} + +/// +/// Initialise the mocking framework. +/// +pub fn initTest() void { + // Make sure there isn't a mock object + if (mock) |_| { + warn("MOCK object already exists, please free previous test\n"); + expect(false); + unreachable; + } else { + mock = Mock().init(); + } +} + +/// +/// End the mocking session. This will check all test parameters and consume functions are +/// consumed. Any repeat functions are deinit. +/// +pub fn freeTest() void { + getMockObject().finish(); + + // This will stop double frees + mock = null; +} + +/// +/// Add a list of test parameters to the action list. This will create a list of data +/// elements that represent the list of parameters that will be passed to a mocked +/// function. A mocked function may be called multiple times, so this list may contain +/// multiple values for each call to the same mocked function. +/// +/// Arguments: +/// IN/OUT self: *Self - Self. This is the mocking object to be modified to add +/// the test parameters. +/// IN fun_name: []const u8 - The function name to add the test parameters to. +/// IN params: arglist - The parameters to add. +/// +pub fn addTestParams(comptime fun_name: []const u8, params: ...) void { + var mock_obj = getMockObject(); + comptime var i = 0; + inline while (i < params.len) : (i += 1) { + mock_obj.addAction(fun_name, params[i], ActionType.TestValue); + } +} + +/// +/// Add a function to mock out another. This will add a consume function action, so once +/// the mocked function is called, this action wil be removed. +/// +/// Arguments: +/// IN fun_name: []const u8 - The function name to add the function to. +/// IN function: var - The function to add. +/// +pub fn addConsumeFunction(comptime fun_name: []const u8, function: var) void { + getMockObject().addAction(fun_name, function, ActionType.ConsumeFunctionCall); +} + +/// +/// Add a function to mock out another. This will add a repeat function action, so once +/// the mocked function is called, this action wil be removed. +/// +/// Arguments: +/// IN fun_name: []const u8 - The function name to add the function to. +/// IN function: var - The function to add. +/// +pub fn addRepeatFunction(comptime fun_name: []const u8, function: var) void { + getMockObject().addAction(fun_name, function, ActionType.RepeatFunctionCall); +} + +/// +/// Perform an action on a function. This can be one of ActionType. +/// +/// Arguments: +/// IN fun_name: []const u8 - The function name to act on. +/// IN RetType: type - The return type of the function being mocked. +/// IN params: arglist - The list of parameters of the mocked function. +/// +/// Return: RetType +/// The return value of the mocked function. This can be void. +/// +pub fn performAction(comptime fun_name: []const u8, comptime RetType: type, params: ...) RetType { + return getMockObject().performAction(fun_name, RetType, params); +} diff --git a/test/mock/kernel/mocking.zig b/test/mock/kernel/mocking.zig new file mode 100644 index 0000000..c5ed21c --- /dev/null +++ b/test/mock/kernel/mocking.zig @@ -0,0 +1,5 @@ +pub const log = @import("log_mock.zig"); +pub const mem = @import("mem_mock.zig"); +pub const vga = @import("vga_mock.zig"); +pub const arch = @import("arch_mock.zig"); +pub const panic = @import("panic_mock.zig"); diff --git a/test/mock/kernel/panic_mock.zig b/test/mock/kernel/panic_mock.zig new file mode 100644 index 0000000..b89c16d --- /dev/null +++ b/test/mock/kernel/panic_mock.zig @@ -0,0 +1,7 @@ +const builtin = @import("builtin"); +const panic = @import("std").debug.panic; + +pub fn panicFmt(trace: ?*builtin.StackTrace, comptime format: []const u8, args: ...) noreturn { + @setCold(true); + panic(format, args); +} diff --git a/test/mock/kernel/vga_mock.zig b/test/mock/kernel/vga_mock.zig new file mode 100644 index 0000000..cf48279 --- /dev/null +++ b/test/mock/kernel/vga_mock.zig @@ -0,0 +1,88 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const arch = @import("arch.zig").internals; +const mock_framework = @import("mock_framework.zig"); + +pub const initTest = mock_framework.initTest; +pub const freeTest = mock_framework.freeTest; +pub const addTestParams = mock_framework.addTestParams; +pub const addConsumeFunction = mock_framework.addConsumeFunction; +pub const addRepeatFunction = mock_framework.addRepeatFunction; + +pub const WIDTH: u16 = 80; +pub const HEIGHT: u16 = 25; + +pub const COLOUR_BLACK: u4 = 0x00; +pub const COLOUR_BLUE: u4 = 0x01; +pub const COLOUR_GREEN: u4 = 0x02; +pub const COLOUR_CYAN: u4 = 0x03; +pub const COLOUR_RED: u4 = 0x04; +pub const COLOUR_MAGENTA: u4 = 0x05; +pub const COLOUR_BROWN: u4 = 0x06; +pub const COLOUR_LIGHT_GREY: u4 = 0x07; +pub const COLOUR_DARK_GREY: u4 = 0x08; +pub const COLOUR_LIGHT_BLUE: u4 = 0x09; +pub const COLOUR_LIGHT_GREEN: u4 = 0x0A; +pub const COLOUR_LIGHT_CYAN: u4 = 0x0B; +pub const COLOUR_LIGHT_RED: u4 = 0x0C; +pub const COLOUR_LIGHT_MAGENTA: u4 = 0x0D; +pub const COLOUR_LIGHT_BROWN: u4 = 0x0E; +pub const COLOUR_WHITE: u4 = 0x0F; + +pub const CursorShape = enum { + UNDERLINE, + BLOCK, +}; + +pub fn entryColour(fg: u4, bg: u4) u8 { + return mock_framework.performAction("entryColour", u8, fg, bg); +} + +pub fn entry(uc: u8, colour: u8) u16 { + return mock_framework.performAction("entry", u16, uc, colour); +} + +pub fn updateCursor(x: u16, y: u16) void { + return mock_framework.performAction("updateCursor", void, x, y); +} + +pub fn getCursor() u16 { + return mock_framework.performAction("getCursor", u16); +} + +pub fn enableCursor() void { + return mock_framework.performAction("enableCursor", void); +} + +pub fn disableCursor() void { + return mock_framework.performAction("disableCursor", void); +} + +pub fn setCursorShape(shape: CursorShape) void { + return mock_framework.performAction("setCursorShape", void, shape); +} + +pub fn init() void { + return mock_framework.performAction("init", void); +} + +// User defined mocked functions + +pub fn mock_entryColour(fg: u4, bg: u4) u8 { + return u8(fg) | u8(bg) << 4; +} + +pub fn mock_entry(uc: u8, c: u8) u16 { + return u16(uc) | u16(c) << 8; +} + +pub fn mock_updateCursor(x: u16, y: u16) void { + // Here we can do any testing we like with the parameters. e.g. test out of bounds + expect(x < WIDTH); + expect(y < HEIGHT); +} + +pub fn mock_enableCursor() void {} + +pub fn mock_disableCursor() void {} diff --git a/test/unittests/kernel/test_tty.zig b/test/unittests/kernel/test_tty.zig new file mode 100644 index 0000000..969a846 --- /dev/null +++ b/test/unittests/kernel/test_tty.zig @@ -0,0 +1,4 @@ +// I gave up on trying to get all the tests in a separate file for the tty +test "" { + _ = @import("../../../src/kernel/tty.zig"); +} diff --git a/test/unittests/kernel/test_vga.zig b/test/unittests/kernel/test_vga.zig new file mode 100644 index 0000000..e388ff3 --- /dev/null +++ b/test/unittests/kernel/test_vga.zig @@ -0,0 +1,293 @@ +const vga = @import("../../../src/kernel/vga.zig"); +const arch = @import("../../../src/kernel/arch.zig").internals; + +const expectEqual = @import("std").testing.expectEqual; + +test "entryColour" { + var fg: u4 = vga.COLOUR_BLACK; + var bg: u4 = vga.COLOUR_BLACK; + var res: u8 = vga.entryColour(fg, bg); + expectEqual(u8(0x00), res); + + fg = vga.COLOUR_LIGHT_GREEN; + bg = vga.COLOUR_BLACK; + res = vga.entryColour(fg, bg); + expectEqual(u8(0x0A), res); + + fg = vga.COLOUR_BLACK; + bg = vga.COLOUR_LIGHT_GREEN; + res = vga.entryColour(fg, bg); + expectEqual(u8(0xA0), res); + + fg = vga.COLOUR_BROWN; + bg = vga.COLOUR_LIGHT_GREEN; + res = vga.entryColour(fg, bg); + expectEqual(u8(0xA6), res); +} + +test "entry" { + var colour: u8 = vga.entryColour(vga.COLOUR_BROWN, vga.COLOUR_LIGHT_GREEN); + expectEqual(u8(0xA6), colour); + + // Character '0' is 0x30 + var video_entry: u16 = vga.entry('0', colour); + expectEqual(u16(0xA630), video_entry); + + video_entry = vga.entry(0x55, colour); + expectEqual(u16(0xA655), video_entry); +} + +test "updateCursor width out of bounds" { + const x: u16 = vga.WIDTH; + const y: u16 = 0; + + const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, + vga.PORT_DATA, expected_lower, + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, + vga.PORT_DATA, expected_upper); + + vga.updateCursor(x, y); +} + +test "updateCursor height out of bounds" { + const x: u16 = 0; + const y: u16 = vga.HEIGHT; + + const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, + vga.PORT_DATA, expected_lower, + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, + vga.PORT_DATA, expected_upper); + + vga.updateCursor(x, y); +} + +test "updateCursor width and height out of bounds" { + const x: u16 = vga.WIDTH; + const y: u16 = vga.HEIGHT; + + const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, + vga.PORT_DATA, expected_lower, + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, + vga.PORT_DATA, expected_upper); + + vga.updateCursor(x, y); +} + +test "updateCursor width-1 and height out of bounds" { + const x: u16 = vga.WIDTH - 1; + const y: u16 = vga.HEIGHT; + + const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, + vga.PORT_DATA, expected_lower, + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, + vga.PORT_DATA, expected_upper); + + vga.updateCursor(x, y); +} + +test "updateCursor width and height-1 out of bounds" { + const x: u16 = vga.WIDTH; + const y: u16 = vga.HEIGHT - 1; + + const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, + vga.PORT_DATA, expected_lower, + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, + vga.PORT_DATA, expected_upper); + + vga.updateCursor(x, y); +} + +test "updateCursor in bounds" { + var x: u16 = 0x000A; + var y: u16 = 0x000A; + const expected: u16 = y * vga.WIDTH + x; + + var expected_upper: u8 = @truncate(u8, (expected >> 8) & 0x00FF); + var expected_lower: u8 = @truncate(u8, expected & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, + vga.PORT_DATA, expected_lower, + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, + vga.PORT_DATA, expected_upper); + vga.updateCursor(x, y); +} + +test "getCursor 1: 10" { + const expect: u16 = u16(10); + + // Mocking out the arch.outb and arch.inb calls for getting the hardware cursor: + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW); + + arch.addTestParams("inb", + vga.PORT_DATA, u8(10)); + + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH); + + arch.addTestParams("inb", + vga.PORT_DATA, u8(0)); + + const actual: u16 = vga.getCursor(); + expectEqual(expect, actual); +} + +test "getCursor 2: 0xBEEF" { + const expect: u16 = u16(0xBEEF); + + // Mocking out the arch.outb and arch.inb calls for getting the hardware cursor: + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW); + + arch.addTestParams("inb", + vga.PORT_DATA, u8(0xEF)); + + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH); + + arch.addTestParams("inb", + vga.PORT_DATA, u8(0xBE)); + + const actual: u16 = vga.getCursor(); + expectEqual(expect, actual); +} + +test "enableCursor all" { + arch.initTest(); + defer arch.freeTest(); + + // Need to init the cursor start and end positions, so call the vga.init() to set this up + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_MAXIMUM_SCAN_LINE, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END, + vga.PORT_ADDRESS, vga.REG_CURSOR_START, + vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, + vga.PORT_ADDRESS, vga.REG_CURSOR_END, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END, + // Mocking out the arch.outb calls for enabling the cursor: + // These are the default cursor positions from vga.init() + vga.PORT_ADDRESS, vga.REG_CURSOR_START, + vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, + vga.PORT_ADDRESS, vga.REG_CURSOR_END, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END); + + vga.init(); + vga.enableCursor(); +} + +test "disableCursor all" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for disabling the cursor: + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_START, + vga.PORT_DATA, vga.CURSOR_DISABLE); + vga.disableCursor(); +} + +test "setCursorShape UNDERLINE" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for setting the cursor shape to underline: + // This will also check that the scan line variables were set properly as these are using in + // the arch.outb call + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_START, + vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, + vga.PORT_ADDRESS, vga.REG_CURSOR_END, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END); + + vga.setCursorShape(vga.CursorShape.UNDERLINE); +} + +test "setCursorShape BLOCK" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for setting the cursor shape to block: + // This will also check that the scan line variables were set properly as these are using in + // the arch.outb call + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_CURSOR_START, + vga.PORT_DATA, vga.CURSOR_SCANLINE_START, + vga.PORT_ADDRESS, vga.REG_CURSOR_END, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END); + + vga.setCursorShape(vga.CursorShape.BLOCK); +} + +test "init all" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for setting the cursor max scan line and the shape to block: + // This will also check that the scan line variables were set properly as these are using in + // the arch.outb call for setting the cursor shape. + arch.addTestParams("outb", + vga.PORT_ADDRESS, vga.REG_MAXIMUM_SCAN_LINE, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END, + vga.PORT_ADDRESS, vga.REG_CURSOR_START, + vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, + vga.PORT_ADDRESS, vga.REG_CURSOR_END, + vga.PORT_DATA, vga.CURSOR_SCANLINE_END); + + vga.init(); +} diff --git a/test/unittests/test_all.zig b/test/unittests/test_all.zig new file mode 100644 index 0000000..ef4006f --- /dev/null +++ b/test/unittests/test_all.zig @@ -0,0 +1,4 @@ +test "all" { + _ = @import("kernel/test_vga.zig"); + _ = @import("kernel/test_tty.zig"); +}