Add more test coverage and clean up code
This commit is contained in:
parent
9251b11427
commit
4bb86c41bc
|
@ -13,7 +13,7 @@ pub const Allocation = struct {
|
|||
};
|
||||
|
||||
///
|
||||
/// Dynamic memory allocation interface.
|
||||
/// Closure for dynamic memory allocation through the referenced allocator state machine capture.
|
||||
///
|
||||
pub const Allocator = meta.Function(Allocation, ?[*]u8);
|
||||
|
||||
|
@ -78,7 +78,7 @@ pub fn Spliterator(comptime Element: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
test "Spliterator of string literals" {
|
||||
test "Spliterator(u8)" {
|
||||
// Empty source.
|
||||
{
|
||||
var spliterator = Spliterator(u8){
|
||||
|
@ -172,7 +172,7 @@ pub fn bytesOf(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) {
|
|||
}
|
||||
}
|
||||
|
||||
test "Bytes of types" {
|
||||
test "bytesOf" {
|
||||
var foo: u32 = 10;
|
||||
|
||||
try testing.expect(bytesOf(&foo)[0] == 0x0a);
|
||||
|
@ -195,7 +195,7 @@ pub fn compareBytes(this: []const u8, that: []const u8) isize {
|
|||
return (@intCast(isize, this.len) - @intCast(isize, that.len));
|
||||
}
|
||||
|
||||
test "Compare bytes" {
|
||||
test "compareBytes" {
|
||||
try testing.expect(compareBytes(&.{69, 42, 0}, &.{69, 42, 0}) == 0);
|
||||
try testing.expect(compareBytes(&.{69, 42, 11}, &.{69, 42}) == 1);
|
||||
try testing.expect(compareBytes(&.{69, 42}, &.{69, 42, 11}) == -1);
|
||||
|
@ -208,7 +208,7 @@ pub fn copy(comptime Element: type, target: []Element, source: []const Element)
|
|||
for (source) |element, index| target[index] = element;
|
||||
}
|
||||
|
||||
test "Copy data" {
|
||||
test "copy" {
|
||||
var buffer = [_]u32{0} ** 20;
|
||||
const data = [_]u32{3, 20, 8000};
|
||||
|
||||
|
@ -231,7 +231,7 @@ pub fn equals(comptime Element: type, this: []const Element, that: []const Eleme
|
|||
return true;
|
||||
}
|
||||
|
||||
test "Check memory is equal" {
|
||||
test "equals" {
|
||||
const bytes_sequence = &.{69, 42, 0};
|
||||
|
||||
try testing.expect(equals(u8, bytes_sequence, bytes_sequence));
|
||||
|
@ -245,7 +245,7 @@ pub fn fill(comptime Element: type, target: []Element, source: Element) void {
|
|||
for (target) |_, index| target[index] = source;
|
||||
}
|
||||
|
||||
test "Fill data" {
|
||||
test "fill" {
|
||||
var buffer = [_]u32{0} ** 8;
|
||||
|
||||
fill(u32, &buffer, 1);
|
||||
|
@ -265,7 +265,7 @@ pub fn findFirst(comptime Element: type, haystack: []const Element,
|
|||
return null;
|
||||
}
|
||||
|
||||
test "Find first of element" {
|
||||
test "findFirst" {
|
||||
const haystack = &.{"", "", "foo"};
|
||||
|
||||
const testEquality = struct {
|
||||
|
@ -298,7 +298,7 @@ pub fn findFirstOf(comptime Element: type, haystack: []const Element,
|
|||
return null;
|
||||
}
|
||||
|
||||
test "Find first of sequence" {
|
||||
test "findFirstOf" {
|
||||
const haystack = &.{"foo", "bar", "baz"};
|
||||
|
||||
const testEquality = struct {
|
||||
|
@ -333,6 +333,16 @@ pub fn free(allocator: Allocator, allocated_memory: anytype) void {
|
|||
}) != null) unreachable;
|
||||
}
|
||||
|
||||
test "free" {
|
||||
var buffer = [_]u8{0} ** 4096;
|
||||
var memory = stack.Fixed(u8){.buffer = &buffer};
|
||||
const fixed_allocator = stack.fixedAllocator(&memory);
|
||||
const block_size = 8;
|
||||
const allocated_block = (try makeMany(u8, fixed_allocator, block_size))[0 .. block_size];
|
||||
|
||||
defer free(fixed_allocator, allocated_block);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a deterministic hash code compiled from each byte in `bytes`.
|
||||
///
|
||||
|
@ -346,7 +356,7 @@ pub fn hashBytes(bytes: []const u8) usize {
|
|||
return hash;
|
||||
}
|
||||
|
||||
test "Hashing bytes" {
|
||||
test "hashBytes" {
|
||||
const bytes_sequence = &.{69, 42, 0};
|
||||
|
||||
try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence));
|
||||
|
@ -360,15 +370,42 @@ test "Hashing bytes" {
|
|||
pub fn makeMany(comptime Element: type, allocator: Allocator, size: usize) MakeError![*]Element {
|
||||
const alignment = @alignOf(Element);
|
||||
|
||||
if (allocator.call(.{
|
||||
return @ptrCast([*]Element, @alignCast(alignment, allocator.call(.{
|
||||
.existing = null,
|
||||
.size = @sizeOf(Element) * size,
|
||||
.alignment = alignment,
|
||||
})) |buffer| {
|
||||
return @ptrCast([*]Element, @alignCast(alignment, buffer));
|
||||
}
|
||||
}) orelse return error.OutOfMemory));
|
||||
}
|
||||
|
||||
return error.OutOfMemory;
|
||||
test "makeMany" {
|
||||
var buffer = [_]u8{0} ** 4096;
|
||||
var memory = stack.Fixed(u8){.buffer = &buffer};
|
||||
const block_size = 8;
|
||||
|
||||
// Don't care about the actual allocation - just assertions about it.
|
||||
_ = (try makeMany(u8, stack.fixedAllocator(&memory), block_size))[0 .. block_size];
|
||||
}
|
||||
|
||||
///
|
||||
/// Attempts to allocate a buffer of `1` `Element` using `allocator`, returning it or a [MakeError]
|
||||
/// if it failed.
|
||||
///
|
||||
pub fn makeOne(comptime Element: type, allocator: Allocator) MakeError!*Element {
|
||||
const alignment = @alignOf(Element);
|
||||
|
||||
return @ptrCast(*Element, @alignCast(alignment, allocator.call(.{
|
||||
.existing = null,
|
||||
.size = @sizeOf(Element),
|
||||
.alignment = alignment,
|
||||
}) orelse return error.OutOfMemory));
|
||||
}
|
||||
|
||||
test "makeOne" {
|
||||
var buffer = [_]u8{0} ** 4096;
|
||||
var memory = stack.Fixed(u8){.buffer = &buffer};
|
||||
|
||||
// Don't care about the actual allocation - just assertions about it.
|
||||
_ = try makeOne(u8, stack.fixedAllocator(&memory));
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -380,7 +417,7 @@ pub fn swap(comptime Data: type, this: *Data, that: *Data) void {
|
|||
that.* = temp;
|
||||
}
|
||||
|
||||
test "Data swapping" {
|
||||
test "swap" {
|
||||
var a: u64 = 0;
|
||||
var b: u64 = 1;
|
||||
|
||||
|
@ -403,7 +440,7 @@ pub const null_writer = Writer.from(struct {
|
|||
}
|
||||
}.write);
|
||||
|
||||
test "Null writing" {
|
||||
test "null_writer" {
|
||||
const sequence = "foo";
|
||||
|
||||
try testing.expect(null_writer.call(sequence) == sequence.len);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
const std = @import("std");
|
||||
const testing = @import("./testing.zig");
|
||||
|
||||
pub const IntFittingRange = @import("std").math.IntFittingRange;
|
||||
pub const IntFittingRange = std.math.IntFittingRange;
|
||||
|
||||
///
|
||||
/// Returns the maximum value of `Integer`.
|
||||
/// Returns the highest integer value representable by `Integer`.
|
||||
///
|
||||
pub fn maxInt(comptime Integer: type) comptime_int {
|
||||
pub fn maxIntValue(comptime Integer: type) comptime_int {
|
||||
return switch (@typeInfo(Integer)) {
|
||||
.Int => |info| if (info.bits == 0) 0 else
|
||||
((1 << (info.bits - @boolToInt(info.signedness == .signed))) - 1),
|
||||
|
@ -13,6 +15,32 @@ pub fn maxInt(comptime Integer: type) comptime_int {
|
|||
};
|
||||
}
|
||||
|
||||
test "maxIntValue" {
|
||||
try testing.expect(maxIntValue(u8) == 255);
|
||||
try testing.expect(maxIntValue(i8) == 127);
|
||||
|
||||
try testing.expect(maxIntValue(u16) == 65535);
|
||||
try testing.expect(maxIntValue(i16) == 32767);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the highest `Number` value between `this` and `that`.
|
||||
///
|
||||
pub fn max(comptime Number: type, this: Number, that: Number) Number {
|
||||
return switch (@typeInfo(Number)) {
|
||||
.Int, .Float, .ComptimeInt, .ComptimeFloat => if (this > that) this else that,
|
||||
|
||||
else => @compileError("`" ++ @typeName(Number) ++
|
||||
"` must be an int, float, comptime_int, or comptime_float"),
|
||||
};
|
||||
}
|
||||
|
||||
test "max" {
|
||||
try testing.expect(max(f32, 0.1, 1.0) == 1.0);
|
||||
try testing.expect(max(f64, 1.0, 1.01) == 1.01);
|
||||
try testing.expect(max(u32, 35615, 2873) == 35615);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the lowest `Number` value between `this` and `that`.
|
||||
///
|
||||
|
@ -24,3 +52,9 @@ pub fn min(comptime Number: type, this: Number, that: Number) Number {
|
|||
"` must be an int, float, comptime_int, or comptime_float"),
|
||||
};
|
||||
}
|
||||
|
||||
test "min" {
|
||||
try testing.expect(min(f32, 0.1, 1.0) == 0.1);
|
||||
try testing.expect(min(f64, 1.0, 1.01) == 1.0);
|
||||
try testing.expect(min(u32, 35615, 2873) == 2873);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,13 @@ pub fn Fixed(comptime Element: type) type {
|
|||
self.filled = 0;
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns `true` if `self` has filled its buffer to maximum capacity, otherwise `false`.
|
||||
///
|
||||
pub fn isFull(self: Self) bool {
|
||||
return (self.filled == self.buffer.len);
|
||||
}
|
||||
|
||||
///
|
||||
/// If `self` is filled with at least `1` value, it is decremented by `1`, otherwise leaving
|
||||
/// the actual memory contents of the buffer untouched until it is later overwritten by
|
||||
|
@ -42,7 +49,7 @@ pub fn Fixed(comptime Element: type) type {
|
|||
/// Attempts to push `element` into `self`, returning a [PushError] if it failed.
|
||||
///
|
||||
pub fn push(self: *Self, element: Element) PushError!void {
|
||||
if (self.filled == self.buffer.len) return error.OutOfMemory;
|
||||
if (self.isFull()) return error.OutOfMemory;
|
||||
|
||||
self.buffer[self.filled] = element;
|
||||
self.filled += 1;
|
||||
|
@ -77,7 +84,7 @@ pub fn Fixed(comptime Element: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
test "Fixed stack of string literals" {
|
||||
test "Fixed([]const u8)" {
|
||||
const default_value = "";
|
||||
var buffer = [_][]const u8{default_value} ** 4;
|
||||
var shopping_list = Fixed([]const u8){.buffer = &buffer};
|
||||
|
@ -140,12 +147,12 @@ pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator {
|
|||
if (allocation.existing) |buffer| if (allocation.size == 0) {
|
||||
// Deallocate the memory.
|
||||
const buffer_address = @ptrToInt(buffer);
|
||||
const stack_buffer_address = @ptrToInt(stack.buffer.ptr);
|
||||
const stack_address = @ptrToInt(stack.buffer.ptr);
|
||||
|
||||
// Check the buffer is within the address space of the stack buffer. If not, it
|
||||
// should just be returned to let the caller know it cannot be freed.
|
||||
if ((buffer_address < stack_buffer_address) or
|
||||
(buffer_address >= (stack_buffer_address + stack.filled))) return buffer;
|
||||
if (buffer_address < stack_address or buffer_address >=
|
||||
(stack_address + stack.filled)) return buffer;
|
||||
|
||||
// TODO: Investigate ways of freeing if it is the last allocation.
|
||||
return null;
|
||||
|
@ -167,6 +174,54 @@ pub fn fixedAllocator(fixed_stack: *Fixed(u8)) io.Allocator {
|
|||
}.alloc);
|
||||
}
|
||||
|
||||
test "fixedAllocator" {
|
||||
var buffer = [_]u8{0} ** 32;
|
||||
var stack = Fixed(u8){.buffer = &buffer};
|
||||
const allocator = fixedAllocator(&stack);
|
||||
|
||||
// Allocation
|
||||
var block_memory = allocator.call(.{
|
||||
.existing = null,
|
||||
.alignment = @alignOf(u64),
|
||||
.size = @sizeOf(u64),
|
||||
});
|
||||
|
||||
try testing.expect(block_memory != null);
|
||||
|
||||
const buffer_address_head = @ptrToInt(&buffer);
|
||||
const buffer_address_tail = @ptrToInt(&buffer) + buffer.len;
|
||||
|
||||
{
|
||||
const block_memory_address = @ptrToInt(block_memory);
|
||||
|
||||
try testing.expect(block_memory_address >= buffer_address_head and
|
||||
block_memory_address < buffer_address_tail);
|
||||
}
|
||||
|
||||
// Reallocation.
|
||||
block_memory = allocator.call(.{
|
||||
.existing = block_memory,
|
||||
.alignment = @alignOf(u64),
|
||||
.size = @sizeOf(u64),
|
||||
});
|
||||
|
||||
try testing.expect(block_memory != null);
|
||||
|
||||
{
|
||||
const block_memory_address = @ptrToInt(block_memory);
|
||||
|
||||
try testing.expect(block_memory_address >= buffer_address_head and
|
||||
block_memory_address < buffer_address_tail);
|
||||
}
|
||||
|
||||
// Deallocation.
|
||||
try testing.expect(allocator.call(.{
|
||||
.existing = block_memory,
|
||||
.alignment = 0,
|
||||
.size = 0,
|
||||
}) == null);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns an [io.Writer] wrapping `fixed_stack`.
|
||||
///
|
||||
|
@ -185,7 +240,7 @@ pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
|
|||
}.write);
|
||||
}
|
||||
|
||||
test "Fixed writer" {
|
||||
test "fixedWriter" {
|
||||
var buffer = [_]u8{0} ** 4;
|
||||
var sequence_stack = Fixed(u8){.buffer = &buffer};
|
||||
const sequence_data = [_]u8{8, 16, 32, 64};
|
||||
|
|
|
@ -197,12 +197,12 @@ pub const string_literal_context = KeyContext([]const u8){
|
|||
}.stringsEqual,
|
||||
};
|
||||
|
||||
test "Hash table manipulation with string literal context" {
|
||||
test "Hashed([]const u8, u32, string_literal_context)" {
|
||||
var buffer = [_]u8{0} ** 4096;
|
||||
var fixed_stack = stack.Fixed(u8){.buffer = &buffer};
|
||||
var memory = stack.Fixed(u8){.buffer = &buffer};
|
||||
|
||||
var table = try Hashed([]const u8, u32, string_literal_context).
|
||||
init(stack.fixedAllocator(&fixed_stack));
|
||||
init(stack.fixedAllocator(&memory));
|
||||
|
||||
defer table.deinit();
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ pub fn expect(ok: bool) TestError!void {
|
|||
if (!ok) return error.UnexpectedResult;
|
||||
}
|
||||
|
||||
// TODO: Implement tests.
|
||||
test "expect" {
|
||||
try expect(true);
|
||||
|
||||
expect(false) catch {};
|
||||
}
|
||||
|
||||
pub const expectError = @import("std").testing.expectError;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const io = @import("./io.zig");
|
||||
const math = @import("./math.zig");
|
||||
const stack = @import("./stack.zig");
|
||||
const testing = @import("./testing.zig");
|
||||
|
||||
///
|
||||
/// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write
|
||||
|
@ -10,7 +12,7 @@ pub const PrintError = error {
|
|||
};
|
||||
|
||||
///
|
||||
/// Number formatting modes supported by [printInt].
|
||||
/// Named identifiers for number formats used in printing functions.
|
||||
///
|
||||
pub const Radix = enum {
|
||||
binary,
|
||||
|
@ -28,6 +30,29 @@ pub const Radix = enum {
|
|||
tetradecimal,
|
||||
pentadecimal,
|
||||
hexadecimal,
|
||||
|
||||
///
|
||||
/// Returns the base number of `radix`.
|
||||
///
|
||||
pub fn base(radix: Radix) u8 {
|
||||
return switch (radix) {
|
||||
.binary => 2,
|
||||
.tinary => 3,
|
||||
.quaternary => 4,
|
||||
.quinary => 5,
|
||||
.senary => 6,
|
||||
.septenary => 7,
|
||||
.octal => 8,
|
||||
.nonary => 9,
|
||||
.decimal => 10,
|
||||
.undecimal => 11,
|
||||
.duodecimal => 12,
|
||||
.tridecimal => 13,
|
||||
.tetradecimal => 14,
|
||||
.pentadecimal => 15,
|
||||
.hexadecimal => 16,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
///
|
||||
|
@ -40,35 +65,39 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void
|
|||
const Int = @TypeOf(value);
|
||||
|
||||
switch (@typeInfo(Int)) {
|
||||
.Int => |int_info| {
|
||||
if (value == 0) return writer.apply("0");
|
||||
.Int => |info| {
|
||||
if (value == 0) {
|
||||
const zero = "0";
|
||||
|
||||
const base = @enumToInt(radix);
|
||||
const is_signed = (int_info.signedness == .signed);
|
||||
if (writer.call(zero) != zero.len) return error.WriteFailure;
|
||||
} else {
|
||||
// Big enough to hold the hexadecimal representation of the integer type, which is
|
||||
// the largest number format accomodated for in [Radix].
|
||||
var buffer = [_]u8{0} ** (@sizeOf(Int) * (@bitSizeOf(u8) / 4));
|
||||
var buffer_count: usize = 0;
|
||||
var n1 = value;
|
||||
|
||||
var buffer = [_]u8{0} ** (math.ceil(math.log(math.
|
||||
maxInt(Int), base)) + @boolToInt(is_signed));
|
||||
if (info.signedness == .signed and value < 0) {
|
||||
// Negative value.
|
||||
n1 = -value;
|
||||
buffer[0] = '-';
|
||||
buffer_count += 1;
|
||||
}
|
||||
|
||||
var buffer_count: usize = 0;
|
||||
var n1 = value;
|
||||
while (n1 != 0) {
|
||||
const base = radix.base();
|
||||
|
||||
if (is_signed and (value < 0)) {
|
||||
// Negative value.
|
||||
n1 = -value;
|
||||
buffer[0] = '-';
|
||||
buffer_count += 1;
|
||||
buffer[buffer_count] = @intCast(u8, (n1 % base) + '0');
|
||||
n1 = (n1 / base);
|
||||
buffer_count += 1;
|
||||
}
|
||||
|
||||
for (buffer[0 .. (buffer_count / 2)]) |_, i|
|
||||
io.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]);
|
||||
|
||||
if (writer.call(buffer[0 .. buffer_count]) != buffer_count)
|
||||
return error.WriteFailure;
|
||||
}
|
||||
|
||||
while (n1 != 0) {
|
||||
buffer[buffer_count] = @intCast(u8, (n1 % base) + '0');
|
||||
n1 = (n1 / base);
|
||||
buffer_count += 1;
|
||||
}
|
||||
|
||||
for (buffer[0 .. (buffer_count / 2)]) |_, i|
|
||||
io.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]);
|
||||
|
||||
if (writer.call(buffer[0 .. buffer_count]) != buffer_count) return error.WriteFailure;
|
||||
},
|
||||
|
||||
// Cast comptime int into known-size integer and try again.
|
||||
|
@ -79,6 +108,12 @@ pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void
|
|||
}
|
||||
}
|
||||
|
||||
test "Print 64-bit signed integer" {
|
||||
// TODO: implement.
|
||||
test "printInt" {
|
||||
// Max digits to represent a decimal u8 is 3 (i.e. 127 / 255).
|
||||
var decimal_buffer = [_]u8{0} ** 3;
|
||||
var decimal_stack = stack.Fixed(u8){.buffer = &decimal_buffer};
|
||||
var decimal_writer = stack.fixedWriter(&decimal_stack);
|
||||
|
||||
try printInt(decimal_writer, .decimal, 365);
|
||||
try testing.expect(decimal_stack.isFull());
|
||||
}
|
||||
|
|
|
@ -114,7 +114,10 @@ pub const ReadableFile = opaque {
|
|||
{
|
||||
ext.SDL_ClearError();
|
||||
|
||||
var sought = core.math.min(u64, offset, core.math.maxInt(i64));
|
||||
const math = core.math;
|
||||
const min = math.min;
|
||||
const maxIntValue = math.maxIntValue;
|
||||
var sought = min(u64, offset, maxIntValue(i64));
|
||||
|
||||
if (ext.SDL_RWseek(rw_ops, @intCast(i64, sought), ext.RW_SEEK_SET) < 0)
|
||||
return error.FileInaccessible;
|
||||
|
@ -122,7 +125,7 @@ pub const ReadableFile = opaque {
|
|||
var to_seek = offset - sought;
|
||||
|
||||
while (to_seek != 0) {
|
||||
sought = core.math.min(u64, to_seek, core.math.maxInt(i64));
|
||||
sought = min(u64, to_seek, maxIntValue(i64));
|
||||
|
||||
ext.SDL_ClearError();
|
||||
|
||||
|
|
Loading…
Reference in New Issue