From 4bb86c41bc5d0190b56807bc202f3300e4d0a300 Mon Sep 17 00:00:00 2001 From: kayomn Date: Thu, 3 Nov 2022 15:53:36 +0000 Subject: [PATCH] Add more test coverage and clean up code --- src/core/io.zig | 71 ++++++++++++++++++++++++++--------- src/core/math.zig | 40 ++++++++++++++++++-- src/core/stack.zig | 67 ++++++++++++++++++++++++++++++--- src/core/table.zig | 6 +-- src/core/testing.zig | 6 ++- src/core/unicode.zig | 89 ++++++++++++++++++++++++++++++-------------- src/ona/sys.zig | 7 +++- 7 files changed, 227 insertions(+), 59 deletions(-) diff --git a/src/core/io.zig b/src/core/io.zig index f3282ae..11b7ed1 100644 --- a/src/core/io.zig +++ b/src/core/io.zig @@ -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); diff --git a/src/core/math.zig b/src/core/math.zig index 6cb3c23..52c74a5 100644 --- a/src/core/math.zig +++ b/src/core/math.zig @@ -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); +} diff --git a/src/core/stack.zig b/src/core/stack.zig index e79a4ef..c83b35a 100755 --- a/src/core/stack.zig +++ b/src/core/stack.zig @@ -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}; diff --git a/src/core/table.zig b/src/core/table.zig index 33a2e4c..1458060 100644 --- a/src/core/table.zig +++ b/src/core/table.zig @@ -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(); diff --git a/src/core/testing.zig b/src/core/testing.zig index 1881378..c2b88e9 100644 --- a/src/core/testing.zig +++ b/src/core/testing.zig @@ -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; diff --git a/src/core/unicode.zig b/src/core/unicode.zig index 6e89678..de6279f 100644 --- a/src/core/unicode.zig +++ b/src/core/unicode.zig @@ -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()); } diff --git a/src/ona/sys.zig b/src/ona/sys.zig index d3f33a0..4682760 100644 --- a/src/ona/sys.zig +++ b/src/ona/sys.zig @@ -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();