const std = @import("std");
const io = std.io;
const math = std.math;
const mem = std.mem;
const testing = std.testing;
const bare = @import("bare.zig");
const Reader = bare.Reader;
const Writer = bare.Writer;
const ReadError = bare.ReadError;
const WriteError = bare.WriteError;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
const expectEqualSlices = testing.expectEqualSlices;
const expectError = testing.expectError;
const warn = std.debug.warn;
test "" {
std.testing.refAllDecls(bare);
}
test "read u8" {
var r = Reader.init(testing.allocator);
expectEqual(try r.read(u8, io.fixedBufferStream("\x00").reader()), 0);
expectEqual(try r.read(u8, io.fixedBufferStream("\xff").reader()), 255);
expectEqual(try r.read(u8, io.fixedBufferStream("\x2a").reader()), 42);
}
test "read bool" {
var r = Reader.init(testing.allocator);
expectEqual(try r.read(bool, io.fixedBufferStream("\x00").reader()), false);
expectEqual(try r.read(bool, io.fixedBufferStream("\xff").reader()), true);
expectEqual(try r.read(bool, io.fixedBufferStream("\x01").reader()), true);
}
test "read struct 1" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2a\xaa").reader());
expectEqual(res.a, 42.0);
expectEqual(res.b, 42);
expectEqual(res.c, true);
}
test "read struct 2" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2a\x00").reader());
expectEqual(res.a, 42.0);
expectEqual(res.b, 42);
expectEqual(res.c, false);
}
test "read struct 3" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2b\x00").reader());
expectEqual(res.a, 42.0);
expectEqual(res.b, 43);
expectEqual(res.c, false);
}
test "read struct 4" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x29\x42\x2b\x00").reader());
expectEqual(res.a, 42.25);
expectEqual(res.b, 43);
expectEqual(res.c, false);
}
test "read struct 5" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x00\x00\x00\x00").reader());
expectEqual(res.a, 0.0);
expectEqual(res.b, 0);
expectEqual(res.c, false);
}
test "read struct 6" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\xff\xff\xff\xff\xff\xff").reader());
expect(math.isNan(res.a));
expectEqual(res.b, 255);
expectEqual(res.c, true);
}
test "read struct with void members" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ const Foo = struct { a: u8, b: void };
\\ _ = try Reader.init(testing.allocator).read(Foo, fbs.reader());
\\}
, "unsupported type void"
);
}
test "read enum" {
const Foo = enum {
a = 2,
b = 1,
c = 0,
};
var res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x02").reader());
expectEqual(res, .a);
res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x01").reader());
expectEqual(res, .b);
res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00").reader());
expectEqual(res, .c);
}
test "read optional u8 value" {
const res = try Reader.init(testing.allocator).read(?u8, io.fixedBufferStream("\x01\x2a").reader());
expectEqual(res, 42);
}
test "read optional u8 null" {
const res = try Reader.init(testing.allocator).read(?u8, io.fixedBufferStream("\x00").reader());
expectEqual(res, null);
}
test "read u8 array" {
const res = try Reader.init(testing.allocator).read([4]u8, io.fixedBufferStream("\x01\x02\x03\x04").reader());
const expected = [4]u8{ 1, 2, 3, 4 };
expectEqual(res, expected);
}
test "read u8 slice" {
const res = try Reader.init(testing.allocator).read([]const u8, io.fixedBufferStream("\x04\x01\x02\x03\x04").reader());
defer testing.allocator.free(res);
const expected = [_]u8{ 1, 2, 3, 4 };
expectEqualSlices(u8, expected[0..], res[0..]);
}
test "read 0-length slice" {
const res = try Reader.init(testing.allocator).read([]const u8, io.fixedBufferStream("\x00").reader());
defer testing.allocator.free(res);
const expected = [_]u8{};
expectEqualSlices(u8, expected[0..], res[0..]);
}
test "read map[u8]u8" {
var r = Reader.init(testing.allocator);
defer r.deinit();
const map = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").reader());
expectEqual(map.get(1) orelse unreachable, 2);
expectEqual(map.get(3) orelse unreachable, 4);
}
test "read map[u8]u8 with 0 items" {
var r = Reader.init(testing.allocator);
defer r.deinit();
const map = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x00").reader());
expectEqual(map.count(), 0);
}
test "read map[string]u8" {
var r = Reader.init(testing.allocator);
defer r.deinit();
const T = std.StringHashMap(u8);
const map = try r.read(T, io.fixedBufferStream("\x02\x04zomg\x04\x03lol\x02").reader());
expectEqual(map.get("lol") orelse unreachable, 2);
expectEqual(map.get("zomg") orelse unreachable, 4);
}
test "read map[u8]void" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ const HM = std.AutoHashMap(u8, void);
\\ _ = try Reader.init(testing.allocator).read(HM, fbs.reader());
\\}
, "unsupported type void"
);
}
test "read tagged union" {
const Foo = union(enum) { a: i64, b: bool, c: u8 };
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x02\x2a").reader());
expectEqual(Foo{ .c = 42 }, res);
}
test "read untagged union" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = union { a: u8, b: void };
\\ var fbs = io.fixedBufferStream("lol");
\\ _ = try Reader.init(testing.allocator).read(Foo, fbs.reader());
\\}
, "only tagged unions are supported"
);
}
test "read void" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ _ = try Reader.init(testing.allocator).read(void, fbs.reader());
\\}
, "unsupported type void"
);
}
test "read unsupported integer type" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ _ = try Reader.init(testing.allocator).read(u7, fbs.reader());
\\}
, "unsupported integer type u7"
);
}
test "read unsupported float type" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ _ = try Reader.init(testing.allocator).read(f16, fbs.reader());
\\}
, "unsupported float type f16"
);
}
test "read unsupported pointer type" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ _ = try Reader.init(testing.allocator).read(*u8, fbs.reader());
\\}
, "slices are the only supported pointer type"
);
}
test "write u8" {
var buf: [1]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const x: u8 = 42;
try Writer.init().write(x, fbs.writer());
expectEqual(fbs.getWritten()[0], x);
}
test "write bool" {
var buf: [2]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
try Writer.init().write(false, fbs.writer());
try Writer.init().write(true, fbs.writer());
expectEqual(fbs.getWritten()[0], 0);
expectEqual(fbs.getWritten()[1], 1);
}
test "write struct" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
var buf: [@sizeOf(Foo)]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const expected = "\x00\x00\x28\x42\x2a\x01";
try Writer.init().write(Foo{ .a = 42.0, .b = 42, .c = true }, fbs.writer());
expect(mem.eql(u8, fbs.getWritten(), expected));
}
test "write struct with void members" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = struct { x: u8, y: void };
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ try Writer.init().write(Foo{ .x = 1, .y = {} }, fbs.writer());
\\}
, "unsupported type"
);
}
test "write enum" {
const Foo = enum {
a,
b,
c = 2,
};
var buf: [@sizeOf(Foo)]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const expected = "\x02";
try Writer.init().write(Foo.c, fbs.writer());
expect(mem.eql(u8, fbs.getWritten(), expected));
}
test "write optional u8 value" {
var buf: [2]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
var x: ?u8 = 42;
try Writer.init().write(x, fbs.writer());
expectEqual(fbs.getWritten()[0], @boolToInt(true));
expectEqual(fbs.getWritten()[1], 42);
}
test "write optional u8 null" {
var buf: [2]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
var x: ?u8 = null;
try Writer.init().write(x, fbs.writer());
expectEqual(fbs.getWritten().len, 1);
expectEqual(fbs.getWritten()[0], @boolToInt(false));
}
test "write optional void" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const foo: ?void = null;
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "unsupported type"
);
}
test "write u8 array" {
var buf: [4]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
var x = [4]u8{ 1, 2, 3, 4 };
try Writer.init().write(x, fbs.writer());
const expected = [_]u8{ 1, 2, 3, 4 };
expectEqual(fbs.getWritten().len, 4);
expectEqualSlices(u8, fbs.getWritten()[0..], expected[0..]);
}
test "write array of void" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const foo: [4]void = .{ {}, {}, {}, {} };
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "unsupported type"
);
}
test "write slice" {
var buf: [5]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const expected = [_]u8{ 1, 2, 3, 4 };
const slajs: []const u8 = expected[0..];
try Writer.init().write(slajs, fbs.writer());
expectEqual(fbs.getWritten().len, 5);
expectEqual(fbs.getWritten()[0], 4);
expectEqualSlices(u8, fbs.getWritten()[1..], expected[0..]);
}
test "write slice of void" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const foo: []const void = &[_]void{ {}, {} };
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "unsupported type void"
);
}
test "write map[u8]u8" {
var buf: [5]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
var map = std.AutoHashMap(u8, u8).init(testing.allocator);
defer map.deinit();
_ = try map.put(1, 2);
_ = try map.put(3, 4);
try Writer.init().write(map, fbs.writer());
const expected = "\x02\x01\x02\x03\x04";
expectEqual(fbs.getWritten().len, 5);
expectEqualSlices(u8, fbs.getWritten(), expected[0..]);
}
test "write map[string]u8" {
var buf: [100]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
var map = std.StringHashMap(u8).init(testing.allocator);
defer map.deinit();
_ = try map.put("lol", 2);
_ = try map.put("zomg", 4);
try Writer.init().write(map, fbs.writer());
// TODO: Fix this; later versions of std.HashMap preserve insertion order.
const expected = "\x02\x04zomg\x04\x03lol\x02";
expectEqual(fbs.getWritten().len, 12);
expectEqualSlices(u8, fbs.getWritten(), expected[0..]);
}
test "write map[u8]void" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const foo = std.AutoHashMap(u8, void);
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "unsupported type"
);
}
test "write tagged union" {
var buf: [10]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const Foo = union(enum) { a: i64, b: bool, c: u8 };
const foo = Foo{ .a = 42 };
try Writer.init().write(foo, fbs.writer());
const expected = "\x00\x2a\x00\x00\x00\x00\x00\x00\x00";
expectEqual(fbs.getWritten().len, 1 + @sizeOf(i64));
expectEqualSlices(u8, fbs.getWritten(), expected[0..]);
}
test "write tagged union with void member active" {
var buf: [10]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const Foo = union(enum) { a: i64, b: void, c: u8 };
const foo = Foo{ .b = {} };
try Writer.init().write(foo, fbs.writer());
const expected = "\x01";
expectEqualSlices(u8, fbs.getWritten(), expected[0..]);
}
test "write untagged union" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = union { a: u8, b: void };
\\ var fbs = io.fixedBufferStream("lol");
\\ _ = try Writer.init().write(Foo{ .a = 2 }, fbs.writer());
\\}
, "only tagged unions are supported"
);
}
test "write void" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ try Writer.init().write(void, fbs.writer());
\\}
, "unsupported type" // TODO: I get `type` as the name here.
);
}
test "write unsupported integer type" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const foo: u7 = 7;
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "unsupported integer type u7"
);
}
test "write unsupported float type" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const foo: f16 = 4.2;
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "unsupported float type f16"
);
}
test "write unsupported pointer type" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ const x: u8 = 2;
\\ const foo: *const u8 = &x;
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "slices are the only supported pointer type"
);
}
test "round trip u8" {
var buf: [0x100]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const x: u8 = 42;
try Writer.init().write(x, fbs.writer());
const res = try Reader.init(testing.allocator).read(u8, io.fixedBufferStream(fbs.getWritten()).reader());
expectEqual(res, x);
}
test "round trip struct" {
const Foo = struct {
a: f32,
b: u8,
c: bool,
};
const foo = Foo{ .a = 42.0, .b = 3, .c = true };
var buf: [@sizeOf(Foo)]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
try Writer.init().write(foo, fbs.writer());
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());
expectEqual(res, foo);
}
test "round trip enum" {
const Foo = enum {
a,
b,
c = 2,
};
var buf: [@sizeOf(Foo)]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
try Writer.init().write(Foo.c, fbs.writer());
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());
expectEqual(res, Foo.c);
}
test "round trip i64 array" {
var buf: [0x100]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const arr = [_]i64{ -1, 1, math.maxInt(i64), math.minInt(i64) };
try Writer.init().write(arr, fbs.writer());
const res = try Reader.init(testing.allocator).read(@TypeOf(arr), io.fixedBufferStream(fbs.getWritten()).reader());
expectEqualSlices(i64, arr[0..], res[0..]);
}
test "round trip i32 slice" {
var buf: [0x100]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
const expected = [4]i32{ -1, 2, math.maxInt(i32), math.minInt(i32) };
const slajs: []const i32 = expected[0..];
try Writer.init().write(slajs, fbs.writer());
const res = try Reader.init(testing.allocator).read([]const i32, io.fixedBufferStream(fbs.getWritten()).reader());
defer testing.allocator.free(res);
expectEqualSlices(i32, slajs, res[0..]);
}
test "round trip tagged union" {
const Foo = union(enum) {
a: i64,
b: u32,
c: bool,
};
const foo = Foo{ .b = 42 };
var buf: [0x100]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
try Writer.init().write(foo, fbs.writer());
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());
expectEqual(foo, res);
}
test "round trip tagged union with void member active" {
const Foo = union(enum) {
a: i64,
b: void,
c: bool,
};
const foo = Foo{ .b = {} };
var buf: [0x100]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);
try Writer.init().write(foo, fbs.writer());
const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());
expectEqual(foo, res);
}
test "bare.Reader frees its memory" {
var r = Reader.init(testing.allocator);
_ = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").reader());
_ = try r.read([]const u8, io.fixedBufferStream("\x04\x01\x02\x03\x04").reader());
defer r.deinit();
// testing.allocator will yell if memory is leaked
}
test "invariant: fixed-length array must have length > 0 (read)" {
try testCompileError(
\\pub fn main() void {
\\ var fbs = io.fixedBufferStream("lol");
\\ try Reader.init(testing.allocator).read([0]u8, fbs.reader());
\\}
, "array length must be at least 1"
);
}
test "invariant: fixed-length array must have length > 0 (write)" {
try testCompileError(
\\pub fn main() void {
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ var foo = [0]u8{};
\\ try Writer.init().write(foo, fbs.writer());
\\}
, "array length must be at least 1"
);
}
test "invariant: structs must have at least 1 field (read)" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = struct {};
\\ var fbs = io.fixedBufferStream("lol");
\\ try Reader.init(testing.allocator).read(Foo, fbs.reader());
\\}
, "structs must have 1 or more fields"
);
}
test "invariant: structs must have at least 1 field (write)" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = struct {};
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ try Writer.init().write(Foo{}, fbs.writer());
\\}
, "structs must have 1 or more fields"
);
}
test "invariant: unions must have at least 1 field (read)" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = union(enum) {};
\\ var fbs = io.fixedBufferStream("lol");
\\ try Reader.init(testing.allocator).read(Foo{}, fbs.reader());
\\}
, "unions must have 1 or more fields"
);
}
test "invariant: unions must have at least 1 field (write)" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = union(enum) {};
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ try Writer.init().write(Foo{}, fbs.writer());
\\}
, "unions must have 1 or more fields"
);
}
test "invariant: hashmap keys must be of primitive type" {
try testCompileError(
\\pub fn main() void {
\\ const hm = std.AutoHashMap([64]u8, void).init(testing.allocator);
\\ var buf: [0x100]u8 = undefined;
\\ var fbs = io.fixedBufferStream(&buf);
\\ try Writer.init().write(hm, fbs.writer());
\\}
, "unsupported hashmap key type [64]u8"
);
}
test "invariant: enum values must be unique" {
try testCompileError(
\\pub fn main() void {
\\ const Foo = enum {
\\ x = 1,
\\ y = 1,
\\ };
\\}
, "enum tag value 1 already taken"
);
}
const fs = std.fs;
const boilerplate =
\\const std = @import("std");
\\const io = std.io;
\\const math = std.math;
\\const mem = std.mem;
\\const testing = std.testing;
\\
\\const bare = @import("zig-bare.zig");
\\const Reader = bare.Reader;
\\const Writer = bare.Writer;
;
fn testCompileError(comptime code: []const u8, comptime expected_error: []const u8) !void {
const fname = "/tmp/zig-bare-test.zig";
var cwd = fs.cwd();
try cwd.copyFile("src/bare.zig", cwd, "/tmp/zig-bare.zig", .{});
var tmpfile = try cwd.createFile(fname, .{ .read = true, .truncate = true });
defer tmpfile.close();
_ = try tmpfile.write(boilerplate);
_ = try tmpfile.write(code);
var proc = try std.ChildProcess.init(&[_][]const u8{ "zig", "build-exe", fname }, testing.allocator);
defer proc.deinit();
proc.stderr_behavior = .Pipe;
proc.stdout_behavior = .Pipe;
try proc.spawn();
const stderr = try proc.stderr.?.reader().readAllAlloc(testing.allocator, 0x1000);
const term = try proc.wait();
defer testing.allocator.free(stderr);
try checkErrorMessage(stderr, expected_error);
expectEqual(term.Exited, 1);
}
fn checkErrorMessage(haystack: []const u8, message: []const u8) !void {
const errorstart = "error: ";
const idx = mem.indexOf(u8, haystack, errorstart) orelse {
expect(false);
return;
};
const errorslice = haystack[idx + errorstart.len..];
const starts_with = mem.startsWith(u8, errorslice, message);
if (!starts_with)
warn("\nunexpected output: {}\n", .{ errorslice });
expect(starts_with);
}