const std = @import("std"); const TestContext = @import("../../src/test.zig").TestContext; // These tests should work with all platforms, but we're using linux_x64 for // now for consistency. Will be expanded eventually. const linux_x64 = std.zig.CrossTarget{ .cpu_arch = .x86_64, .os_tag = .linux, }; pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exeFromCompiledC("hello world with updates", .{}); // Regular old hello world case.addCompareOutput( \\extern fn puts(s: [*:0]const u8) c_int; \\pub export fn main() c_int { \\ _ = puts("hello world!"); \\ return 0; \\} , "hello world!" ++ std.cstr.line_sep); // Now change the message only case.addCompareOutput( \\extern fn puts(s: [*:0]const u8) c_int; \\pub export fn main() c_int { \\ _ = puts("yo"); \\ return 0; \\} , "yo" ++ std.cstr.line_sep); // Add an unused Decl case.addCompareOutput( \\extern fn puts(s: [*:0]const u8) c_int; \\pub export fn main() c_int { \\ _ = puts("yo!"); \\ return 0; \\} \\fn unused() void {} , "yo!" ++ std.cstr.line_sep); // Comptime return type and calling convention expected. case.addError( \\var x: i32 = 1234; \\pub export fn main() x { \\ return 0; \\} \\export fn foo() callconv(y) c_int { \\ return 0; \\} \\var y: i32 = 1234; , &.{ ":2:22: error: unable to resolve comptime value", ":5:26: error: unable to resolve comptime value", }); } { var case = ctx.exeFromCompiledC("var args", .{}); case.addCompareOutput( \\extern fn printf(format: [*:0]const u8, ...) c_int; \\ \\pub export fn main() c_int { \\ _ = printf("Hello, %s!\n", "world"); \\ return 0; \\} , "Hello, world!" ++ std.cstr.line_sep); } { var case = ctx.exeFromCompiledC("@intToError", .{}); case.addCompareOutput( \\pub export fn main() c_int { \\ // comptime checks \\ const a = error.A; \\ const b = error.B; \\ const c = @intToError(2); \\ const d = @intToError(1); \\ if (!(c == b)) unreachable; \\ if (!(a == d)) unreachable; \\ // runtime checks \\ var x = error.A; \\ var y = error.B; \\ var z = @intToError(2); \\ var f = @intToError(1); \\ if (!(y == z)) unreachable; \\ if (!(x == f)) unreachable; \\ return 0; \\} , ""); case.addError( \\pub export fn main() c_int { \\ const c = @intToError(0); \\ return 0; \\} , &.{":2:27: error: integer value 0 represents no error"}); case.addError( \\pub export fn main() c_int { \\ const c = @intToError(3); \\ return 0; \\} , &.{":2:27: error: integer value 3 represents no error"}); } { var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); // Exit with 0 case.addCompareOutput( \\fn exitGood() noreturn { \\ asm volatile ("syscall" \\ : \\ : [number] "{rax}" (231), \\ [arg1] "{rdi}" (0) \\ ); \\ unreachable; \\} \\ \\pub export fn main() c_int { \\ exitGood(); \\} , ""); // Pass a usize parameter to exit case.addCompareOutput( \\pub export fn main() c_int { \\ exit(0); \\} \\ \\fn exit(code: usize) noreturn { \\ asm volatile ("syscall" \\ : \\ : [number] "{rax}" (231), \\ [arg1] "{rdi}" (code) \\ ); \\ unreachable; \\} , ""); // Change the parameter to u8 case.addCompareOutput( \\pub export fn main() c_int { \\ exit(0); \\} \\ \\fn exit(code: u8) noreturn { \\ asm volatile ("syscall" \\ : \\ : [number] "{rax}" (231), \\ [arg1] "{rdi}" (code) \\ ); \\ unreachable; \\} , ""); // Do some arithmetic at the exit callsite case.addCompareOutput( \\pub export fn main() c_int { \\ exitMath(1); \\} \\ \\fn exitMath(a: u8) noreturn { \\ exit(0 + a - a); \\} \\ \\fn exit(code: u8) noreturn { \\ asm volatile ("syscall" \\ : \\ : [number] "{rax}" (231), \\ [arg1] "{rdi}" (code) \\ ); \\ unreachable; \\} \\ , ""); // Invert the arithmetic case.addCompareOutput( \\pub export fn main() c_int { \\ exitMath(1); \\} \\ \\fn exitMath(a: u8) noreturn { \\ exit(a + 0 - a); \\} \\ \\fn exit(code: u8) noreturn { \\ asm volatile ("syscall" \\ : \\ : [number] "{rax}" (231), \\ [arg1] "{rdi}" (code) \\ ); \\ unreachable; \\} \\ , ""); } { var case = ctx.exeFromCompiledC("alloc and retptr", .{}); case.addCompareOutput( \\fn add(a: i32, b: i32) i32 { \\ return a + b; \\} \\ \\fn addIndirect(a: i32, b: i32) i32 { \\ return add(a, b); \\} \\ \\pub export fn main() c_int { \\ return addIndirect(1, 2) - 3; \\} , ""); } { var case = ctx.exeFromCompiledC("inferred local const and var", .{}); case.addCompareOutput( \\fn add(a: i32, b: i32) i32 { \\ return a + b; \\} \\ \\pub export fn main() c_int { \\ const x = add(1, 2); \\ var y = add(3, 0); \\ y -= x; \\ return y; \\} , ""); } // This will make a pretty deep call stack, so this test can only be enabled // on hosts where Zig's linking strategy can honor the 16 MiB (default) we // link the self-hosted compiler with. const host_supports_custom_stack_size = @import("builtin").target.os.tag == .linux; if (host_supports_custom_stack_size) { var case = ctx.exeFromCompiledC("@setEvalBranchQuota", .{}); case.addCompareOutput( \\pub export fn main() i32 { \\ @setEvalBranchQuota(1001); \\ const y = rec(1001); \\ return y - 1; \\} \\ \\fn rec(n: usize) callconv(.Inline) usize { \\ if (n <= 1) return n; \\ return rec(n - 1); \\} , ""); } { var case = ctx.exeFromCompiledC("control flow", .{}); // Simple while loop case.addCompareOutput( \\pub export fn main() c_int { \\ var a: c_int = 0; \\ while (a < 5) : (a+=1) {} \\ return a - 5; \\} , ""); case.addCompareOutput( \\pub export fn main() c_int { \\ var a = true; \\ while (!a) {} \\ return 0; \\} , ""); // If expression case.addCompareOutput( \\pub export fn main() c_int { \\ var cond: c_int = 0; \\ var a: c_int = @as(c_int, if (cond == 0) \\ 2 \\ else \\ 3) + 9; \\ return a - 11; \\} , ""); // If expression with breakpoint that does not get hit case.addCompareOutput( \\pub export fn main() c_int { \\ var x: i32 = 1; \\ if (x != 1) @breakpoint(); \\ return 0; \\} , ""); // Switch expression case.addCompareOutput( \\pub export fn main() c_int { \\ var cond: c_int = 0; \\ var a: c_int = switch (cond) { \\ 1 => 1, \\ 2 => 2, \\ 99...300, 12 => 3, \\ 0 => 4, \\ else => 5, \\ }; \\ return a - 4; \\} , ""); // Switch expression missing else case. case.addError( \\pub export fn main() c_int { \\ var cond: c_int = 0; \\ const a: c_int = switch (cond) { \\ 1 => 1, \\ 2 => 2, \\ 3 => 3, \\ 4 => 4, \\ }; \\ return a - 4; \\} , &.{":3:22: error: switch must handle all possibilities"}); // Switch expression, has an unreachable prong. case.addCompareOutput( \\pub export fn main() c_int { \\ var cond: c_int = 0; \\ const a: c_int = switch (cond) { \\ 1 => 1, \\ 2 => 2, \\ 99...300, 12 => 3, \\ 0 => 4, \\ 13 => unreachable, \\ else => 5, \\ }; \\ return a - 4; \\} , ""); // Switch expression, has an unreachable prong and prongs write // to result locations. case.addCompareOutput( \\pub export fn main() c_int { \\ var cond: c_int = 0; \\ var a: c_int = switch (cond) { \\ 1 => 1, \\ 2 => 2, \\ 99...300, 12 => 3, \\ 0 => 4, \\ 13 => unreachable, \\ else => 5, \\ }; \\ return a - 4; \\} , ""); // Integer switch expression has duplicate case value. case.addError( \\pub export fn main() c_int { \\ var cond: c_int = 0; \\ const a: c_int = switch (cond) { \\ 1 => 1, \\ 2 => 2, \\ 96, 11...13, 97 => 3, \\ 0 => 4, \\ 90, 12 => 100, \\ else => 5, \\ }; \\ return a - 4; \\} , &.{ ":8:13: error: duplicate switch value", ":6:15: note: previous value here", }); // Boolean switch expression has duplicate case value. case.addError( \\pub export fn main() c_int { \\ var a: bool = false; \\ const b: c_int = switch (a) { \\ false => 1, \\ true => 2, \\ false => 3, \\ }; \\} , &.{ ":6:9: error: duplicate switch value", }); // Sparse (no range capable) switch expression has duplicate case value. case.addError( \\pub export fn main() c_int { \\ const A: type = i32; \\ const b: c_int = switch (A) { \\ i32 => 1, \\ bool => 2, \\ f64, i32 => 3, \\ else => 4, \\ }; \\} , &.{ ":6:14: error: duplicate switch value", ":4:9: note: previous value here", }); // Ranges not allowed for some kinds of switches. case.addError( \\pub export fn main() c_int { \\ const A: type = i32; \\ const b: c_int = switch (A) { \\ i32 => 1, \\ bool => 2, \\ f16...f64 => 3, \\ else => 4, \\ }; \\} , &.{ ":3:30: error: ranges not allowed when switching on type 'type'", ":6:12: note: range here", }); // Switch expression has unreachable else prong. case.addError( \\pub export fn main() c_int { \\ var a: u2 = 0; \\ const b: i32 = switch (a) { \\ 0 => 10, \\ 1 => 20, \\ 2 => 30, \\ 3 => 40, \\ else => 50, \\ }; \\} , &.{ ":8:14: error: unreachable else prong; all cases already handled", }); } //{ // var case = ctx.exeFromCompiledC("optionals", .{}); // // Simple while loop // case.addCompareOutput( // \\pub export fn main() c_int { // \\ var count: c_int = 0; // \\ var opt_ptr: ?*c_int = &count; // \\ while (opt_ptr) |_| : (count += 1) { // \\ if (count == 4) opt_ptr = null; // \\ } // \\ return count - 5; // \\} // , ""); // // Same with non pointer optionals // case.addCompareOutput( // \\pub export fn main() c_int { // \\ var count: c_int = 0; // \\ var opt_ptr: ?c_int = count; // \\ while (opt_ptr) |_| : (count += 1) { // \\ if (count == 4) opt_ptr = null; // \\ } // \\ return count - 5; // \\} // , ""); //} { var case = ctx.exeFromCompiledC("errors", .{}); case.addCompareOutput( \\pub export fn main() c_int { \\ var e1 = error.Foo; \\ var e2 = error.Bar; \\ assert(e1 != e2); \\ assert(e1 == error.Foo); \\ assert(e2 == error.Bar); \\ return 0; \\} \\fn assert(b: bool) void { \\ if (!b) unreachable; \\} , ""); case.addCompareOutput( \\pub export fn main() c_int { \\ var e: anyerror!c_int = 0; \\ const i = e catch 69; \\ return i; \\} , ""); case.addCompareOutput( \\pub export fn main() c_int { \\ var e: anyerror!c_int = error.Foo; \\ const i = e catch 69; \\ return 69 - i; \\} , ""); } { var case = ctx.exeFromCompiledC("structs", .{}); case.addError( \\const Point = struct { x: i32, y: i32 }; \\pub export fn main() c_int { \\ var p: Point = .{ \\ .y = 24, \\ .x = 12, \\ .y = 24, \\ }; \\ return p.y - p.x - p.x; \\} , &.{ ":6:10: error: duplicate field", ":4:10: note: other field here", }); case.addError( \\const Point = struct { x: i32, y: i32 }; \\pub export fn main() c_int { \\ var p: Point = .{ \\ .y = 24, \\ }; \\ return p.y - p.x - p.x; \\} , &.{ ":3:21: error: missing struct field: x", ":1:15: note: struct 'test_case.Point' declared here", }); case.addError( \\const Point = struct { x: i32, y: i32 }; \\pub export fn main() c_int { \\ var p: Point = .{ \\ .x = 12, \\ .y = 24, \\ .z = 48, \\ }; \\ return p.y - p.x - p.x; \\} , &.{ ":6:10: error: no field named 'z' in struct 'test_case.Point'", ":1:15: note: struct declared here", }); case.addCompareOutput( \\const Point = struct { x: i32, y: i32 }; \\pub export fn main() c_int { \\ var p: Point = .{ \\ .x = 12, \\ .y = 24, \\ }; \\ return p.y - p.x - p.x; \\} , ""); } { var case = ctx.exeFromCompiledC("enums", .{}); case.addError( \\const E1 = packed enum { a, b, c }; \\const E2 = extern enum { a, b, c }; \\export fn foo() void { \\ const x = E1.a; \\} \\export fn bar() void { \\ const x = E2.a; \\} , &.{ ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", }); // comptime and types are caught in AstGen. case.addError( \\const E1 = enum { \\ a, \\ comptime b, \\ c, \\}; \\const E2 = enum { \\ a, \\ b: i32, \\ c, \\}; \\export fn foo() void { \\ const x = E1.a; \\} \\export fn bar() void { \\ const x = E2.a; \\} , &.{ ":3:5: error: enum fields cannot be marked comptime", ":8:8: error: enum fields do not have types", }); // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch case.addCompareOutput( \\const Number = enum { One, Two, Three }; \\ \\pub export fn main() c_int { \\ var number1 = Number.One; \\ var number2: Number = .Two; \\ const number3 = @intToEnum(Number, 2); \\ if (number1 == number2) return 1; \\ if (number2 == number3) return 1; \\ if (@enumToInt(number1) != 0) return 1; \\ if (@enumToInt(number2) != 1) return 1; \\ if (@enumToInt(number3) != 2) return 1; \\ var x: Number = .Two; \\ if (number2 != x) return 1; \\ switch (x) { \\ .One => return 1, \\ .Two => return 0, \\ number3 => return 2, \\ } \\} , ""); // Specifying alignment is a parse error. // This also tests going from a successful build to a parse error. case.addError( \\const E1 = enum { \\ a, \\ b align(4), \\ c, \\}; \\export fn foo() void { \\ const x = E1.a; \\} , &.{ ":3:7: error: expected ',', found 'align'", }); // Redundant non-exhaustive enum mark. // This also tests going from a parse error to an AstGen error. case.addError( \\const E1 = enum { \\ a, \\ _, \\ b, \\ c, \\ _, \\}; \\export fn foo() void { \\ const x = E1.a; \\} , &.{ ":6:5: error: redundant non-exhaustive enum mark", ":3:5: note: other mark here", }); case.addError( \\const E1 = enum { \\ a, \\ b, \\ c, \\ _ = 10, \\}; \\export fn foo() void { \\ const x = E1.a; \\} , &.{ ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value", }); case.addError( \\const E1 = enum {}; \\export fn foo() void { \\ const x = E1.a; \\} , &.{ ":1:12: error: enum declarations must have at least one tag", }); case.addError( \\const E1 = enum { a, b, _ }; \\export fn foo() void { \\ const x = E1.a; \\} , &.{ ":1:12: error: non-exhaustive enum missing integer tag type", ":1:25: note: marked non-exhaustive here", }); case.addError( \\const E1 = enum { a, b, c, b, d }; \\pub export fn main() c_int { \\ const x = E1.a; \\} , &.{ ":1:28: error: duplicate enum tag", ":1:22: note: other tag here", }); case.addError( \\pub export fn main() c_int { \\ const a = true; \\ const b = @enumToInt(a); \\} , &.{ ":3:26: error: expected enum or tagged union, found bool", }); case.addError( \\pub export fn main() c_int { \\ const a = 1; \\ const b = @intToEnum(bool, a); \\} , &.{ ":3:26: error: expected enum, found bool", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ const b = @intToEnum(E, 3); \\} , &.{ ":3:15: error: enum 'test_case.E' has no tag with value 3", ":1:11: note: enum declared here", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ var x: E = .a; \\ switch (x) { \\ .a => {}, \\ .c => {}, \\ } \\} , &.{ ":4:5: error: switch must handle all possibilities", ":4:5: note: unhandled enumeration value: 'b'", ":1:11: note: enum 'test_case.E' declared here", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ var x: E = .a; \\ switch (x) { \\ .a => {}, \\ .b => {}, \\ .b => {}, \\ .c => {}, \\ } \\} , &.{ ":7:10: error: duplicate switch value", ":6:10: note: previous value here", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ var x: E = .a; \\ switch (x) { \\ .a => {}, \\ .b => {}, \\ .c => {}, \\ else => {}, \\ } \\} , &.{ ":8:14: error: unreachable else prong; all cases already handled", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ var x: E = .a; \\ switch (x) { \\ .a => {}, \\ .b => {}, \\ _ => {}, \\ } \\} , &.{ ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums", ":7:11: note: '_' prong here", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ var x = E.d; \\} , &.{ ":3:14: error: enum 'test_case.E' has no member named 'd'", ":1:11: note: enum declared here", }); case.addError( \\const E = enum { a, b, c }; \\pub export fn main() c_int { \\ var x: E = .d; \\} , &.{ ":3:17: error: enum 'test_case.E' has no field named 'd'", ":1:11: note: enum declared here", }); } ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; \\} , \\ZIG_EXTERN_C zig_noreturn void _start(void); \\ \\zig_noreturn void _start(void) { \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ ); ctx.h("simple header", linux_x64, \\export fn start() void{} , \\ZIG_EXTERN_C void start(void); \\ ); ctx.h("header with single param function", linux_x64, \\export fn start(a: u8) void{} , \\ZIG_EXTERN_C void start(uint8_t a0); \\ ); ctx.h("header with multiple param function", linux_x64, \\export fn start(a: u8, b: u8, c: u8) void{} , \\ZIG_EXTERN_C void start(uint8_t a0, uint8_t a1, uint8_t a2); \\ ); ctx.h("header with u32 param function", linux_x64, \\export fn start(a: u32) void{} , \\ZIG_EXTERN_C void start(uint32_t a0); \\ ); ctx.h("header with usize param function", linux_x64, \\export fn start(a: usize) void{} , \\ZIG_EXTERN_C void start(uintptr_t a0); \\ ); ctx.h("header with bool param function", linux_x64, \\export fn start(a: bool) void{} , \\ZIG_EXTERN_C void start(bool a0); \\ ); ctx.h("header with noreturn function", linux_x64, \\export fn start() noreturn { \\ unreachable; \\} , \\ZIG_EXTERN_C zig_noreturn void start(void); \\ ); ctx.h("header with multiple functions", linux_x64, \\export fn a() void{} \\export fn b() void{} \\export fn c() void{} , \\ZIG_EXTERN_C void a(void); \\ZIG_EXTERN_C void b(void); \\ZIG_EXTERN_C void c(void); \\ ); ctx.h("header with multiple includes", linux_x64, \\export fn start(a: u32, b: usize) void{} , \\ZIG_EXTERN_C void start(uint32_t a0, uintptr_t a1); \\ ); }