diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 71af5586ed92b0ac7d424f5325191736608c5d4f..dba43268e2e043916fcdc34176174c0782d79779 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -3,12 +3,13 @@ comptime { _ = @import("behavior/alignof.zig"); _ = @import("behavior/array.zig"); _ = @import("behavior/asm.zig"); + _ = @import("behavior/async_fn.zig"); _ = @import("behavior/atomics.zig"); + _ = @import("behavior/await_struct.zig"); _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); _ = @import("behavior/bitreverse.zig"); _ = @import("behavior/bool.zig"); - _ = @import("behavior/byteswap.zig"); _ = @import("behavior/bugs/1025.zig"); _ = @import("behavior/bugs/1076.zig"); _ = @import("behavior/bugs/1111.zig"); @@ -38,23 +39,24 @@ comptime { _ = @import("behavior/bugs/726.zig"); _ = @import("behavior/bugs/828.zig"); _ = @import("behavior/bugs/920.zig"); + _ = @import("behavior/byteswap.zig"); _ = @import("behavior/byval_arg_var.zig"); _ = @import("behavior/cancel.zig"); _ = @import("behavior/cast.zig"); _ = @import("behavior/const_slice_child.zig"); - _ = @import("behavior/coroutine_await_struct.zig"); - _ = @import("behavior/coroutines.zig"); _ = @import("behavior/defer.zig"); _ = @import("behavior/enum.zig"); _ = @import("behavior/enum_with_members.zig"); _ = @import("behavior/error.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/field_parent_ptr.zig"); + _ = @import("behavior/floatop.zig"); _ = @import("behavior/fn.zig"); _ = @import("behavior/fn_in_struct_in_comptime.zig"); _ = @import("behavior/for.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/hasdecl.zig"); + _ = @import("behavior/hasfield.zig"); _ = @import("behavior/if.zig"); _ = @import("behavior/import.zig"); _ = @import("behavior/incomplete_struct_param_tld.zig"); @@ -63,14 +65,13 @@ comptime { _ = @import("behavior/math.zig"); _ = @import("behavior/merge_error_sets.zig"); _ = @import("behavior/misc.zig"); + _ = @import("behavior/muladd.zig"); _ = @import("behavior/namespace_depends_on_compile_var.zig"); _ = @import("behavior/new_stack_call.zig"); _ = @import("behavior/null.zig"); _ = @import("behavior/optional.zig"); _ = @import("behavior/pointers.zig"); _ = @import("behavior/popcount.zig"); - _ = @import("behavior/muladd.zig"); - _ = @import("behavior/floatop.zig"); _ = @import("behavior/ptrcast.zig"); _ = @import("behavior/pub_enum.zig"); _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig"); @@ -99,5 +100,4 @@ comptime { _ = @import("behavior/void.zig"); _ = @import("behavior/while.zig"); _ = @import("behavior/widening.zig"); - _ = @import("behavior/hasfield.zig"); } diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig new file mode 100644 index 0000000000000000000000000000000000000000..2d76b47244257c2ea3de3babc8a44b80f3a7debd --- /dev/null +++ b/test/stage1/behavior/async_fn.zig @@ -0,0 +1,779 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +var global_x: i32 = 1; + +test "simple coroutine suspend and resume" { + const frame = async simpleAsyncFn(); + expect(global_x == 2); + resume frame; + expect(global_x == 3); + const af: anyframe->void = &frame; + resume frame; + expect(global_x == 4); +} +fn simpleAsyncFn() void { + global_x += 1; + suspend; + global_x += 1; + suspend; + global_x += 1; +} + +var global_y: i32 = 1; + +test "pass parameter to coroutine" { + const p = async simpleAsyncFnWithArg(2); + expect(global_y == 3); + resume p; + expect(global_y == 5); +} +fn simpleAsyncFnWithArg(delta: i32) void { + global_y += delta; + suspend; + global_y += delta; +} + +test "suspend at end of function" { + const S = struct { + var x: i32 = 1; + + fn doTheTest() void { + expect(x == 1); + const p = async suspendAtEnd(); + expect(x == 2); + } + + fn suspendAtEnd() void { + x += 1; + suspend; + } + }; + S.doTheTest(); +} + +test "local variable in async function" { + const S = struct { + var x: i32 = 0; + + fn doTheTest() void { + expect(x == 0); + const p = async add(1, 2); + expect(x == 0); + resume p; + expect(x == 0); + resume p; + expect(x == 0); + resume p; + expect(x == 3); + } + + fn add(a: i32, b: i32) void { + var accum: i32 = 0; + suspend; + accum += a; + suspend; + accum += b; + suspend; + x = accum; + } + }; + S.doTheTest(); +} + +test "calling an inferred async function" { + const S = struct { + var x: i32 = 1; + var other_frame: *@Frame(other) = undefined; + + fn doTheTest() void { + _ = async first(); + expect(x == 1); + resume other_frame.*; + expect(x == 2); + } + + fn first() void { + other(); + } + fn other() void { + other_frame = @frame(); + suspend; + x += 1; + } + }; + S.doTheTest(); +} + +test "@frameSize" { + const S = struct { + fn doTheTest() void { + { + var ptr = @ptrCast(async fn(i32) void, other); + const size = @frameSize(ptr); + expect(size == @sizeOf(@Frame(other))); + } + { + var ptr = @ptrCast(async fn() void, first); + const size = @frameSize(ptr); + expect(size == @sizeOf(@Frame(first))); + } + } + + fn first() void { + other(1); + } + fn other(param: i32) void { + var local: i32 = undefined; + suspend; + } + }; + S.doTheTest(); +} + +test "coroutine suspend, resume" { + const S = struct { + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async amain(); + seq('d'); + resume frame; + seq('h'); + + expect(std.mem.eql(u8, points, "abcdefgh")); + } + + fn amain() void { + seq('a'); + var f = async testAsyncSeq(); + seq('c'); + cancel f; + seq('g'); + } + + fn testAsyncSeq() void { + defer seq('f'); + + seq('b'); + suspend { + frame = @frame(); + } + seq('e'); + } + var points = [_]u8{'x'} ** "abcdefgh".len; + var index: usize = 0; + + fn seq(c: u8) void { + points[index] = c; + index += 1; + } + }; + S.doTheTest(); +} + +test "coroutine suspend with block" { + const p = async testSuspendBlock(); + expect(!global_result); + resume a_promise; + expect(global_result); +} + +var a_promise: anyframe = undefined; +var global_result = false; +async fn testSuspendBlock() void { + suspend { + comptime expect(@typeOf(@frame()) == *@Frame(testSuspendBlock)); + a_promise = @frame(); + } + + // Test to make sure that @frame() works as advertised (issue #1296) + // var our_handle: anyframe = @frame(); + expect(a_promise == anyframe(@frame())); + + global_result = true; +} + +var await_a_promise: anyframe = undefined; +var await_final_result: i32 = 0; + +test "coroutine await" { + await_seq('a'); + const p = async await_amain(); + await_seq('f'); + resume await_a_promise; + await_seq('i'); + expect(await_final_result == 1234); + expect(std.mem.eql(u8, await_points, "abcdefghi")); +} +async fn await_amain() void { + await_seq('b'); + const p = async await_another(); + await_seq('e'); + await_final_result = await p; + await_seq('h'); +} +async fn await_another() i32 { + await_seq('c'); + suspend { + await_seq('d'); + await_a_promise = @frame(); + } + await_seq('g'); + return 1234; +} + +var await_points = [_]u8{0} ** "abcdefghi".len; +var await_seq_index: usize = 0; + +fn await_seq(c: u8) void { + await_points[await_seq_index] = c; + await_seq_index += 1; +} + +var early_final_result: i32 = 0; + +test "coroutine await early return" { + early_seq('a'); + const p = async early_amain(); + early_seq('f'); + expect(early_final_result == 1234); + expect(std.mem.eql(u8, early_points, "abcdef")); +} +async fn early_amain() void { + early_seq('b'); + const p = async early_another(); + early_seq('d'); + early_final_result = await p; + early_seq('e'); +} +async fn early_another() i32 { + early_seq('c'); + return 1234; +} + +var early_points = [_]u8{0} ** "abcdef".len; +var early_seq_index: usize = 0; + +fn early_seq(c: u8) void { + early_points[early_seq_index] = c; + early_seq_index += 1; +} + +test "async function with dot syntax" { + const S = struct { + var y: i32 = 1; + async fn foo() void { + y += 1; + suspend; + } + }; + const p = async S.foo(); + // can't cancel in tests because they are non-async functions + expect(S.y == 2); +} + +test "async fn pointer in a struct field" { + var data: i32 = 1; + const Foo = struct { + bar: async fn (*i32) void, + }; + var foo = Foo{ .bar = simpleAsyncFn2 }; + var bytes: [64]u8 = undefined; + const f = @asyncCall(&bytes, {}, foo.bar, &data); + comptime expect(@typeOf(f) == anyframe->void); + expect(data == 2); + resume f; + expect(data == 2); + _ = async doTheAwait(f); + expect(data == 4); +} + +fn doTheAwait(f: anyframe->void) void { + await f; +} + +async fn simpleAsyncFn2(y: *i32) void { + defer y.* += 2; + y.* += 1; + suspend; +} + +test "@asyncCall with return type" { + const Foo = struct { + bar: async fn () i32, + + var global_frame: anyframe = undefined; + + async fn middle() i32 { + return afunc(); + } + + fn afunc() i32 { + global_frame = @frame(); + suspend; + return 1234; + } + }; + var foo = Foo{ .bar = Foo.middle }; + var bytes: [150]u8 = undefined; + var aresult: i32 = 0; + _ = @asyncCall(&bytes, &aresult, foo.bar); + expect(aresult == 0); + resume Foo.global_frame; + expect(aresult == 1234); +} + +test "async fn with inferred error set" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + var frame: [1]@Frame(middle) = undefined; + var result: anyerror!void = undefined; + _ = @asyncCall(@sliceToBytes(frame[0..]), &result, middle); + resume global_frame; + std.testing.expectError(error.Fail, result); + } + + async fn middle() !void { + var f = async middle2(); + return await f; + } + + fn middle2() !void { + return failing(); + } + + fn failing() !void { + global_frame = @frame(); + suspend; + return error.Fail; + } + }; + S.doTheTest(); +} + +test "error return trace across suspend points - early return" { + const p = nonFailing(); + resume p; + const p2 = async printTrace(p); +} + +test "error return trace across suspend points - async return" { + const p = nonFailing(); + const p2 = async printTrace(p); + resume p; +} + +fn nonFailing() (anyframe->anyerror!void) { + const Static = struct { + var frame: @Frame(suspendThenFail) = undefined; + }; + Static.frame = async suspendThenFail(); + return &Static.frame; +} +async fn suspendThenFail() anyerror!void { + suspend; + return error.Fail; +} +async fn printTrace(p: anyframe->(anyerror!void)) void { + (await p) catch |e| { + std.testing.expect(e == error.Fail); + if (@errorReturnTrace()) |trace| { + expect(trace.index == 1); + } else switch (builtin.mode) { + .Debug, .ReleaseSafe => @panic("expected return trace"), + .ReleaseFast, .ReleaseSmall => {}, + } + }; +} + +test "break from suspend" { + var my_result: i32 = 1; + const p = async testBreakFromSuspend(&my_result); + // can't cancel here + std.testing.expect(my_result == 2); +} +async fn testBreakFromSuspend(my_result: *i32) void { + suspend { + resume @frame(); + } + my_result.* += 1; + suspend; + my_result.* += 1; +} + +test "heap allocated async function frame" { + const S = struct { + var x: i32 = 42; + + fn doTheTest() !void { + const frame = try std.heap.direct_allocator.create(@Frame(someFunc)); + defer std.heap.direct_allocator.destroy(frame); + + expect(x == 42); + frame.* = async someFunc(); + expect(x == 43); + resume frame; + expect(x == 44); + } + + fn someFunc() void { + x += 1; + suspend; + x += 1; + } + }; + try S.doTheTest(); +} + +test "async function call return value" { + const S = struct { + var frame: anyframe = undefined; + var pt = Point{.x = 10, .y = 11 }; + + fn doTheTest() void { + expectEqual(pt.x, 10); + expectEqual(pt.y, 11); + _ = async first(); + expectEqual(pt.x, 10); + expectEqual(pt.y, 11); + resume frame; + expectEqual(pt.x, 1); + expectEqual(pt.y, 2); + } + + fn first() void { + pt = second(1, 2); + } + + fn second(x: i32, y: i32) Point { + return other(x, y); + } + + fn other(x: i32, y: i32) Point { + frame = @frame(); + suspend; + return Point{ + .x = x, + .y = y, + }; + } + + const Point = struct { + x: i32, + y: i32, + }; + }; + S.doTheTest(); +} + +test "suspension points inside branching control flow" { + const S = struct { + var result: i32 = 10; + + fn doTheTest() void { + expect(10 == result); + var frame = async func(true); + expect(10 == result); + resume frame; + expect(11 == result); + resume frame; + expect(12 == result); + resume frame; + expect(13 == result); + } + + fn func(b: bool) void { + while (b) { + suspend; + result += 1; + } + } + }; + S.doTheTest(); +} + +test "call async function which has struct return type" { + const S = struct { + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async atest(); + resume frame; + } + + fn atest() void { + const result = func(); + expect(result.x == 5); + expect(result.y == 6); + } + + const Point = struct { + x: usize, + y: usize, + }; + + fn func() Point { + suspend { + frame = @frame(); + } + return Point{ + .x = 5, + .y = 6, + }; + } + }; + S.doTheTest(); +} + +test "errdefers in scope get run when canceling async fn call" { + const S = struct { + var frame: anyframe = undefined; + var x: u32 = 0; + + fn doTheTest() void { + x = 9; + _ = async cancelIt(); + resume frame; + expect(x == 6); + + x = 9; + _ = async awaitIt(); + resume frame; + expect(x == 11); + } + + fn cancelIt() void { + var f = async func(); + cancel f; + } + + fn awaitIt() void { + var f = async func(); + await f; + } + + fn func() void { + defer x += 1; + errdefer x /= 2; + defer x += 1; + suspend { + frame = @frame(); + } + } + }; + S.doTheTest(); +} + +test "pass string literal to async function" { + const S = struct { + var frame: anyframe = undefined; + var ok: bool = false; + + fn doTheTest() void { + _ = async hello("hello"); + resume frame; + expect(ok); + } + + fn hello(msg: []const u8) void { + frame = @frame(); + suspend; + expectEqual(([]const u8)("hello"), msg); + ok = true; + } + }; + S.doTheTest(); +} + +test "cancel inside an errdefer" { + const S = struct { + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async amainWrap(); + resume frame; + } + + fn amainWrap() !void { + var foo = async func(); + errdefer cancel foo; + return error.Bad; + } + + fn func() void { + frame = @frame(); + suspend; + } + + }; + S.doTheTest(); +} + +test "combining try with errdefer cancel" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + _ = async amain(); + resume frame; + expect(ok); + } + + fn amain() !void { + var f = async func("https://example.com/"); + errdefer cancel f; + + _ = try await f; + } + + fn func(url: []const u8) ![]u8 { + errdefer ok = true; + frame = @frame(); + suspend; + return error.Bad; + } + + }; + S.doTheTest(); +} + +test "try in an async function with error union and non-zero-bit payload" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + _ = async amain(); + resume frame; + expect(ok); + } + + fn amain() void { + std.testing.expectError(error.Bad, theProblem()); + ok = true; + } + + fn theProblem() ![]u8 { + frame = @frame(); + suspend; + const result = try other(); + return result; + } + + fn other() ![]u8 { + return error.Bad; + } + }; + S.doTheTest(); +} + +test "returning a const error from async function" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + _ = async amain(); + resume frame; + expect(ok); + } + + fn amain() !void { + var download_frame = async fetchUrl(10, "a string"); + const download_text = try await download_frame; + + @panic("should not get here"); + } + + fn fetchUrl(unused: i32, url: []const u8) ![]u8 { + frame = @frame(); + suspend; + ok = true; + return error.OutOfMemory; + } + }; + S.doTheTest(); +} + +test "async/await typical usage" { + inline for ([_]bool{false, true}) |b1| { + inline for ([_]bool{false, true}) |b2| { + testAsyncAwaitTypicalUsage(b1, b2).doTheTest(); + } + } +} + +fn testAsyncAwaitTypicalUsage(comptime simulate_fail_download: bool, comptime simulate_fail_file: bool) type { + return struct { + fn doTheTest() void { + _ = async amainWrap(); + resume global_file_frame; + resume global_download_frame; + } + fn amainWrap() void { + if (amain()) |_| { + expect(!simulate_fail_download); + expect(!simulate_fail_file); + } else |e| switch (e) { + error.NoResponse => expect(simulate_fail_download), + error.FileNotFound => expect(simulate_fail_file), + else => @panic("test failure"), + } + } + + fn amain() !void { + const allocator = std.heap.direct_allocator; // TODO once we have the debug allocator, use that, so that this can detect leaks + var download_frame = async fetchUrl(allocator, "https://example.com/"); + errdefer cancel download_frame; + + var file_frame = async readFile(allocator, "something.txt"); + errdefer cancel file_frame; + + const download_text = try await download_frame; + defer allocator.free(download_text); + + const file_text = try await file_frame; + defer allocator.free(file_text); + + expect(std.mem.eql(u8, "expected download text", download_text)); + expect(std.mem.eql(u8, "expected file text", file_text)); + } + + var global_download_frame: anyframe = undefined; + fn fetchUrl(allocator: *std.mem.Allocator, url: []const u8) anyerror![]u8 { + global_download_frame = @frame(); + const result = try std.mem.dupe(allocator, u8, "expected download text"); + errdefer allocator.free(result); + suspend; + if (simulate_fail_download) return error.NoResponse; + return result; + } + + var global_file_frame: anyframe = undefined; + fn readFile(allocator: *std.mem.Allocator, filename: []const u8) anyerror![]u8 { + global_file_frame = @frame(); + const result = try std.mem.dupe(allocator, u8, "expected file text"); + errdefer allocator.free(result); + suspend; + if (simulate_fail_file) return error.FileNotFound; + return result; + } + }; +} + +test "alignment of local variables in async functions" { + const S = struct { + fn doTheTest() void { + var y: u8 = 123; + var x: u8 align(128) = 1; + expect(@ptrToInt(&x) % 128 == 0); + } + }; + S.doTheTest(); +} diff --git a/test/stage1/behavior/await_struct.zig b/test/stage1/behavior/await_struct.zig new file mode 100644 index 0000000000000000000000000000000000000000..a649b0a39b3ac642db1240f96087d151504cbd6c --- /dev/null +++ b/test/stage1/behavior/await_struct.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const expect = std.testing.expect; + +const Foo = struct { + x: i32, +}; + +var await_a_promise: anyframe = undefined; +var await_final_result = Foo{ .x = 0 }; + +test "coroutine await struct" { + await_seq('a'); + const p = async await_amain(); + await_seq('f'); + resume await_a_promise; + await_seq('i'); + expect(await_final_result.x == 1234); + expect(std.mem.eql(u8, await_points, "abcdefghi")); +} +async fn await_amain() void { + await_seq('b'); + const p = async await_another(); + await_seq('e'); + await_final_result = await p; + await_seq('h'); +} +async fn await_another() Foo { + await_seq('c'); + suspend { + await_seq('d'); + await_a_promise = @frame(); + } + await_seq('g'); + return Foo{ .x = 1234 }; +} + +var await_points = [_]u8{0} ** "abcdefghi".len; +var await_seq_index: usize = 0; + +fn await_seq(c: u8) void { + await_points[await_seq_index] = c; + await_seq_index += 1; +} diff --git a/test/stage1/behavior/coroutine_await_struct.zig b/test/stage1/behavior/coroutine_await_struct.zig deleted file mode 100644 index a649b0a39b3ac642db1240f96087d151504cbd6c..0000000000000000000000000000000000000000 --- a/test/stage1/behavior/coroutine_await_struct.zig +++ /dev/null @@ -1,44 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const expect = std.testing.expect; - -const Foo = struct { - x: i32, -}; - -var await_a_promise: anyframe = undefined; -var await_final_result = Foo{ .x = 0 }; - -test "coroutine await struct" { - await_seq('a'); - const p = async await_amain(); - await_seq('f'); - resume await_a_promise; - await_seq('i'); - expect(await_final_result.x == 1234); - expect(std.mem.eql(u8, await_points, "abcdefghi")); -} -async fn await_amain() void { - await_seq('b'); - const p = async await_another(); - await_seq('e'); - await_final_result = await p; - await_seq('h'); -} -async fn await_another() Foo { - await_seq('c'); - suspend { - await_seq('d'); - await_a_promise = @frame(); - } - await_seq('g'); - return Foo{ .x = 1234 }; -} - -var await_points = [_]u8{0} ** "abcdefghi".len; -var await_seq_index: usize = 0; - -fn await_seq(c: u8) void { - await_points[await_seq_index] = c; - await_seq_index += 1; -} diff --git a/test/stage1/behavior/coroutines.zig b/test/stage1/behavior/coroutines.zig deleted file mode 100644 index 2d76b47244257c2ea3de3babc8a44b80f3a7debd..0000000000000000000000000000000000000000 --- a/test/stage1/behavior/coroutines.zig +++ /dev/null @@ -1,779 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const expect = std.testing.expect; -const expectEqual = std.testing.expectEqual; - -var global_x: i32 = 1; - -test "simple coroutine suspend and resume" { - const frame = async simpleAsyncFn(); - expect(global_x == 2); - resume frame; - expect(global_x == 3); - const af: anyframe->void = &frame; - resume frame; - expect(global_x == 4); -} -fn simpleAsyncFn() void { - global_x += 1; - suspend; - global_x += 1; - suspend; - global_x += 1; -} - -var global_y: i32 = 1; - -test "pass parameter to coroutine" { - const p = async simpleAsyncFnWithArg(2); - expect(global_y == 3); - resume p; - expect(global_y == 5); -} -fn simpleAsyncFnWithArg(delta: i32) void { - global_y += delta; - suspend; - global_y += delta; -} - -test "suspend at end of function" { - const S = struct { - var x: i32 = 1; - - fn doTheTest() void { - expect(x == 1); - const p = async suspendAtEnd(); - expect(x == 2); - } - - fn suspendAtEnd() void { - x += 1; - suspend; - } - }; - S.doTheTest(); -} - -test "local variable in async function" { - const S = struct { - var x: i32 = 0; - - fn doTheTest() void { - expect(x == 0); - const p = async add(1, 2); - expect(x == 0); - resume p; - expect(x == 0); - resume p; - expect(x == 0); - resume p; - expect(x == 3); - } - - fn add(a: i32, b: i32) void { - var accum: i32 = 0; - suspend; - accum += a; - suspend; - accum += b; - suspend; - x = accum; - } - }; - S.doTheTest(); -} - -test "calling an inferred async function" { - const S = struct { - var x: i32 = 1; - var other_frame: *@Frame(other) = undefined; - - fn doTheTest() void { - _ = async first(); - expect(x == 1); - resume other_frame.*; - expect(x == 2); - } - - fn first() void { - other(); - } - fn other() void { - other_frame = @frame(); - suspend; - x += 1; - } - }; - S.doTheTest(); -} - -test "@frameSize" { - const S = struct { - fn doTheTest() void { - { - var ptr = @ptrCast(async fn(i32) void, other); - const size = @frameSize(ptr); - expect(size == @sizeOf(@Frame(other))); - } - { - var ptr = @ptrCast(async fn() void, first); - const size = @frameSize(ptr); - expect(size == @sizeOf(@Frame(first))); - } - } - - fn first() void { - other(1); - } - fn other(param: i32) void { - var local: i32 = undefined; - suspend; - } - }; - S.doTheTest(); -} - -test "coroutine suspend, resume" { - const S = struct { - var frame: anyframe = undefined; - - fn doTheTest() void { - _ = async amain(); - seq('d'); - resume frame; - seq('h'); - - expect(std.mem.eql(u8, points, "abcdefgh")); - } - - fn amain() void { - seq('a'); - var f = async testAsyncSeq(); - seq('c'); - cancel f; - seq('g'); - } - - fn testAsyncSeq() void { - defer seq('f'); - - seq('b'); - suspend { - frame = @frame(); - } - seq('e'); - } - var points = [_]u8{'x'} ** "abcdefgh".len; - var index: usize = 0; - - fn seq(c: u8) void { - points[index] = c; - index += 1; - } - }; - S.doTheTest(); -} - -test "coroutine suspend with block" { - const p = async testSuspendBlock(); - expect(!global_result); - resume a_promise; - expect(global_result); -} - -var a_promise: anyframe = undefined; -var global_result = false; -async fn testSuspendBlock() void { - suspend { - comptime expect(@typeOf(@frame()) == *@Frame(testSuspendBlock)); - a_promise = @frame(); - } - - // Test to make sure that @frame() works as advertised (issue #1296) - // var our_handle: anyframe = @frame(); - expect(a_promise == anyframe(@frame())); - - global_result = true; -} - -var await_a_promise: anyframe = undefined; -var await_final_result: i32 = 0; - -test "coroutine await" { - await_seq('a'); - const p = async await_amain(); - await_seq('f'); - resume await_a_promise; - await_seq('i'); - expect(await_final_result == 1234); - expect(std.mem.eql(u8, await_points, "abcdefghi")); -} -async fn await_amain() void { - await_seq('b'); - const p = async await_another(); - await_seq('e'); - await_final_result = await p; - await_seq('h'); -} -async fn await_another() i32 { - await_seq('c'); - suspend { - await_seq('d'); - await_a_promise = @frame(); - } - await_seq('g'); - return 1234; -} - -var await_points = [_]u8{0} ** "abcdefghi".len; -var await_seq_index: usize = 0; - -fn await_seq(c: u8) void { - await_points[await_seq_index] = c; - await_seq_index += 1; -} - -var early_final_result: i32 = 0; - -test "coroutine await early return" { - early_seq('a'); - const p = async early_amain(); - early_seq('f'); - expect(early_final_result == 1234); - expect(std.mem.eql(u8, early_points, "abcdef")); -} -async fn early_amain() void { - early_seq('b'); - const p = async early_another(); - early_seq('d'); - early_final_result = await p; - early_seq('e'); -} -async fn early_another() i32 { - early_seq('c'); - return 1234; -} - -var early_points = [_]u8{0} ** "abcdef".len; -var early_seq_index: usize = 0; - -fn early_seq(c: u8) void { - early_points[early_seq_index] = c; - early_seq_index += 1; -} - -test "async function with dot syntax" { - const S = struct { - var y: i32 = 1; - async fn foo() void { - y += 1; - suspend; - } - }; - const p = async S.foo(); - // can't cancel in tests because they are non-async functions - expect(S.y == 2); -} - -test "async fn pointer in a struct field" { - var data: i32 = 1; - const Foo = struct { - bar: async fn (*i32) void, - }; - var foo = Foo{ .bar = simpleAsyncFn2 }; - var bytes: [64]u8 = undefined; - const f = @asyncCall(&bytes, {}, foo.bar, &data); - comptime expect(@typeOf(f) == anyframe->void); - expect(data == 2); - resume f; - expect(data == 2); - _ = async doTheAwait(f); - expect(data == 4); -} - -fn doTheAwait(f: anyframe->void) void { - await f; -} - -async fn simpleAsyncFn2(y: *i32) void { - defer y.* += 2; - y.* += 1; - suspend; -} - -test "@asyncCall with return type" { - const Foo = struct { - bar: async fn () i32, - - var global_frame: anyframe = undefined; - - async fn middle() i32 { - return afunc(); - } - - fn afunc() i32 { - global_frame = @frame(); - suspend; - return 1234; - } - }; - var foo = Foo{ .bar = Foo.middle }; - var bytes: [150]u8 = undefined; - var aresult: i32 = 0; - _ = @asyncCall(&bytes, &aresult, foo.bar); - expect(aresult == 0); - resume Foo.global_frame; - expect(aresult == 1234); -} - -test "async fn with inferred error set" { - const S = struct { - var global_frame: anyframe = undefined; - - fn doTheTest() void { - var frame: [1]@Frame(middle) = undefined; - var result: anyerror!void = undefined; - _ = @asyncCall(@sliceToBytes(frame[0..]), &result, middle); - resume global_frame; - std.testing.expectError(error.Fail, result); - } - - async fn middle() !void { - var f = async middle2(); - return await f; - } - - fn middle2() !void { - return failing(); - } - - fn failing() !void { - global_frame = @frame(); - suspend; - return error.Fail; - } - }; - S.doTheTest(); -} - -test "error return trace across suspend points - early return" { - const p = nonFailing(); - resume p; - const p2 = async printTrace(p); -} - -test "error return trace across suspend points - async return" { - const p = nonFailing(); - const p2 = async printTrace(p); - resume p; -} - -fn nonFailing() (anyframe->anyerror!void) { - const Static = struct { - var frame: @Frame(suspendThenFail) = undefined; - }; - Static.frame = async suspendThenFail(); - return &Static.frame; -} -async fn suspendThenFail() anyerror!void { - suspend; - return error.Fail; -} -async fn printTrace(p: anyframe->(anyerror!void)) void { - (await p) catch |e| { - std.testing.expect(e == error.Fail); - if (@errorReturnTrace()) |trace| { - expect(trace.index == 1); - } else switch (builtin.mode) { - .Debug, .ReleaseSafe => @panic("expected return trace"), - .ReleaseFast, .ReleaseSmall => {}, - } - }; -} - -test "break from suspend" { - var my_result: i32 = 1; - const p = async testBreakFromSuspend(&my_result); - // can't cancel here - std.testing.expect(my_result == 2); -} -async fn testBreakFromSuspend(my_result: *i32) void { - suspend { - resume @frame(); - } - my_result.* += 1; - suspend; - my_result.* += 1; -} - -test "heap allocated async function frame" { - const S = struct { - var x: i32 = 42; - - fn doTheTest() !void { - const frame = try std.heap.direct_allocator.create(@Frame(someFunc)); - defer std.heap.direct_allocator.destroy(frame); - - expect(x == 42); - frame.* = async someFunc(); - expect(x == 43); - resume frame; - expect(x == 44); - } - - fn someFunc() void { - x += 1; - suspend; - x += 1; - } - }; - try S.doTheTest(); -} - -test "async function call return value" { - const S = struct { - var frame: anyframe = undefined; - var pt = Point{.x = 10, .y = 11 }; - - fn doTheTest() void { - expectEqual(pt.x, 10); - expectEqual(pt.y, 11); - _ = async first(); - expectEqual(pt.x, 10); - expectEqual(pt.y, 11); - resume frame; - expectEqual(pt.x, 1); - expectEqual(pt.y, 2); - } - - fn first() void { - pt = second(1, 2); - } - - fn second(x: i32, y: i32) Point { - return other(x, y); - } - - fn other(x: i32, y: i32) Point { - frame = @frame(); - suspend; - return Point{ - .x = x, - .y = y, - }; - } - - const Point = struct { - x: i32, - y: i32, - }; - }; - S.doTheTest(); -} - -test "suspension points inside branching control flow" { - const S = struct { - var result: i32 = 10; - - fn doTheTest() void { - expect(10 == result); - var frame = async func(true); - expect(10 == result); - resume frame; - expect(11 == result); - resume frame; - expect(12 == result); - resume frame; - expect(13 == result); - } - - fn func(b: bool) void { - while (b) { - suspend; - result += 1; - } - } - }; - S.doTheTest(); -} - -test "call async function which has struct return type" { - const S = struct { - var frame: anyframe = undefined; - - fn doTheTest() void { - _ = async atest(); - resume frame; - } - - fn atest() void { - const result = func(); - expect(result.x == 5); - expect(result.y == 6); - } - - const Point = struct { - x: usize, - y: usize, - }; - - fn func() Point { - suspend { - frame = @frame(); - } - return Point{ - .x = 5, - .y = 6, - }; - } - }; - S.doTheTest(); -} - -test "errdefers in scope get run when canceling async fn call" { - const S = struct { - var frame: anyframe = undefined; - var x: u32 = 0; - - fn doTheTest() void { - x = 9; - _ = async cancelIt(); - resume frame; - expect(x == 6); - - x = 9; - _ = async awaitIt(); - resume frame; - expect(x == 11); - } - - fn cancelIt() void { - var f = async func(); - cancel f; - } - - fn awaitIt() void { - var f = async func(); - await f; - } - - fn func() void { - defer x += 1; - errdefer x /= 2; - defer x += 1; - suspend { - frame = @frame(); - } - } - }; - S.doTheTest(); -} - -test "pass string literal to async function" { - const S = struct { - var frame: anyframe = undefined; - var ok: bool = false; - - fn doTheTest() void { - _ = async hello("hello"); - resume frame; - expect(ok); - } - - fn hello(msg: []const u8) void { - frame = @frame(); - suspend; - expectEqual(([]const u8)("hello"), msg); - ok = true; - } - }; - S.doTheTest(); -} - -test "cancel inside an errdefer" { - const S = struct { - var frame: anyframe = undefined; - - fn doTheTest() void { - _ = async amainWrap(); - resume frame; - } - - fn amainWrap() !void { - var foo = async func(); - errdefer cancel foo; - return error.Bad; - } - - fn func() void { - frame = @frame(); - suspend; - } - - }; - S.doTheTest(); -} - -test "combining try with errdefer cancel" { - const S = struct { - var frame: anyframe = undefined; - var ok = false; - - fn doTheTest() void { - _ = async amain(); - resume frame; - expect(ok); - } - - fn amain() !void { - var f = async func("https://example.com/"); - errdefer cancel f; - - _ = try await f; - } - - fn func(url: []const u8) ![]u8 { - errdefer ok = true; - frame = @frame(); - suspend; - return error.Bad; - } - - }; - S.doTheTest(); -} - -test "try in an async function with error union and non-zero-bit payload" { - const S = struct { - var frame: anyframe = undefined; - var ok = false; - - fn doTheTest() void { - _ = async amain(); - resume frame; - expect(ok); - } - - fn amain() void { - std.testing.expectError(error.Bad, theProblem()); - ok = true; - } - - fn theProblem() ![]u8 { - frame = @frame(); - suspend; - const result = try other(); - return result; - } - - fn other() ![]u8 { - return error.Bad; - } - }; - S.doTheTest(); -} - -test "returning a const error from async function" { - const S = struct { - var frame: anyframe = undefined; - var ok = false; - - fn doTheTest() void { - _ = async amain(); - resume frame; - expect(ok); - } - - fn amain() !void { - var download_frame = async fetchUrl(10, "a string"); - const download_text = try await download_frame; - - @panic("should not get here"); - } - - fn fetchUrl(unused: i32, url: []const u8) ![]u8 { - frame = @frame(); - suspend; - ok = true; - return error.OutOfMemory; - } - }; - S.doTheTest(); -} - -test "async/await typical usage" { - inline for ([_]bool{false, true}) |b1| { - inline for ([_]bool{false, true}) |b2| { - testAsyncAwaitTypicalUsage(b1, b2).doTheTest(); - } - } -} - -fn testAsyncAwaitTypicalUsage(comptime simulate_fail_download: bool, comptime simulate_fail_file: bool) type { - return struct { - fn doTheTest() void { - _ = async amainWrap(); - resume global_file_frame; - resume global_download_frame; - } - fn amainWrap() void { - if (amain()) |_| { - expect(!simulate_fail_download); - expect(!simulate_fail_file); - } else |e| switch (e) { - error.NoResponse => expect(simulate_fail_download), - error.FileNotFound => expect(simulate_fail_file), - else => @panic("test failure"), - } - } - - fn amain() !void { - const allocator = std.heap.direct_allocator; // TODO once we have the debug allocator, use that, so that this can detect leaks - var download_frame = async fetchUrl(allocator, "https://example.com/"); - errdefer cancel download_frame; - - var file_frame = async readFile(allocator, "something.txt"); - errdefer cancel file_frame; - - const download_text = try await download_frame; - defer allocator.free(download_text); - - const file_text = try await file_frame; - defer allocator.free(file_text); - - expect(std.mem.eql(u8, "expected download text", download_text)); - expect(std.mem.eql(u8, "expected file text", file_text)); - } - - var global_download_frame: anyframe = undefined; - fn fetchUrl(allocator: *std.mem.Allocator, url: []const u8) anyerror![]u8 { - global_download_frame = @frame(); - const result = try std.mem.dupe(allocator, u8, "expected download text"); - errdefer allocator.free(result); - suspend; - if (simulate_fail_download) return error.NoResponse; - return result; - } - - var global_file_frame: anyframe = undefined; - fn readFile(allocator: *std.mem.Allocator, filename: []const u8) anyerror![]u8 { - global_file_frame = @frame(); - const result = try std.mem.dupe(allocator, u8, "expected file text"); - errdefer allocator.free(result); - suspend; - if (simulate_fail_file) return error.FileNotFound; - return result; - } - }; -} - -test "alignment of local variables in async functions" { - const S = struct { - fn doTheTest() void { - var y: u8 = 123; - var x: u8 align(128) = 1; - expect(@ptrToInt(&x) % 128 == 0); - } - }; - S.doTheTest(); -}