You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
ndg/src/test.zig

264 lines
7.8 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const nif = @import("nif");
comptime {
if (!builtin.is_test) @compileError("test-only module");
}
export fn wifi_ssid_add_network(name: [*:0]const u8) void {
_ = name;
}
export fn lv_timer_del(timer: *opaque {}) void {
_ = timer;
}
export fn lv_disp_get_inactive_time(disp: *opaque {}) u32 {
_ = disp;
return 0;
}
/// TestTimer always reports the same fixed value.
pub const TestTimer = struct {
value: u64,
started: bool = false, // true if called start
resetted: bool = false, // true if called reset
pub fn start() std.time.Timer.Error!TestTimer {
return .{ .value = 42 };
}
pub fn reset(self: *TestTimer) void {
self.resetted = true;
}
pub fn read(self: *TestTimer) u64 {
return self.value;
}
};
/// args in init are dup'ed using an allocator.
/// the caller must deinit in the end.
pub const TestChildProcess = struct {
// test hooks
spawn_callback: ?*const fn (*TestChildProcess) std.ChildProcess.SpawnError!void = null,
wait_callback: ?*const fn (*TestChildProcess) anyerror!std.ChildProcess.Term = null,
kill_callback: ?*const fn (*TestChildProcess) anyerror!std.ChildProcess.Term = null,
spawned: bool = false,
waited: bool = false,
killed: bool = false,
// original std ChildProcess init args
allocator: std.mem.Allocator,
argv: []const []const u8,
pub fn init(argv: []const []const u8, allocator: std.mem.Allocator) TestChildProcess {
var adup = allocator.alloc([]u8, argv.len) catch unreachable;
for (argv) |v, i| {
adup[i] = allocator.dupe(u8, v) catch unreachable;
}
return .{
.allocator = allocator,
.argv = adup,
};
}
pub fn deinit(self: *TestChildProcess) void {
for (self.argv) |v| self.allocator.free(v);
self.allocator.free(self.argv);
}
pub fn spawn(self: *TestChildProcess) std.ChildProcess.SpawnError!void {
defer self.spawned = true;
if (self.spawn_callback) |cb| {
return cb(self);
}
}
pub fn wait(self: *TestChildProcess) anyerror!std.ChildProcess.Term {
defer self.waited = true;
if (self.wait_callback) |cb| {
return cb(self);
}
return .{ .Exited = 0 };
}
pub fn spawnAndWait(self: *TestChildProcess) !std.ChildProcess.Term {
try self.spawn();
return self.wait();
}
pub fn kill(self: *TestChildProcess) !std.ChildProcess.Term {
defer self.killed = true;
if (self.kill_callback) |cb| {
return cb(self);
}
return .{ .Exited = 0 };
}
};
/// a nif.wpa.Control stub for tests.
pub const TestWpaControl = struct {
ctrl_path: []const u8,
opened: bool,
attached: bool = false,
scanned: bool = false,
saved: bool = false,
const Self = @This();
pub fn open(path: [:0]const u8) !Self {
return .{ .ctrl_path = path, .opened = true };
}
pub fn close(self: *Self) !void {
self.opened = false;
}
pub fn attach(self: *Self) !void {
self.attached = true;
}
pub fn detach(self: *Self) !void {
self.attached = false;
}
pub fn pending(_: Self) !bool {
return false;
}
pub fn receive(_: Self, _: [:0]u8) ![]const u8 {
return &.{};
}
pub fn scan(self: *Self) !void {
self.scanned = true;
}
pub fn saveConfig(self: *Self) !void {
self.saved = true;
}
pub fn request(_: Self, _: [:0]const u8, _: [:0]u8, _: ?nif.wpa.ReqCallback) ![]const u8 {
return &.{};
}
pub fn addNetwork(_: *Self) !u32 {
return 12345;
}
pub fn removeNetwork(_: *Self, id: u32) !void {
_ = id;
}
pub fn setNetworkParam(_: *Self, id: u32, name: []const u8, val: []const u8) !void {
_ = id;
_ = name;
_ = val;
}
pub fn selectNetwork(_: *Self, id: u32) !void {
_ = id;
}
pub fn enableNetwork(_: *Self, id: u32) !void {
_ = id;
}
};
/// similar to std.testing.expectEqual but compares slices with expectEqualSlices
/// or expectEqualStrings where slice element is a u8.
pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void {
const t = std.testing;
switch (@typeInfo(@TypeOf(actual))) {
.Pointer => |p| {
switch (p.size) {
.One => try expectDeepEqual(expected.*, actual.*),
.Slice => {
switch (@typeInfo(p.child)) {
.Pointer, .Struct, .Optional, .Union => {
var err: ?anyerror = blk: {
if (expected.len != actual.len) {
std.debug.print("expected.len = {d}, actual.len = {d}\n", .{ expected.len, actual.len });
break :blk error.ExpectDeepEqual;
}
break :blk null;
};
const n = std.math.min(expected.len, actual.len);
var i: usize = 0;
while (i < n) : (i += 1) {
expectDeepEqual(expected[i], actual[i]) catch |e| {
std.debug.print("unequal slice elements at index {d}\n", .{i});
return e;
};
}
if (err) |e| {
return e;
}
},
else => {
if (p.child == u8) {
try t.expectEqualStrings(expected, actual);
} else {
try t.expectEqualSlices(p.child, expected, actual);
}
},
}
},
else => try t.expectEqual(expected, actual),
}
},
.Struct => |st| {
inline for (st.fields) |f| {
expectDeepEqual(@field(expected, f.name), @field(actual, f.name)) catch |err| {
std.debug.print("unequal field '{s}' of struct {any}\n", .{ f.name, @TypeOf(actual) });
return err;
};
}
},
.Optional => {
if (expected) |x| {
if (actual) |v| {
try expectDeepEqual(x, v);
} else {
std.debug.print("expected {any}, found null\n", .{x});
return error.TestExpectDeepEqual;
}
} else {
if (actual) |v| {
std.debug.print("expected null, found {any}\n", .{v});
return error.TestExpectDeepEqual;
}
}
},
.Union => |u| {
if (u.tag_type == null) {
@compileError("unable to compare untagged union values");
}
const Tag = std.meta.Tag(@TypeOf(expected));
const atag = @as(Tag, actual);
try t.expectEqual(@as(Tag, expected), atag);
inline for (u.fields) |f| {
if (std.mem.eql(u8, f.name, @tagName(atag))) {
try expectDeepEqual(@field(expected, f.name), @field(actual, f.name));
return;
}
}
unreachable;
},
else => {
try t.expectEqual(expected, actual);
},
}
}
test {
_ = @import("nd.zig");
_ = @import("nd/Daemon.zig");
_ = @import("nd/SysService.zig");
_ = @import("ngui.zig");
std.testing.refAllDecls(@This());
}