From d5d4082a66c036d2c74d20ac20fd730f20b37d04 Mon Sep 17 00:00:00 2001 From: ED Date: Sun, 8 Sep 2019 20:48:23 +0100 Subject: [PATCH] Fixed tesing Add mocking of functions Added new function type Fixed up the mock testing Working mock_framework :), fixed up all tests for VGA and TTY Adding tests VGA testing done Fin vga and tty mock testing Fixed build Removed white spaces WIP Added tests for all build modes + reduced import string length for testing Added comments refactoring Re-added constants Added some comments Updated to master of zig Added unit tests to pipeline PR comments Fixed typos --- README.md | 33 +- azure-pipelines.yml | 7 +- build.zig | 32 +- src/kernel/arch.zig | 2 +- src/kernel/arch/x86/arch.zig | 6 +- src/kernel/arch/x86/boot.zig | 2 +- src/kernel/arch/x86/paging.zig | 9 +- src/kernel/kmain.zig | 10 +- src/kernel/log.zig | 4 +- src/kernel/mem.zig | 2 +- src/kernel/panic.zig | 2 +- src/kernel/tty.zig | 968 ++++++++++++++-------------- src/kernel/vga.zig | 357 +++------- test/kernel/arch_mock.zig | 41 -- test/mock/kernel/arch_mock.zig | 94 +++ test/mock/kernel/log_mock.zig | 50 ++ test/mock/kernel/mem_mock.zig | 32 + test/mock/kernel/mock_framework.zig | 589 +++++++++++++++++ test/mock/kernel/mocking.zig | 5 + test/mock/kernel/panic_mock.zig | 7 + test/mock/kernel/vga_mock.zig | 88 +++ test/unittests/kernel/test_tty.zig | 4 + test/unittests/kernel/test_vga.zig | 293 +++++++++ test/unittests/test_all.zig | 4 + 24 files changed, 1812 insertions(+), 829 deletions(-) delete mode 100644 test/kernel/arch_mock.zig create mode 100644 test/mock/kernel/arch_mock.zig create mode 100644 test/mock/kernel/log_mock.zig create mode 100644 test/mock/kernel/mem_mock.zig create mode 100644 test/mock/kernel/mock_framework.zig create mode 100644 test/mock/kernel/mocking.zig create mode 100644 test/mock/kernel/panic_mock.zig create mode 100644 test/mock/kernel/vga_mock.zig create mode 100644 test/unittests/kernel/test_tty.zig create mode 100644 test/unittests/kernel/test_vga.zig create mode 100644 test/unittests/test_all.zig 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"); +}