zig/test/behavior/zon.zig
Mason Remaley 06ee383da9
compiler: allow @import of ZON without a result type
In particular, this allows importing `build.zig.zon` at comptime.
2025-04-02 05:53:22 +01:00

574 lines
17 KiB
Zig

const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectEqualSlices = std.testing.expectEqualSlices;
const expectEqualStrings = std.testing.expectEqualStrings;
test "bool" {
try expectEqual(true, @as(bool, @import("zon/true.zon")));
try expectEqual(false, @as(bool, @import("zon/false.zon")));
}
test "optional" {
const some: ?u32 = @import("zon/some.zon");
const none: ?u32 = @import("zon/none.zon");
const @"null": @TypeOf(null) = @import("zon/none.zon");
try expectEqual(@as(u32, 10), some);
try expectEqual(@as(?u32, null), none);
try expectEqual(null, @"null");
}
test "union" {
// No tag
{
const Union = union {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
try expectEqual(1.5, union1.x);
try expectEqual(true, union2.y);
try expectEqual({}, union3.z);
}
// Inferred tag
{
const Union = union(enum) {
x: f32,
y: bool,
z: void,
};
const union1: Union = comptime @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
try expectEqual(1.5, union1.x);
try expectEqual(true, union2.y);
try expectEqual({}, union3.z);
}
// Explicit tag
{
const Tag = enum(i128) {
x = -1,
y = 2,
z = 1,
};
const Union = union(Tag) {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
try expectEqual(1.5, union1.x);
try expectEqual(true, union2.y);
try expectEqual({}, union3.z);
}
}
test "struct" {
const Vec0 = struct {};
const Vec1 = struct { x: f32 };
const Vec2 = struct { x: f32, y: f32 };
const Escaped = struct { @"0": f32, foo: f32 };
try expectEqual(Vec0{}, @as(Vec0, @import("zon/vec0.zon")));
try expectEqual(Vec1{ .x = 1.5 }, @as(Vec1, @import("zon/vec1.zon")));
try expectEqual(Vec2{ .x = 1.5, .y = 2 }, @as(Vec2, @import("zon/vec2.zon")));
try expectEqual(Escaped{ .@"0" = 1.5, .foo = 2 }, @as(Escaped, @import("zon/escaped_struct.zon")));
}
test "struct default fields" {
const Vec3 = struct {
x: f32,
y: f32,
z: f32 = 123.4,
};
try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, @as(Vec3, @import("zon/vec2.zon")));
const ascribed: Vec3 = @import("zon/vec2.zon");
try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, ascribed);
const Vec2 = struct {
x: f32 = 20.0,
y: f32 = 10.0,
};
try expectEqual(Vec2{ .x = 1.5, .y = 2.0 }, @as(Vec2, @import("zon/vec2.zon")));
}
test "struct enum field" {
const Struct = struct {
x: enum { x, y, z },
};
try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon")));
}
test "tuple" {
const Tuple = struct { f32, bool, []const u8, u16 };
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
test "comptime fields" {
// Test setting comptime tuple fields to the correct value
{
const Tuple = struct {
comptime f32 = 1.2,
comptime bool = true,
comptime []const u8 = "hello",
comptime u16 = 3,
};
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
// Test setting comptime struct fields to the correct value
{
const Vec2 = struct {
comptime x: f32 = 1.5,
comptime y: f32 = 2.0,
};
try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/vec2.zon")));
}
// Test allowing comptime tuple fields to be set to their defaults
{
const Tuple = struct {
f32,
bool,
[]const u8,
u16,
comptime u8 = 255,
};
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
// Test allowing comptime struct fields to be set to their defaults
{
const Vec2 = struct {
comptime x: f32 = 1.5,
comptime y: f32 = 2.0,
};
try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/slice-empty.zon")));
}
}
test "char" {
try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon")));
try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon")));
}
test "arrays" {
try expectEqual([0]u8{}, @as([0]u8, @import("zon/vec0.zon")));
try expectEqual([0:1]u8{}, @as([0:1]u8, @import("zon/vec0.zon")));
try expectEqual(1, @as([0:1]u8, @import("zon/vec0.zon"))[0]);
try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @as([4]u8, @import("zon/array.zon")));
try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @as([4:2]u8, @import("zon/array.zon")));
try expectEqual(2, @as([4:2]u8, @import("zon/array.zon"))[4]);
}
test "slices, arrays, tuples" {
{
const expected_slice: []const u8 = &.{};
const found_slice: []const u8 = @import("zon/slice-empty.zon");
try expectEqualSlices(u8, expected_slice, found_slice);
const expected_array: [0]u8 = .{};
const found_array: [0]u8 = @import("zon/slice-empty.zon");
try expectEqual(expected_array, found_array);
const T = struct {};
const expected_tuple: T = .{};
const found_tuple: T = @import("zon/slice-empty.zon");
try expectEqual(expected_tuple, found_tuple);
}
{
const expected_slice: []const u8 = &.{1};
const found_slice: []const u8 = @import("zon/slice1_no_newline.zon");
try expectEqualSlices(u8, expected_slice, found_slice);
const expected_array: [1]u8 = .{1};
const found_array: [1]u8 = @import("zon/slice1_no_newline.zon");
try expectEqual(expected_array, found_array);
const T = struct { u8 };
const expected_tuple: T = .{1};
const found_tuple: T = @import("zon/slice1_no_newline.zon");
try expectEqual(expected_tuple, found_tuple);
}
{
const expected_slice: []const u8 = &.{ 'a', 'b', 'c' };
const found_slice: []const u8 = @import("zon/slice-abc.zon");
try expectEqualSlices(u8, expected_slice, found_slice);
const expected_array: [3]u8 = .{ 'a', 'b', 'c' };
const found_array: [3]u8 = @import("zon/slice-abc.zon");
try expectEqual(expected_array, found_array);
const T = struct { u8, u8, u8 };
const expected_tuple: T = .{ 'a', 'b', 'c' };
const found_tuple: T = @import("zon/slice-abc.zon");
try expectEqual(expected_tuple, found_tuple);
}
}
test "string literals" {
try expectEqualSlices(u8, "abc", @import("zon/abc.zon"));
try expectEqualSlices(u8, "ab\\c", @import("zon/abc-escaped.zon"));
const zero_terminated: [:0]const u8 = @import("zon/abc.zon");
try expectEqualDeep(zero_terminated, "abc");
try expectEqual(0, zero_terminated[zero_terminated.len]);
try expectEqualStrings(
\\Hello, world!
\\This is a multiline string!
\\ There are no escapes, we can, for example, include \n in the string
, @import("zon/multiline_string.zon"));
try expectEqualStrings("a\nb\x00c", @import("zon/string_embedded_null.zon"));
}
test "enum literals" {
const Enum = enum {
foo,
bar,
baz,
@"0\na",
};
try expectEqual(Enum.foo, @as(Enum, @import("zon/foo.zon")));
try expectEqual(.foo, @as(@TypeOf(.foo), @import("zon/foo.zon")));
try expectEqual(Enum.@"0\na", @as(Enum, @import("zon/escaped_enum.zon")));
}
test "int" {
const T = struct {
u8,
i16,
i14,
i32,
i8,
i8,
u8,
u8,
u65,
u65,
i128,
i128,
i66,
i66,
i8,
i8,
i16,
i16,
i16,
i16,
i16,
i16,
u65,
i66,
i66,
u65,
i66,
i66,
u65,
i66,
i66,
};
const expected: T = .{
// Test various numbers and types
10,
24,
-4,
-123,
// Test limits
127,
-128,
// Test characters
'a',
'z',
// Test big integers
36893488147419103231,
36893488147419103231,
-18446744073709551615, // Only a big int due to negation
-9223372036854775809, // Only a big int due to negation
// Test big integer limits
36893488147419103231,
-36893488147419103232,
// Test parsing whole number floats as integers
-1,
123,
// Test non-decimal integers
0xff,
-0xff,
0o77,
-0o77,
0b11,
-0b11,
// Test non-decimal big integers
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
};
const actual: T = @import("zon/ints.zon");
try expectEqual(expected, actual);
}
test "floats" {
const T = struct {
f16,
f32,
f64,
f128,
f16,
f16,
f32,
f32,
f32,
f32,
f32,
f32,
f128,
f32,
f32,
f32,
f32,
f32,
};
const expected: T = .{
// Test decimals
0.5,
123.456,
-123.456,
42.5,
// Test whole numbers with and without decimals
5.0,
5.0,
-102,
-102,
// Test characters and negated characters
'a',
'z',
// Test big integers
36893488147419103231,
-36893488147419103231,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
// Exponents, underscores
123.0E+77,
// Hexadecimal
0x103.70p-5,
-0x103.70,
0x1234_5678.9ABC_CDEFp-10,
};
const actual: T = @import("zon/floats.zon");
try expectEqual(expected, actual);
}
test "inf and nan" {
// f32
{
const actual: struct { f32, f32, f32 } = @import("zon/inf_and_nan.zon");
try expect(std.math.isNan(actual[0]));
try expect(std.math.isPositiveInf(actual[1]));
try expect(std.math.isNegativeInf(actual[2]));
}
// f128
{
const actual: struct { f128, f128, f128 } = @import("zon/inf_and_nan.zon");
try expect(std.math.isNan(actual[0]));
try expect(std.math.isPositiveInf(actual[1]));
try expect(std.math.isNegativeInf(actual[2]));
}
}
test "vector" {
{
const actual: @Vector(0, bool) = @import("zon/vec0.zon");
const expected: @Vector(0, bool) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, bool) = @import("zon/vec3_bool.zon");
const expected: @Vector(3, bool) = .{ false, false, true };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, f32) = @import("zon/vec0.zon");
const expected: @Vector(0, f32) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, f32) = @import("zon/vec3_float.zon");
const expected: @Vector(3, f32) = .{ 1.5, 2.5, 3.5 };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, u8) = @import("zon/vec0.zon");
const expected: @Vector(0, u8) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, u8) = @import("zon/vec3_int.zon");
const expected: @Vector(3, u8) = .{ 2, 4, 6 };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, *const u8) = @import("zon/vec0.zon");
const expected: @Vector(0, *const u8) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, *const u8) = @import("zon/vec3_int.zon");
const expected: @Vector(3, *const u8) = .{ &2, &4, &6 };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, ?*const u8) = @import("zon/vec0.zon");
const expected: @Vector(0, ?*const u8) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, ?*const u8) = @import("zon/vec3_int_opt.zon");
const expected: @Vector(3, ?*const u8) = .{ &2, null, &6 };
try expectEqual(expected, actual);
}
}
test "pointers" {
// Primitive with varying levels of pointers
try expectEqual(@as(u8, 'a'), @as(*const u8, @import("zon/a.zon")).*);
try expectEqual(@as(u8, 'a'), @as(*const *const u8, @import("zon/a.zon")).*.*);
try expectEqual(@as(u8, 'a'), @as(*const *const *const u8, @import("zon/a.zon")).*.*.*);
// Primitive optional with varying levels of pointers
try expectEqual(@as(u8, 'a'), @as(?*const u8, @import("zon/a.zon")).?.*);
try expectEqual(null, @as(?*const u8, @import("zon/none.zon")));
try expectEqual(@as(u8, 'a'), @as(*const ?u8, @import("zon/a.zon")).*.?);
try expectEqual(null, @as(*const ?u8, @import("zon/none.zon")).*);
try expectEqual(@as(u8, 'a'), @as(?*const *const u8, @import("zon/a.zon")).?.*.*);
try expectEqual(null, @as(?*const *const u8, @import("zon/none.zon")));
try expectEqual(@as(u8, 'a'), @as(*const ?*const u8, @import("zon/a.zon")).*.?.*);
try expectEqual(null, @as(*const ?*const u8, @import("zon/none.zon")).*);
try expectEqual(@as(u8, 'a'), @as(*const *const ?u8, @import("zon/a.zon")).*.*.?);
try expectEqual(null, @as(*const *const ?u8, @import("zon/none.zon")).*.*);
try expectEqual([3]u8{ 2, 4, 6 }, @as(*const [3]u8, @import("zon/vec3_int.zon")).*);
// A complicated type with nested internal pointers and string allocations
{
const Inner = struct {
f1: *const ?*const []const u8,
f2: *const ?*const []const u8,
};
const Outer = struct {
f1: *const ?*const Inner,
f2: *const ?*const Inner,
};
const expected: Outer = .{
.f1 = &&.{
.f1 = &null,
.f2 = &&"foo",
},
.f2 = &null,
};
const found: ?*const Outer = @import("zon/complex.zon");
try std.testing.expectEqualDeep(expected, found.?.*);
}
}
test "recursive" {
const Recursive = struct { foo: ?*const @This() };
const expected: Recursive = .{ .foo = &.{ .foo = null } };
try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon")));
}
test "anon" {
const expected = .{
.{
.bool_true = true,
.bool_false = false,
.string = "foo",
},
.{
null,
10,
36893488147419103232,
1.234,
'z',
.bar,
.{},
},
};
const actual = @import("zon/anon.zon");
try expectEqual(expected.len, actual.len);
try expectEqual(expected[1], actual[1]);
const expected_struct = expected[0];
const actual_struct = actual[0];
const expected_fields = @typeInfo(@TypeOf(expected_struct)).@"struct".fields;
const actual_fields = @typeInfo(@TypeOf(actual_struct)).@"struct".fields;
try expectEqual(expected_fields.len, actual_fields.len);
inline for (expected_fields) |field| {
try expectEqual(@field(expected_struct, field.name), @field(actual_struct, field.name));
}
}
test "build.zig.zon" {
const build = @import("zon/build.zig.zon");
try expectEqual(4, @typeInfo(@TypeOf(build)).@"struct".fields.len);
try expectEqualStrings("temp", build.name);
try expectEqualStrings("0.0.0", build.version);
const dependencies = build.dependencies;
try expectEqual(2, @typeInfo(@TypeOf(dependencies)).@"struct".fields.len);
const example_0 = dependencies.example_0;
try expectEqual(2, @typeInfo(@TypeOf(dependencies)).@"struct".fields.len);
try expectEqualStrings("https://example.com/foo.tar.gz", example_0.url);
try expectEqualStrings("...", example_0.hash);
const example_1 = dependencies.example_1;
try expectEqual(2, @typeInfo(@TypeOf(dependencies)).@"struct".fields.len);
try expectEqualStrings("../foo", example_1.path);
try expectEqual(false, example_1.lazy);
try expectEqual(.{ "build.zig", "build.zig.zon", "src" }, build.paths);
}