d600be874c
Fix TSS Also change to .{} syntax where appropriate. Added the SS segment Fixed spelling Refactoring GDT Multitasking working for now WIP scheduler Refactored Bitmap a bit WIP still Task switching working Handlers return the stack pointer that will be used to restore the tasks stack, normal handlers will return the same stack pointer it was called with where task switching will return the stack pointer of the next task and restore its state using the interrupt stub. Initial scheduler done Created a stage 2 init task Change u32 to usize Move Task to arch specific WIP WIP2 Removed esp from task, replaced with stack_pointer Removed the debug logs Fixed init task stack Change pickNextTask to pointer manipulation This allows less allocations so faster switching Temporary enable interrupts for some runtime tests PIT and RTC need interrupts enabled to run their runtime tests Renamed schedule => pickNextTask, comptime bitmap for pids not task init And some other stuff: No pub for the task anymore Use the leak detector allocator Fmt Fix unit tests And some other stuff :P PR review Moved Task out of arch and have the stack init in the arch file Mocking clean up Removed commented code Renamed createTask to scheduleTask where the user will have to provide a task to schedule Removed redundant pub in log runtime test Removed global allocator for scheduler Cleaner assembly in paging Fmt Added new Scheduler test mode Added new test mode to CI Removed one of the prints Added doc comment, task test for i386 Removed test WIP Runtime tests work Have a global set in one task and reacted to in another. Also test that local variables are preserved after a task switch. Removed new lines Increased line length Move the allocation of the bool above the task creation
768 lines
36 KiB
Zig
768 lines
36 KiB
Zig
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.testing.allocator;
|
|
const TailQueue = std.TailQueue;
|
|
const warn = std.debug.warn;
|
|
const gdt = @import("gdt_mock.zig");
|
|
const idt = @import("idt_mock.zig");
|
|
const cmos = @import("cmos_mock.zig");
|
|
const task = @import("task_mock.zig");
|
|
|
|
///
|
|
/// The enumeration of types that the mocking framework supports. These include basic types like u8
|
|
/// and function types like fn () void
|
|
///
|
|
const DataElementType = enum {
|
|
BOOL,
|
|
U4,
|
|
U8,
|
|
U16,
|
|
U32,
|
|
USIZE,
|
|
PTR_ALLOCATOR,
|
|
ECMOSSTATUSREGISTER,
|
|
ECMOSRTCREGISTER,
|
|
GDTPTR,
|
|
IDTPTR,
|
|
IDTENTRY,
|
|
PTR_CONST_GDTPTR,
|
|
PTR_CONST_IDTPTR,
|
|
ERROR_IDTERROR_VOID,
|
|
ERROR_MEM_PTRTASK,
|
|
PTR_TASK,
|
|
EFN_OVOID,
|
|
NFN_OVOID,
|
|
FN_OVOID,
|
|
FN_OUSIZE,
|
|
FN_OU16,
|
|
FN_IU8_OBOOL,
|
|
FN_IU8_OVOID,
|
|
FN_IU16_OVOID,
|
|
FN_IUSIZE_OVOID,
|
|
FN_IU16_OU8,
|
|
FN_IU4_IU4_OU8,
|
|
FN_IU8_IU8_OU16,
|
|
FN_IU16_IU8_OVOID,
|
|
FN_IU16_IU16_OVOID,
|
|
FN_IECMOSSTATUSREGISTER_IBOOL_OU8,
|
|
FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID,
|
|
FN_IECMOSRTCREGISTER_OU8,
|
|
FN_IU8_IEFNOVOID_OERRORIDTERRORVOID,
|
|
FN_IU8_INFNOVOID_OERRORIDTERRORVOID,
|
|
FN_IPTRCONSTGDTPTR_OVOID,
|
|
FN_IPTRCONSTIDTPTR_OVOID,
|
|
FN_OGDTPTR,
|
|
FN_OIDTPTR,
|
|
FN_IIDTENTRY_OBOOL,
|
|
FN_IPTRTask_IUSIZE_OVOID,
|
|
FN_IPTRTASK_IPTRALLOCATOR_OVOID,
|
|
FN_IFNOVOID_OMEMERRORPTRTASK,
|
|
FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK,
|
|
};
|
|
|
|
///
|
|
/// 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.
|
|
/// When https://github.com/ziglang/zig/issues/383 anf https://github.com/ziglang/zig/issues/2907
|
|
/// is done, can programitaclly create types for this. Can use a compile time block that loops
|
|
/// through the available basic types and create function types so don't have a long list.
|
|
///
|
|
const DataElement = union(DataElementType) {
|
|
BOOL: bool,
|
|
U4: u4,
|
|
U8: u8,
|
|
U16: u16,
|
|
U32: u32,
|
|
USIZE: usize,
|
|
PTR_ALLOCATOR: *std.mem.Allocator,
|
|
ECMOSSTATUSREGISTER: cmos.StatusRegister,
|
|
ECMOSRTCREGISTER: cmos.RtcRegister,
|
|
GDTPTR: gdt.GdtPtr,
|
|
IDTPTR: idt.IdtPtr,
|
|
IDTENTRY: idt.IdtEntry,
|
|
PTR_CONST_GDTPTR: *const gdt.GdtPtr,
|
|
PTR_CONST_IDTPTR: *const idt.IdtPtr,
|
|
ERROR_IDTERROR_VOID: idt.IdtError!void,
|
|
ERROR_MEM_PTRTASK: std.mem.Allocator.Error!*task.Task,
|
|
PTR_TASK: *task.Task,
|
|
EFN_OVOID: fn () callconv(.C) void,
|
|
NFN_OVOID: fn () callconv(.Naked) void,
|
|
FN_OVOID: fn () void,
|
|
FN_OUSIZE: fn () usize,
|
|
FN_OU16: fn () u16,
|
|
FN_IU8_OBOOL: fn (u8) bool,
|
|
FN_IU8_OVOID: fn (u8) void,
|
|
FN_IUSIZE_OVOID: fn (usize) void,
|
|
FN_IU16_OVOID: fn (u16) void,
|
|
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,
|
|
FN_IECMOSSTATUSREGISTER_IBOOL_OU8: fn (cmos.StatusRegister, bool) u8,
|
|
FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID: fn (cmos.StatusRegister, u8, bool) void,
|
|
FN_IECMOSRTCREGISTER_OU8: fn (cmos.RtcRegister) u8,
|
|
FN_IU8_IEFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.C) void) idt.IdtError!void,
|
|
FN_IU8_INFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.Naked) void) idt.IdtError!void,
|
|
FN_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void,
|
|
FN_IPTRCONSTIDTPTR_OVOID: fn (*const idt.IdtPtr) void,
|
|
FN_OGDTPTR: fn () gdt.GdtPtr,
|
|
FN_OIDTPTR: fn () idt.IdtPtr,
|
|
FN_IIDTENTRY_OBOOL: fn (idt.IdtEntry) bool,
|
|
FN_IPTRTask_IUSIZE_OVOID: fn (*task.Task, usize) void,
|
|
FN_IPTRTASK_IPTRALLOCATOR_OVOID: fn (*task.Task, *std.mem.Allocator) void,
|
|
FN_IFNOVOID_OMEMERRORPTRTASK: fn (fn () void) std.mem.Allocator.Error!*task.Task,
|
|
FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK: fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task,
|
|
};
|
|
|
|
///
|
|
/// 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: anytype - The data, this can be a function or basic type value.
|
|
///
|
|
/// Return: DataElement
|
|
/// A DataElement with the data wrapped.
|
|
///
|
|
fn createDataElement(arg: anytype) DataElement {
|
|
return switch (@TypeOf(arg)) {
|
|
bool => DataElement{ .BOOL = arg },
|
|
u4 => DataElement{ .U4 = arg },
|
|
u8 => DataElement{ .U8 = arg },
|
|
u16 => DataElement{ .U16 = arg },
|
|
u32 => DataElement{ .U32 = arg },
|
|
usize => DataElement{ .USIZE = arg },
|
|
*std.mem.Allocator => DataElement{ .PTR_ALLOCATOR = arg },
|
|
cmos.StatusRegister => DataElement{ .ECMOSSTATUSREGISTER = arg },
|
|
cmos.RtcRegister => DataElement{ .ECMOSRTCREGISTER = arg },
|
|
gdt.GdtPtr => DataElement{ .GDTPTR = arg },
|
|
idt.IdtPtr => DataElement{ .IDTPTR = arg },
|
|
idt.IdtEntry => DataElement{ .IDTENTRY = arg },
|
|
*const gdt.GdtPtr => DataElement{ .PTR_CONST_GDTPTR = arg },
|
|
*const idt.IdtPtr => DataElement{ .PTR_CONST_IDTPTR = arg },
|
|
idt.IdtError!void => DataElement{ .ERROR_IDTERROR_VOID = arg },
|
|
std.mem.Allocator.Error!*task.Task => DataElement{ .ERROR_MEM_PTRTASK = arg },
|
|
*task.Task => DataElement{ .PTR_TASK = arg },
|
|
fn () callconv(.C) void => DataElement{ .EFN_OVOID = arg },
|
|
fn () callconv(.Naked) void => DataElement{ .NFN_OVOID = arg },
|
|
fn () void => DataElement{ .FN_OVOID = arg },
|
|
fn () usize => DataElement{ .FN_OUSIZE = arg },
|
|
fn () u16 => DataElement{ .FN_OU16 = arg },
|
|
fn (u8) bool => DataElement{ .FN_IU8_OBOOL = arg },
|
|
fn (u8) void => DataElement{ .FN_IU8_OVOID = arg },
|
|
fn (usize) void => DataElement{ .FN_IUSIZE_OVOID = arg },
|
|
fn (u16) void => DataElement{ .FN_IU16_OVOID = 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 },
|
|
fn (cmos.StatusRegister, bool) u8 => DataElement{ .FN_IECMOSSTATUSREGISTER_IBOOL_OU8 = arg },
|
|
fn (cmos.StatusRegister, u8, bool) void => DataElement{ .FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID = arg },
|
|
fn (cmos.RtcRegister) u8 => DataElement{ .FN_IECMOSRTCREGISTER_OU8 = arg },
|
|
fn (*const gdt.GdtPtr) void => DataElement{ .FN_IPTRCONSTGDTPTR_OVOID = arg },
|
|
fn () gdt.GdtPtr => DataElement{ .FN_OGDTPTR = arg },
|
|
fn (*const idt.IdtPtr) void => DataElement{ .FN_IPTRCONSTIDTPTR_OVOID = arg },
|
|
fn () idt.IdtPtr => DataElement{ .FN_OIDTPTR = arg },
|
|
fn (u8, fn () callconv(.C) void) idt.IdtError!void => DataElement{ .FN_IU8_IEFNOVOID_OERRORIDTERRORVOID = arg },
|
|
fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => DataElement{ .FN_IU8_INFNOVOID_OERRORIDTERRORVOID = arg },
|
|
fn (idt.IdtEntry) bool => DataElement{ .FN_IIDTENTRY_OBOOL = arg },
|
|
fn (*task.Task, usize) void => DataElement{ .FN_IPTRTask_IUSIZE_OVOID = arg },
|
|
fn (*task.Task, *std.mem.Allocator) void => DataElement{ .FN_IPTRTASK_IPTRALLOCATOR_OVOID = arg },
|
|
fn (fn () void) std.mem.Allocator.Error!*task.Task => DataElement{ .FN_IFNOVOID_OMEMERRORPTRTASK = arg },
|
|
fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task => DataElement{ .FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK = 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) {
|
|
bool => DataElementType.BOOL,
|
|
u4 => DataElementType.U4,
|
|
u8 => DataElementType.U8,
|
|
u16 => DataElementType.U16,
|
|
u32 => DataElementType.U32,
|
|
usize => DataElementType.USIZE,
|
|
*std.mem.Allocator => DataElementType.PTR_ALLOCATOR,
|
|
cmos.StatusRegister => DataElementType.ECMOSSTATUSREGISTER,
|
|
cmos.RtcRegister => DataElementType.ECMOSRTCREGISTER,
|
|
gdt.GdtPtr => DataElementType.GDTPTR,
|
|
idt.IdtPtr => DataElementType.IDTPTR,
|
|
idt.IdtEntry => DataElementType.IDTENTRY,
|
|
*const gdt.GdtPtr => DataElementType.PTR_CONST_GDTPTR,
|
|
*const idt.IdtPtr => DataElementType.PTR_CONST_IDTPTR,
|
|
idt.IdtError!void => DataElementType.ERROR_IDTERROR_VOID,
|
|
std.mem.Allocator.Error!*task.Task => DataElementType.ERROR_MEM_PTRTASK,
|
|
*task.Task => DataElementType.PTR_TASK,
|
|
fn () callconv(.C) void => DataElementType.EFN_OVOID,
|
|
fn () callconv(.Naked) void => DataElementType.NFN_OVOID,
|
|
fn () void => DataElementType.FN_OVOID,
|
|
fn () usize => DataElementType.FN_OUSIZE,
|
|
fn () u16 => DataElementType.FN_OU16,
|
|
fn (u8) bool => DataElementType.FN_IU8_OBOOL,
|
|
fn (u8) void => DataElementType.FN_IU8_OVOID,
|
|
fn (u16) void => DataElementType.FN_IU16_OVOID,
|
|
fn (usize) void => DataElementType.FN_IUSIZE_OVOID,
|
|
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,
|
|
fn (cmos.StatusRegister, bool) u8 => DataElementType.FN_IECMOSSTATUSREGISTER_IBOOL_OU8,
|
|
fn (cmos.StatusRegister, u8, bool) void => DataElementType.FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID,
|
|
fn (cmos.RtcRegister) u8 => DataElementType.FN_IECMOSRTCREGISTER_OU8,
|
|
fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID,
|
|
fn (*const idt.IdtPtr) void => DataElementType.FN_IPTRCONSTIDTPTR_OVOID,
|
|
fn () gdt.GdtPtr => DataElementType.FN_OGDTPTR,
|
|
fn () idt.IdtPtr => DataElementType.FN_OIDTPTR,
|
|
fn (u8, fn () callconv(.C) void) idt.IdtError!void => DataElementType.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID,
|
|
fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => DataElementType.FN_IU8_INFNOVOID_OERRORIDTERRORVOID,
|
|
fn (idt.IdtEntry) bool => DataElementType.FN_IIDTENTRY_OBOOL,
|
|
fn (*task.Task, usize) void => DataElementType.FN_IPTRTask_IUSIZE_OVOID,
|
|
fn (*task.Task, *std.mem.Allocator) void => DataElementType.FN_IPTRTASK_IPTRALLOCATOR_OVOID,
|
|
fn (fn () void) std.mem.Allocator.Error!*task.Task => DataElementType.FN_IFNOVOID_OMEMERRORPTRTASK,
|
|
fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task => DataElementType.FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK,
|
|
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) {
|
|
bool => element.BOOL,
|
|
u4 => element.U4,
|
|
u8 => element.U8,
|
|
u16 => element.U16,
|
|
u32 => element.U32,
|
|
usize => element.USIZE,
|
|
*std.mem.Allocator => element.PTR_ALLOCATOR,
|
|
cmos.StatusRegister => element.ECMOSSTATUSREGISTER,
|
|
gdt.GdtPtr => element.GDTPTR,
|
|
idt.IdtPtr => element.IDTPTR,
|
|
idt.IdtEntry => element.IDTENTRY,
|
|
cmos.RtcRegister => element.ECMOSRTCREGISTER,
|
|
*const gdt.GdtPtr => element.PTR_CONST_GDTPTR,
|
|
*const idt.IdtPtr => element.PTR_CONST_IDTPTR,
|
|
idt.IdtError!void => element.ERROR_IDTERROR_VOID,
|
|
std.mem.Allocator.Error!*task.Task => element.ERROR_MEM_PTRTASK,
|
|
*task.Task => element.PTR_TASK,
|
|
fn () callconv(.C) void => element.EFN_OVOID,
|
|
fn () callconv(.Naked) void => element.NFN_OVOID,
|
|
fn () void => element.FN_OVOID,
|
|
fn () usize => element.FN_OUSIZE,
|
|
fn () u16 => element.FN_OU16,
|
|
fn (u8) bool => element.FN_IU8_OBOOL,
|
|
fn (u8) void => element.FN_IU8_OVOID,
|
|
fn (u16) void => element.FN_IU16_OVOID,
|
|
fn (usize) void => element.FN_IUSIZE_OVOID,
|
|
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,
|
|
fn (cmos.StatusRegister, bool) u8 => element.FN_IECMOSSTATUSREGISTER_IBOOL_OU8,
|
|
fn (cmos.StatusRegister, u8, bool) void => element.FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID,
|
|
fn (cmos.RtcRegister) u8 => element.FN_IECMOSRTCREGISTER_OU8,
|
|
fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_OVOID,
|
|
fn (*const idt.IdtPtr) void => element.FN_IPTRCONSTIDTPTR_OVOID,
|
|
fn (u8, fn () callconv(.C) void) idt.IdtError!void => element.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID,
|
|
fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => element.FN_IU8_INFNOVOID_OERRORIDTERRORVOID,
|
|
fn () gdt.GdtPtr => element.FN_OGDTPTR,
|
|
fn () idt.IdtPtr => element.FN_OIDTPTR,
|
|
fn (idt.IdtEntry) bool => element.FN_IIDTENTRY_OBOOL,
|
|
fn (*task.Task, usize) void => element.FN_IPTRTask_IUSIZE_OVOID,
|
|
fn (*task.Task, *std.mem.Allocator) void => element.FN_IPTRTASK_IPTRALLOCATOR_OVOID,
|
|
fn (fn () void) std.mem.Allocator.Error!*task.Task => element.FN_IFNOVOID_OMEMERRORPTRTASK,
|
|
fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task => element.FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK,
|
|
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: anytype - 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: anytype) 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 " ++ 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
|
|
std.debug.panic("Can not test a value for void\n", .{});
|
|
}
|
|
|
|
// Test that the types match
|
|
const expect_type = comptime getDataElementType(ExpectedType);
|
|
expectEqual(expect_type, @as(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(@as(DataElementType, action.data), expect_type);
|
|
|
|
// Free the node
|
|
action_list.*.destroyNode(action_node, GlobalAllocator);
|
|
|
|
return ret;
|
|
} else {
|
|
std.debug.panic("No more test values for the return of function: " ++ fun_name ++ "\n", .{});
|
|
}
|
|
}
|
|
|
|
///
|
|
/// 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: anytype - The data to add.
|
|
/// IN action_type: ActionType - The action type to add.
|
|
///
|
|
pub fn addAction(self: *Self, comptime fun_name: []const u8, data: anytype, action_type: ActionType) void {
|
|
// Add a new mapping if one doesn't exist.
|
|
if (!self.named_actions.contains(fun_name)) {
|
|
self.named_actions.put(fun_name, TailQueue(Action).init()) catch unreachable;
|
|
}
|
|
|
|
// Get the function mapping to add the parameter to.
|
|
if (self.named_actions.getEntry(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 ;)
|
|
std.debug.panic("No function name: " ++ fun_name ++ "\n", .{});
|
|
}
|
|
}
|
|
|
|
///
|
|
/// 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: anytype - 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: anytype) RetType {
|
|
if (self.named_actions.getEntry(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
|
|
|
|
comptime const param_len = [1]u8{switch (params.len) {
|
|
0...9 => params.len + @as(u8, '0'),
|
|
else => unreachable,
|
|
}};
|
|
|
|
const expected_function = switch (params.len) {
|
|
0 => fn () RetType,
|
|
1 => fn (@TypeOf(params[0])) RetType,
|
|
2 => fn (@TypeOf(params[0]), @TypeOf(params[1])) RetType,
|
|
3 => fn (@TypeOf(params[0]), @TypeOf(params[1]), @TypeOf(params[2])) RetType,
|
|
else => @compileError("Couldn't generate function type for " ++ param_len ++ " parameters\n"),
|
|
};
|
|
|
|
// Get the corresponding DataElementType
|
|
const expect_type = comptime getDataElementType(expected_function);
|
|
|
|
// Test that the types match
|
|
expectEqual(expect_type, @as(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 => actual_function(),
|
|
1 => actual_function(params[0]),
|
|
2 => actual_function(params[0], params[1]),
|
|
3 => actual_function(params[0], params[1], params[2]),
|
|
else => @compileError(param_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;
|
|
|
|
comptime const param_len = [1]u8{switch (params.len) {
|
|
0...9 => params.len + @as(u8, '0'),
|
|
else => unreachable,
|
|
}};
|
|
|
|
const expected_function = switch (params.len) {
|
|
0 => fn () RetType,
|
|
1 => fn (@TypeOf(params[0])) RetType,
|
|
2 => fn (@TypeOf(params[0]), @TypeOf(params[1])) RetType,
|
|
3 => fn (@TypeOf(params[0]), @TypeOf(params[1]), @TypeOf(params[2])) RetType,
|
|
else => @compileError("Couldn't generate function type for " ++ param_len ++ " parameters\n"),
|
|
};
|
|
|
|
// Get the corresponding DataElementType
|
|
const expect_type = comptime getDataElementType(expected_function);
|
|
|
|
// Test that the types match
|
|
expectEqual(expect_type, @as(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 => actual_function(),
|
|
1 => actual_function(params[0]),
|
|
2 => actual_function(params[0], params[1]),
|
|
3 => actual_function(params[0], params[1], params[2]),
|
|
else => @compileError(param_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 {
|
|
std.debug.panic("No action list elements for function: " ++ fun_name ++ "\n", .{});
|
|
}
|
|
} else {
|
|
std.debug.panic("No function name: " ++ fun_name ++ "\n", .{});
|
|
}
|
|
}
|
|
|
|
///
|
|
/// 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
|
|
std.debug.panic("Unused testing value: Type: {}, value: {} for function '{}'\n", .{ action.action, @as(DataElementType, action.data), next.key });
|
|
},
|
|
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 initialise 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: anytype - The parameters to add.
|
|
///
|
|
pub fn addTestParams(comptime fun_name: []const u8, params: anytype) 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: anytype - The function to add.
|
|
///
|
|
pub fn addConsumeFunction(comptime fun_name: []const u8, function: anytype) 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: anytype - The function to add.
|
|
///
|
|
pub fn addRepeatFunction(comptime fun_name: []const u8, function: anytype) 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: anytype - 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: anytype) RetType {
|
|
return getMockObject().performAction(fun_name, RetType, params);
|
|
}
|