Zig 0.15: zig build test

This commit is contained in:
Mitchell Hashimoto
2025-10-01 13:10:40 -07:00
parent 3770f97608
commit cb295b84a0
66 changed files with 1264 additions and 1144 deletions

View File

@@ -162,10 +162,11 @@ pub fn parse(
error.InvalidField => "unknown field",
error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required",
error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value",
else => try std.fmt.allocPrintZ(
else => try std.fmt.allocPrintSentinel(
arena_alloc,
"unknown error {}",
.{err},
0,
),
};
@@ -235,14 +236,16 @@ fn formatValueRequired(
comptime T: type,
arena_alloc: std.mem.Allocator,
key: []const u8,
) std.mem.Allocator.Error![:0]const u8 {
var buf = std.ArrayList(u8).init(arena_alloc);
errdefer buf.deinit();
const writer = buf.writer();
) std.Io.Writer.Error![:0]const u8 {
var stream: std.Io.Writer.Allocating = .init(arena_alloc);
const writer = &stream.writer;
try writer.print("value required", .{});
try formatValues(T, key, writer);
try writer.writeByte(0);
return buf.items[0 .. buf.items.len - 1 :0];
const written = stream.written();
return written[0 .. written.len - 1 :0];
}
fn formatInvalidValue(
@@ -250,17 +253,23 @@ fn formatInvalidValue(
arena_alloc: std.mem.Allocator,
key: []const u8,
value: ?[]const u8,
) std.mem.Allocator.Error![:0]const u8 {
var buf = std.ArrayList(u8).init(arena_alloc);
errdefer buf.deinit();
const writer = buf.writer();
) std.Io.Writer.Error![:0]const u8 {
var stream: std.Io.Writer.Allocating = .init(arena_alloc);
const writer = &stream.writer;
try writer.print("invalid value \"{?s}\"", .{value});
try formatValues(T, key, writer);
try writer.writeByte(0);
return buf.items[0 .. buf.items.len - 1 :0];
const written = stream.written();
return written[0 .. written.len - 1 :0];
}
fn formatValues(comptime T: type, key: []const u8, writer: anytype) std.mem.Allocator.Error!void {
fn formatValues(
comptime T: type,
key: []const u8,
writer: *std.Io.Writer,
) std.Io.Writer.Error!void {
@setEvalBranchQuota(2000);
const typeinfo = @typeInfo(T);
inline for (typeinfo.@"struct".fields) |f| {
@@ -542,8 +551,8 @@ pub fn parseAutoStruct(
const key = std.mem.trim(u8, entry[0..idx], whitespace);
// used if we need to decode a double-quoted string.
var buf: std.ArrayListUnmanaged(u8) = .empty;
defer buf.deinit(alloc);
var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit();
const value = value: {
const value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
@@ -554,10 +563,9 @@ pub fn parseAutoStruct(
value[value.len - 1] == '"')
{
// Decode a double-quoted string as a Zig string literal.
const writer = buf.writer(alloc);
const parsed = try std.zig.string_literal.parseWrite(writer, value);
const parsed = try std.zig.string_literal.parseWrite(&buf.writer, value);
if (parsed == .failure) return error.InvalidValue;
break :value buf.items;
break :value buf.written();
}
break :value value;
@@ -795,15 +803,13 @@ test "parse: diagnostic location" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var fbs = std.io.fixedBufferStream(
var r: std.Io.Reader = .fixed(
\\a=42
\\what
\\b=two
);
const r = fbs.reader();
const Iter = LineIterator(@TypeOf(r));
var iter: Iter = .{ .r = r, .filepath = "test" };
var iter: LineIterator = .{ .r = &r, .filepath = "test" };
try parse(@TypeOf(data), testing.allocator, &data, &iter);
try testing.expect(data._arena != null);
try testing.expectEqualStrings("42", data.a);
@@ -1208,18 +1214,7 @@ test "parseIntoField: struct with basic fields" {
try testing.expectEqual(84, data.value.b);
try testing.expectEqual(24, data.value.c);
// Set with explicit default
data.value = try parseAutoStruct(
@TypeOf(data.value),
alloc,
"a:hello",
.{ .a = "oh no", .b = 42 },
);
try testing.expectEqualStrings("hello", data.value.a);
try testing.expectEqual(42, data.value.b);
try testing.expectEqual(12, data.value.c);
// Missing required field
// Missing require dfield
try testing.expectError(
error.InvalidValue,
parseIntoField(@TypeOf(data), alloc, &data, "value", "a:hello"),
@@ -1395,115 +1390,119 @@ test "ArgsIterator" {
/// Returns an iterator (implements "next") that reads CLI args by line.
/// Each CLI arg is expected to be a single line. This is used to implement
/// configuration files.
pub fn LineIterator(comptime ReaderType: type) type {
return struct {
const Self = @This();
pub const LineIterator = struct {
const Self = @This();
/// The maximum size a single line can be. We don't expect any
/// CLI arg to exceed this size. Can't wait to git blame this in
/// like 4 years and be wrong about this.
pub const MAX_LINE_SIZE = 4096;
/// The maximum size a single line can be. We don't expect any
/// CLI arg to exceed this size. Can't wait to git blame this in
/// like 4 years and be wrong about this.
pub const MAX_LINE_SIZE = 4096;
/// Our stateful reader.
r: ReaderType,
/// Our stateful reader.
r: *std.Io.Reader,
/// Filepath that is used for diagnostics. This is only used for
/// diagnostic messages so it can be formatted however you want.
/// It is prefixed to the messages followed by the line number.
filepath: []const u8 = "",
/// Filepath that is used for diagnostics. This is only used for
/// diagnostic messages so it can be formatted however you want.
/// It is prefixed to the messages followed by the line number.
filepath: []const u8 = "",
/// The current line that we're on. This is 1-indexed because
/// lines are generally 1-indexed in the real world. The value
/// can be zero if we haven't read any lines yet.
line: usize = 0,
/// The current line that we're on. This is 1-indexed because
/// lines are generally 1-indexed in the real world. The value
/// can be zero if we haven't read any lines yet.
line: usize = 0,
/// This is the buffer where we store the current entry that
/// is formatted to be compatible with the parse function.
entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)),
/// This is the buffer where we store the current entry that
/// is formatted to be compatible with the parse function.
entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)),
pub fn next(self: *Self) ?[]const u8 {
// TODO: detect "--" prefixed lines and give a friendlier error
const buf = buf: {
while (true) {
// Read the full line
var entry = self.r.readUntilDelimiterOrEof(self.entry[2..], '\n') catch |err| switch (err) {
inline else => |e| {
log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e });
return null;
},
} orelse return null;
pub fn init(reader: *std.Io.Reader) Self {
return .{ .r = reader };
}
// Increment our line counter
self.line += 1;
pub fn next(self: *Self) ?[]const u8 {
// First prime the reader.
// File readers at least are initialized with a size of 0,
// and this will actually prompt the reader to get the actual
// size of the file, which will be used in the EOF check below.
//
// This will also optimize reads down the line as we're
// more likely to beworking with buffered data.
self.r.fillMore() catch {};
// Trim any whitespace (including CR) around it
const trim = std.mem.trim(u8, entry, whitespace ++ "\r");
if (trim.len != entry.len) {
std.mem.copyForwards(u8, entry, trim);
entry = entry[0..trim.len];
}
var writer: std.Io.Writer = .fixed(self.entry[2..]);
// Ignore blank lines and comments
if (entry.len == 0 or entry[0] == '#') continue;
var entry = while (self.r.seek != self.r.end) {
// Reset write head
writer.end = 0;
// Trim spaces around '='
if (mem.indexOf(u8, entry, "=")) |idx| {
const key = std.mem.trim(u8, entry[0..idx], whitespace);
const value = value: {
var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
_ = self.r.streamDelimiterEnding(&writer, '\n') catch |e| {
log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e });
return null;
};
_ = self.r.discardDelimiterInclusive('\n') catch {};
// Detect a quoted string.
if (value.len >= 2 and
value[0] == '"' and
value[value.len - 1] == '"')
{
// Trim quotes since our CLI args processor expects
// quotes to already be gone.
value = value[1 .. value.len - 1];
}
var entry = writer.buffered();
self.line += 1;
break :value value;
};
// Trim any whitespace (including CR) around it
const trim = std.mem.trim(u8, entry, whitespace ++ "\r");
if (trim.len != entry.len) {
std.mem.copyForwards(u8, entry, trim);
entry = entry[0..trim.len];
}
const len = key.len + value.len + 1;
if (entry.len != len) {
std.mem.copyForwards(u8, entry, key);
entry[key.len] = '=';
std.mem.copyForwards(u8, entry[key.len + 1 ..], value);
entry = entry[0..len];
}
}
// Ignore blank lines and comments
if (entry.len == 0 or entry[0] == '#') continue;
break entry;
} else return null;
break :buf entry;
if (mem.indexOf(u8, entry, "=")) |idx| {
const key = std.mem.trim(u8, entry[0..idx], whitespace);
const value = value: {
var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
// Detect a quoted string.
if (value.len >= 2 and
value[0] == '"' and
value[value.len - 1] == '"')
{
// Trim quotes since our CLI args processor expects
// quotes to already be gone.
value = value[1 .. value.len - 1];
}
break :value value;
};
// We need to reslice so that we include our '--' at the beginning
// of our buffer so that we can trick the CLI parser to treat it
// as CLI args.
return self.entry[0 .. buf.len + 2];
const len = key.len + value.len + 1;
if (entry.len != len) {
std.mem.copyForwards(u8, entry, key);
entry[key.len] = '=';
std.mem.copyForwards(u8, entry[key.len + 1 ..], value);
entry = entry[0..len];
}
}
/// Returns a location for a diagnostic message.
pub fn location(
self: *const Self,
alloc: Allocator,
) Allocator.Error!?diags.Location {
// If we have no filepath then we have no location.
if (self.filepath.len == 0) return null;
// We need to reslice so that we include our '--' at the beginning
// of our buffer so that we can trick the CLI parser to treat it
// as CLI args.
return self.entry[0 .. entry.len + 2];
}
return .{ .file = .{
.path = try alloc.dupe(u8, self.filepath),
.line = self.line,
} };
}
};
}
/// Returns a location for a diagnostic message.
pub fn location(
self: *const Self,
alloc: Allocator,
) Allocator.Error!?diags.Location {
// If we have no filepath then we have no location.
if (self.filepath.len == 0) return null;
// Constructs a LineIterator (see docs for that).
fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) {
return .{ .r = reader };
}
return .{ .file = .{
.path = try alloc.dupe(u8, self.filepath),
.line = self.line,
} };
}
};
/// An iterator valid for arg parsing from a slice.
pub const SliceIterator = struct {
@@ -1526,7 +1525,7 @@ pub fn sliceIterator(slice: []const []const u8) SliceIterator {
test "LineIterator" {
const testing = std.testing;
var fbs = std.io.fixedBufferStream(
var reader: std.Io.Reader = .fixed(
\\A
\\B=42
\\C
@@ -1541,7 +1540,7 @@ test "LineIterator" {
\\F= "value "
);
var iter = lineIterator(fbs.reader());
var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqualStrings("--B=42", iter.next().?);
try testing.expectEqualStrings("--C", iter.next().?);
@@ -1554,9 +1553,9 @@ test "LineIterator" {
test "LineIterator end in newline" {
const testing = std.testing;
var fbs = std.io.fixedBufferStream("A\n\n");
var reader: std.Io.Reader = .fixed("A\n\n");
var iter = lineIterator(fbs.reader());
var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
try testing.expectEqual(@as(?[]const u8, null), iter.next());
@@ -1564,9 +1563,9 @@ test "LineIterator end in newline" {
test "LineIterator spaces around '='" {
const testing = std.testing;
var fbs = std.io.fixedBufferStream("A = B\n\n");
var reader: std.Io.Reader = .fixed("A = B\n\n");
var iter = lineIterator(fbs.reader());
var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A=B", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
try testing.expectEqual(@as(?[]const u8, null), iter.next());
@@ -1574,18 +1573,18 @@ test "LineIterator spaces around '='" {
test "LineIterator no value" {
const testing = std.testing;
var fbs = std.io.fixedBufferStream("A = \n\n");
var reader: std.Io.Reader = .fixed("A = \n\n");
var iter = lineIterator(fbs.reader());
var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A=", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
}
test "LineIterator with CRLF line endings" {
const testing = std.testing;
var fbs = std.io.fixedBufferStream("A\r\nB = C\r\n");
var reader: std.Io.Reader = .fixed("A\r\nB = C\r\n");
var iter = lineIterator(fbs.reader());
var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqualStrings("--B=C", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());