mirror of
https://github.com/ziglang/zig.git
synced 2024-11-27 07:32:44 +00:00
f27d3409bd
My previous commit added a new behavior test that passes for stage2 but I forgot to check whether it passes for stage1. Since it does not, it has to be disabled. Additionally, this commit organizes behavior tests; there is no longer a section of tests only passing for stage1. Instead, tests are disabled on an individual basis. There is an except for the file which has global assembly in it.
1899 lines
52 KiB
Zig
1899 lines
52 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const expect = std.testing.expect;
|
|
const expectEqual = std.testing.expectEqual;
|
|
const expectEqualStrings = std.testing.expectEqualStrings;
|
|
const expectError = std.testing.expectError;
|
|
|
|
var global_x: i32 = 1;
|
|
|
|
test "simple coroutine suspend and resume" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
var frame = async simpleAsyncFn();
|
|
try expect(global_x == 2);
|
|
resume frame;
|
|
try expect(global_x == 3);
|
|
const af: anyframe->void = &frame;
|
|
_ = af;
|
|
resume frame;
|
|
try 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" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
var p = async simpleAsyncFnWithArg(2);
|
|
try expect(global_y == 3);
|
|
resume p;
|
|
try expect(global_y == 5);
|
|
}
|
|
fn simpleAsyncFnWithArg(delta: i32) void {
|
|
global_y += delta;
|
|
suspend {}
|
|
global_y += delta;
|
|
}
|
|
|
|
test "suspend at end of function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var x: i32 = 1;
|
|
|
|
fn doTheTest() !void {
|
|
try expect(x == 1);
|
|
const p = async suspendAtEnd();
|
|
_ = p;
|
|
try expect(x == 2);
|
|
}
|
|
|
|
fn suspendAtEnd() void {
|
|
x += 1;
|
|
suspend {}
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "local variable in async function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var x: i32 = 0;
|
|
|
|
fn doTheTest() !void {
|
|
try expect(x == 0);
|
|
var p = async add(1, 2);
|
|
try expect(x == 0);
|
|
resume p;
|
|
try expect(x == 0);
|
|
resume p;
|
|
try expect(x == 0);
|
|
resume p;
|
|
try expect(x == 3);
|
|
}
|
|
|
|
fn add(a: i32, b: i32) void {
|
|
var accum: i32 = 0;
|
|
suspend {}
|
|
accum += a;
|
|
suspend {}
|
|
accum += b;
|
|
suspend {}
|
|
x = accum;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "calling an inferred async function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var x: i32 = 1;
|
|
var other_frame: *@Frame(other) = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async first();
|
|
try expect(x == 1);
|
|
resume other_frame.*;
|
|
try expect(x == 2);
|
|
}
|
|
|
|
fn first() void {
|
|
other();
|
|
}
|
|
fn other() void {
|
|
other_frame = @frame();
|
|
suspend {}
|
|
x += 1;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "@frameSize" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
if (builtin.target.cpu.arch == .thumb or builtin.target.cpu.arch == .thumbeb)
|
|
return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn doTheTest() !void {
|
|
{
|
|
var ptr = @ptrCast(fn (i32) callconv(.Async) void, other);
|
|
const size = @frameSize(ptr);
|
|
try expect(size == @sizeOf(@Frame(other)));
|
|
}
|
|
{
|
|
var ptr = @ptrCast(fn () callconv(.Async) void, first);
|
|
const size = @frameSize(ptr);
|
|
try expect(size == @sizeOf(@Frame(first)));
|
|
}
|
|
}
|
|
|
|
fn first() void {
|
|
other(1);
|
|
}
|
|
fn other(param: i32) void {
|
|
_ = param;
|
|
var local: i32 = undefined;
|
|
_ = local;
|
|
suspend {}
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "coroutine suspend, resume" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async amain();
|
|
seq('d');
|
|
resume frame;
|
|
seq('h');
|
|
|
|
try expect(std.mem.eql(u8, &points, "abcdefgh"));
|
|
}
|
|
|
|
fn amain() void {
|
|
seq('a');
|
|
var f = async testAsyncSeq();
|
|
seq('c');
|
|
await 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;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "coroutine suspend with block" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const p = async testSuspendBlock();
|
|
_ = p;
|
|
try expect(!global_result);
|
|
resume a_promise;
|
|
try expect(global_result);
|
|
}
|
|
|
|
var a_promise: anyframe = undefined;
|
|
var global_result = false;
|
|
fn testSuspendBlock() callconv(.Async) void {
|
|
suspend {
|
|
comptime expect(@TypeOf(@frame()) == *@Frame(testSuspendBlock)) catch unreachable;
|
|
a_promise = @frame();
|
|
}
|
|
|
|
// Test to make sure that @frame() works as advertised (issue #1296)
|
|
// var our_handle: anyframe = @frame();
|
|
expect(a_promise == @as(anyframe, @frame())) catch @panic("test failed");
|
|
|
|
global_result = true;
|
|
}
|
|
|
|
var await_a_promise: anyframe = undefined;
|
|
var await_final_result: i32 = 0;
|
|
|
|
test "coroutine await" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
await_seq('a');
|
|
var p = async await_amain();
|
|
_ = p;
|
|
await_seq('f');
|
|
resume await_a_promise;
|
|
await_seq('i');
|
|
try expect(await_final_result == 1234);
|
|
try expect(std.mem.eql(u8, &await_points, "abcdefghi"));
|
|
}
|
|
fn await_amain() callconv(.Async) void {
|
|
await_seq('b');
|
|
var p = async await_another();
|
|
await_seq('e');
|
|
await_final_result = await p;
|
|
await_seq('h');
|
|
}
|
|
fn await_another() callconv(.Async) 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" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
early_seq('a');
|
|
var p = async early_amain();
|
|
_ = p;
|
|
early_seq('f');
|
|
try expect(early_final_result == 1234);
|
|
try expect(std.mem.eql(u8, &early_points, "abcdef"));
|
|
}
|
|
fn early_amain() callconv(.Async) void {
|
|
early_seq('b');
|
|
var p = async early_another();
|
|
early_seq('d');
|
|
early_final_result = await p;
|
|
early_seq('e');
|
|
}
|
|
fn early_another() callconv(.Async) 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" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var y: i32 = 1;
|
|
fn foo() callconv(.Async) void {
|
|
y += 1;
|
|
suspend {}
|
|
}
|
|
};
|
|
const p = async S.foo();
|
|
_ = p;
|
|
try expect(S.y == 2);
|
|
}
|
|
|
|
test "async fn pointer in a struct field" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
var data: i32 = 1;
|
|
const Foo = struct {
|
|
bar: fn (*i32) callconv(.Async) void,
|
|
};
|
|
var foo = Foo{ .bar = simpleAsyncFn2 };
|
|
var bytes: [64]u8 align(16) = undefined;
|
|
const f = @asyncCall(&bytes, {}, foo.bar, .{&data});
|
|
comptime try expect(@TypeOf(f) == anyframe->void);
|
|
try expect(data == 2);
|
|
resume f;
|
|
try expect(data == 4);
|
|
_ = async doTheAwait(f);
|
|
try expect(data == 4);
|
|
}
|
|
|
|
fn doTheAwait(f: anyframe->void) void {
|
|
await f;
|
|
}
|
|
fn simpleAsyncFn2(y: *i32) callconv(.Async) void {
|
|
defer y.* += 2;
|
|
y.* += 1;
|
|
suspend {}
|
|
}
|
|
|
|
test "@asyncCall with return type" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const Foo = struct {
|
|
bar: fn () callconv(.Async) i32,
|
|
|
|
var global_frame: anyframe = undefined;
|
|
fn middle() callconv(.Async) i32 {
|
|
return afunc();
|
|
}
|
|
|
|
fn afunc() i32 {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
return 1234;
|
|
}
|
|
};
|
|
var foo = Foo{ .bar = Foo.middle };
|
|
var bytes: [150]u8 align(16) = undefined;
|
|
var aresult: i32 = 0;
|
|
_ = @asyncCall(&bytes, &aresult, foo.bar, .{});
|
|
try expect(aresult == 0);
|
|
resume Foo.global_frame;
|
|
try expect(aresult == 1234);
|
|
}
|
|
|
|
test "async fn with inferred error set" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
var frame: [1]@Frame(middle) = undefined;
|
|
var fn_ptr = middle;
|
|
var result: @typeInfo(@typeInfo(@TypeOf(fn_ptr)).Fn.return_type.?).ErrorUnion.error_set!void = undefined;
|
|
_ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, fn_ptr, .{});
|
|
resume global_frame;
|
|
try std.testing.expectError(error.Fail, result);
|
|
}
|
|
fn middle() callconv(.Async) !void {
|
|
var f = async middle2();
|
|
return await f;
|
|
}
|
|
|
|
fn middle2() !void {
|
|
return failing();
|
|
}
|
|
|
|
fn failing() !void {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
return error.Fail;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "error return trace across suspend points - early return" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const p = nonFailing();
|
|
resume p;
|
|
const p2 = async printTrace(p);
|
|
_ = p2;
|
|
}
|
|
|
|
test "error return trace across suspend points - async return" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const p = nonFailing();
|
|
const p2 = async printTrace(p);
|
|
_ = p2;
|
|
resume p;
|
|
}
|
|
|
|
fn nonFailing() (anyframe->anyerror!void) {
|
|
const Static = struct {
|
|
var frame: @Frame(suspendThenFail) = undefined;
|
|
};
|
|
Static.frame = async suspendThenFail();
|
|
return &Static.frame;
|
|
}
|
|
fn suspendThenFail() callconv(.Async) anyerror!void {
|
|
suspend {}
|
|
return error.Fail;
|
|
}
|
|
fn printTrace(p: anyframe->(anyerror!void)) callconv(.Async) void {
|
|
(await p) catch |e| {
|
|
std.testing.expect(e == error.Fail) catch @panic("test failure");
|
|
if (@errorReturnTrace()) |trace| {
|
|
expect(trace.index == 1) catch @panic("test failure");
|
|
} else switch (builtin.mode) {
|
|
.Debug, .ReleaseSafe => @panic("expected return trace"),
|
|
.ReleaseFast, .ReleaseSmall => {},
|
|
}
|
|
};
|
|
}
|
|
|
|
test "break from suspend" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
var my_result: i32 = 1;
|
|
const p = async testBreakFromSuspend(&my_result);
|
|
_ = p;
|
|
try std.testing.expect(my_result == 2);
|
|
}
|
|
fn testBreakFromSuspend(my_result: *i32) callconv(.Async) void {
|
|
suspend {
|
|
resume @frame();
|
|
}
|
|
my_result.* += 1;
|
|
suspend {}
|
|
my_result.* += 1;
|
|
}
|
|
|
|
test "heap allocated async function frame" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var x: i32 = 42;
|
|
|
|
fn doTheTest() !void {
|
|
const frame = try std.testing.allocator.create(@Frame(someFunc));
|
|
defer std.testing.allocator.destroy(frame);
|
|
|
|
try expect(x == 42);
|
|
frame.* = async someFunc();
|
|
try expect(x == 43);
|
|
resume frame;
|
|
try expect(x == 44);
|
|
}
|
|
|
|
fn someFunc() void {
|
|
x += 1;
|
|
suspend {}
|
|
x += 1;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "async function call return value" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
var pt = Point{ .x = 10, .y = 11 };
|
|
|
|
fn doTheTest() !void {
|
|
try expectEqual(pt.x, 10);
|
|
try expectEqual(pt.y, 11);
|
|
_ = async first();
|
|
try expectEqual(pt.x, 10);
|
|
try expectEqual(pt.y, 11);
|
|
resume frame;
|
|
try expectEqual(pt.x, 1);
|
|
try 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,
|
|
};
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "suspension points inside branching control flow" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var result: i32 = 10;
|
|
|
|
fn doTheTest() !void {
|
|
try expect(10 == result);
|
|
var frame = async func(true);
|
|
try expect(10 == result);
|
|
resume frame;
|
|
try expect(11 == result);
|
|
resume frame;
|
|
try expect(12 == result);
|
|
resume frame;
|
|
try expect(13 == result);
|
|
}
|
|
|
|
fn func(b: bool) void {
|
|
while (b) {
|
|
suspend {}
|
|
result += 1;
|
|
}
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "call async function which has struct return type" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
|
|
fn doTheTest() void {
|
|
_ = async atest();
|
|
resume frame;
|
|
}
|
|
|
|
fn atest() void {
|
|
const result = func();
|
|
expect(result.x == 5) catch @panic("test failed");
|
|
expect(result.y == 6) catch @panic("test failed");
|
|
}
|
|
|
|
const Point = struct {
|
|
x: usize,
|
|
y: usize,
|
|
};
|
|
|
|
fn func() Point {
|
|
suspend {
|
|
frame = @frame();
|
|
}
|
|
return Point{
|
|
.x = 5,
|
|
.y = 6,
|
|
};
|
|
}
|
|
};
|
|
S.doTheTest();
|
|
}
|
|
|
|
test "pass string literal to async function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
var ok: bool = false;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async hello("hello");
|
|
resume frame;
|
|
try expect(ok);
|
|
}
|
|
|
|
fn hello(msg: []const u8) void {
|
|
frame = @frame();
|
|
suspend {}
|
|
expectEqualStrings("hello", msg) catch @panic("test failed");
|
|
ok = true;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "await inside an errdefer" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async amainWrap();
|
|
resume frame;
|
|
}
|
|
|
|
fn amainWrap() !void {
|
|
var foo = async func();
|
|
errdefer await foo;
|
|
return error.Bad;
|
|
}
|
|
|
|
fn func() void {
|
|
frame = @frame();
|
|
suspend {}
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "try in an async function with error union and non-zero-bit payload" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
var ok = false;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async amain();
|
|
resume frame;
|
|
try expect(ok);
|
|
}
|
|
|
|
fn amain() void {
|
|
std.testing.expectError(error.Bad, theProblem()) catch @panic("test failed");
|
|
ok = true;
|
|
}
|
|
|
|
fn theProblem() ![]u8 {
|
|
frame = @frame();
|
|
suspend {}
|
|
const result = try other();
|
|
return result;
|
|
}
|
|
|
|
fn other() ![]u8 {
|
|
return error.Bad;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "returning a const error from async function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
var ok = false;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async amain();
|
|
resume frame;
|
|
try expect(ok);
|
|
}
|
|
|
|
fn amain() !void {
|
|
var download_frame = async fetchUrl(10, "a string");
|
|
const download_text = try await download_frame;
|
|
_ = download_text;
|
|
|
|
@panic("should not get here");
|
|
}
|
|
|
|
fn fetchUrl(unused: i32, url: []const u8) ![]u8 {
|
|
_ = unused;
|
|
_ = url;
|
|
frame = @frame();
|
|
suspend {}
|
|
ok = true;
|
|
return error.OutOfMemory;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "async/await typical usage" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
inline for ([_]bool{ false, true }) |b1| {
|
|
inline for ([_]bool{ false, true }) |b2| {
|
|
inline for ([_]bool{ false, true }) |b3| {
|
|
inline for ([_]bool{ false, true }) |b4| {
|
|
testAsyncAwaitTypicalUsage(b1, b2, b3, b4).doTheTest();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn testAsyncAwaitTypicalUsage(
|
|
comptime simulate_fail_download: bool,
|
|
comptime simulate_fail_file: bool,
|
|
comptime suspend_download: bool,
|
|
comptime suspend_file: bool,
|
|
) type {
|
|
return struct {
|
|
fn doTheTest() void {
|
|
_ = async amainWrap();
|
|
if (suspend_file) {
|
|
resume global_file_frame;
|
|
}
|
|
if (suspend_download) {
|
|
resume global_download_frame;
|
|
}
|
|
}
|
|
fn amainWrap() void {
|
|
if (amain()) |_| {
|
|
expect(!simulate_fail_download) catch @panic("test failure");
|
|
expect(!simulate_fail_file) catch @panic("test failure");
|
|
} else |e| switch (e) {
|
|
error.NoResponse => expect(simulate_fail_download) catch @panic("test failure"),
|
|
error.FileNotFound => expect(simulate_fail_file) catch @panic("test failure"),
|
|
else => @panic("test failure"),
|
|
}
|
|
}
|
|
|
|
fn amain() !void {
|
|
const allocator = std.testing.allocator;
|
|
var download_frame = async fetchUrl(allocator, "https://example.com/");
|
|
var download_awaited = false;
|
|
errdefer if (!download_awaited) {
|
|
if (await download_frame) |x| allocator.free(x) else |_| {}
|
|
};
|
|
|
|
var file_frame = async readFile(allocator, "something.txt");
|
|
var file_awaited = false;
|
|
errdefer if (!file_awaited) {
|
|
if (await file_frame) |x| allocator.free(x) else |_| {}
|
|
};
|
|
|
|
download_awaited = true;
|
|
const download_text = try await download_frame;
|
|
defer allocator.free(download_text);
|
|
|
|
file_awaited = true;
|
|
const file_text = try await file_frame;
|
|
defer allocator.free(file_text);
|
|
|
|
try expect(std.mem.eql(u8, "expected download text", download_text));
|
|
try 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 {
|
|
_ = url;
|
|
const result = try allocator.dupe(u8, "expected download text");
|
|
errdefer allocator.free(result);
|
|
if (suspend_download) {
|
|
suspend {
|
|
global_download_frame = @frame();
|
|
}
|
|
}
|
|
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 {
|
|
_ = filename;
|
|
const result = try allocator.dupe(u8, "expected file text");
|
|
errdefer allocator.free(result);
|
|
if (suspend_file) {
|
|
suspend {
|
|
global_file_frame = @frame();
|
|
}
|
|
}
|
|
if (simulate_fail_file) return error.FileNotFound;
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
|
|
test "alignment of local variables in async functions" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn doTheTest() !void {
|
|
var y: u8 = 123;
|
|
_ = y;
|
|
var x: u8 align(128) = 1;
|
|
try expect(@ptrToInt(&x) % 128 == 0);
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "no reason to resolve frame still works" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
_ = async simpleNothing();
|
|
}
|
|
fn simpleNothing() void {
|
|
var x: i32 = 1234;
|
|
_ = x;
|
|
}
|
|
|
|
test "async call a generic function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn doTheTest() !void {
|
|
var f = async func(i32, 2);
|
|
const result = await f;
|
|
try expect(result == 3);
|
|
}
|
|
|
|
fn func(comptime T: type, inc: T) T {
|
|
var x: T = 1;
|
|
suspend {
|
|
resume @frame();
|
|
}
|
|
x += inc;
|
|
return x;
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
}
|
|
|
|
test "return from suspend block" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn doTheTest() !void {
|
|
expect(func() == 1234) catch @panic("test failure");
|
|
}
|
|
fn func() i32 {
|
|
suspend {
|
|
return 1234;
|
|
}
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
}
|
|
|
|
test "struct parameter to async function is copied to the frame" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
const Point = struct {
|
|
x: i32,
|
|
y: i32,
|
|
};
|
|
|
|
var frame: anyframe = undefined;
|
|
|
|
fn doTheTest() void {
|
|
_ = async atest();
|
|
resume frame;
|
|
}
|
|
|
|
fn atest() void {
|
|
var f: @Frame(foo) = undefined;
|
|
bar(&f);
|
|
clobberStack(10);
|
|
}
|
|
|
|
fn clobberStack(x: i32) void {
|
|
if (x == 0) return;
|
|
clobberStack(x - 1);
|
|
var y: i32 = x;
|
|
_ = y;
|
|
}
|
|
|
|
fn bar(f: *@Frame(foo)) void {
|
|
var pt = Point{ .x = 1, .y = 2 };
|
|
f.* = async foo(pt);
|
|
var result = await f;
|
|
expect(result == 1) catch @panic("test failure");
|
|
}
|
|
|
|
fn foo(point: Point) i32 {
|
|
suspend {
|
|
frame = @frame();
|
|
}
|
|
return point.x;
|
|
}
|
|
};
|
|
S.doTheTest();
|
|
}
|
|
|
|
test "cast fn to async fn when it is inferred to be async" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
var ok = false;
|
|
|
|
fn doTheTest() void {
|
|
var ptr: fn () callconv(.Async) i32 = undefined;
|
|
ptr = func;
|
|
var buf: [100]u8 align(16) = undefined;
|
|
var result: i32 = undefined;
|
|
const f = @asyncCall(&buf, &result, ptr, .{});
|
|
_ = await f;
|
|
expect(result == 1234) catch @panic("test failure");
|
|
ok = true;
|
|
}
|
|
|
|
fn func() i32 {
|
|
suspend {
|
|
frame = @frame();
|
|
}
|
|
return 1234;
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
resume S.frame;
|
|
try expect(S.ok);
|
|
}
|
|
|
|
test "cast fn to async fn when it is inferred to be async, awaited directly" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var frame: anyframe = undefined;
|
|
var ok = false;
|
|
|
|
fn doTheTest() void {
|
|
var ptr: fn () callconv(.Async) i32 = undefined;
|
|
ptr = func;
|
|
var buf: [100]u8 align(16) = undefined;
|
|
var result: i32 = undefined;
|
|
_ = await @asyncCall(&buf, &result, ptr, .{});
|
|
expect(result == 1234) catch @panic("test failure");
|
|
ok = true;
|
|
}
|
|
|
|
fn func() i32 {
|
|
suspend {
|
|
frame = @frame();
|
|
}
|
|
return 1234;
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
resume S.frame;
|
|
try expect(S.ok);
|
|
}
|
|
|
|
test "await does not force async if callee is blocking" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn simple() i32 {
|
|
return 1234;
|
|
}
|
|
};
|
|
var x = async S.simple();
|
|
try expect(await x == 1234);
|
|
}
|
|
|
|
test "recursive async function" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
try expect(recursiveAsyncFunctionTest(false).doTheTest() == 55);
|
|
try expect(recursiveAsyncFunctionTest(true).doTheTest() == 55);
|
|
}
|
|
|
|
fn recursiveAsyncFunctionTest(comptime suspending_implementation: bool) type {
|
|
return struct {
|
|
fn fib(allocator: std.mem.Allocator, x: u32) error{OutOfMemory}!u32 {
|
|
if (x <= 1) return x;
|
|
|
|
if (suspending_implementation) {
|
|
suspend {
|
|
resume @frame();
|
|
}
|
|
}
|
|
|
|
const f1 = try allocator.create(@Frame(fib));
|
|
defer allocator.destroy(f1);
|
|
|
|
const f2 = try allocator.create(@Frame(fib));
|
|
defer allocator.destroy(f2);
|
|
|
|
f1.* = async fib(allocator, x - 1);
|
|
var f1_awaited = false;
|
|
errdefer if (!f1_awaited) {
|
|
_ = await f1;
|
|
};
|
|
|
|
f2.* = async fib(allocator, x - 2);
|
|
var f2_awaited = false;
|
|
errdefer if (!f2_awaited) {
|
|
_ = await f2;
|
|
};
|
|
|
|
var sum: u32 = 0;
|
|
|
|
f1_awaited = true;
|
|
sum += try await f1;
|
|
|
|
f2_awaited = true;
|
|
sum += try await f2;
|
|
|
|
return sum;
|
|
}
|
|
|
|
fn doTheTest() u32 {
|
|
if (suspending_implementation) {
|
|
var result: u32 = undefined;
|
|
_ = async amain(&result);
|
|
return result;
|
|
} else {
|
|
return fib(std.testing.allocator, 10) catch unreachable;
|
|
}
|
|
}
|
|
|
|
fn amain(result: *u32) void {
|
|
var x = async fib(std.testing.allocator, 10);
|
|
result.* = (await x) catch unreachable;
|
|
}
|
|
};
|
|
}
|
|
|
|
test "@asyncCall with comptime-known function, but not awaited directly" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
var frame: [1]@Frame(middle) = undefined;
|
|
var result: @typeInfo(@typeInfo(@TypeOf(middle)).Fn.return_type.?).ErrorUnion.error_set!void = undefined;
|
|
_ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, middle, .{});
|
|
resume global_frame;
|
|
try std.testing.expectError(error.Fail, result);
|
|
}
|
|
fn middle() callconv(.Async) !void {
|
|
var f = async middle2();
|
|
return await f;
|
|
}
|
|
|
|
fn middle2() !void {
|
|
return failing();
|
|
}
|
|
|
|
fn failing() !void {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
return error.Fail;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "@asyncCall with actual frame instead of byte buffer" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn func() i32 {
|
|
suspend {}
|
|
return 1234;
|
|
}
|
|
};
|
|
var frame: @Frame(S.func) = undefined;
|
|
var result: i32 = undefined;
|
|
const ptr = @asyncCall(&frame, &result, S.func, .{});
|
|
resume ptr;
|
|
try expect(result == 1234);
|
|
}
|
|
|
|
test "@asyncCall using the result location inside the frame" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn simple2(y: *i32) callconv(.Async) i32 {
|
|
defer y.* += 2;
|
|
y.* += 1;
|
|
suspend {}
|
|
return 1234;
|
|
}
|
|
fn getAnswer(f: anyframe->i32, out: *i32) void {
|
|
out.* = await f;
|
|
}
|
|
};
|
|
var data: i32 = 1;
|
|
const Foo = struct {
|
|
bar: fn (*i32) callconv(.Async) i32,
|
|
};
|
|
var foo = Foo{ .bar = S.simple2 };
|
|
var bytes: [64]u8 align(16) = undefined;
|
|
const f = @asyncCall(&bytes, {}, foo.bar, .{&data});
|
|
comptime try expect(@TypeOf(f) == anyframe->i32);
|
|
try expect(data == 2);
|
|
resume f;
|
|
try expect(data == 4);
|
|
_ = async S.getAnswer(f, &data);
|
|
try expect(data == 1234);
|
|
}
|
|
|
|
test "@TypeOf an async function call of generic fn with error union type" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn func(comptime x: anytype) anyerror!i32 {
|
|
const T = @TypeOf(async func(x));
|
|
comptime try expect(T == @typeInfo(@TypeOf(@frame())).Pointer.child);
|
|
return undefined;
|
|
}
|
|
};
|
|
_ = async S.func(i32);
|
|
}
|
|
|
|
test "using @TypeOf on a generic function call" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_ok = false;
|
|
|
|
var buf: [100]u8 align(16) = undefined;
|
|
|
|
fn amain(x: anytype) void {
|
|
if (x == 0) {
|
|
global_ok = true;
|
|
return;
|
|
}
|
|
suspend {
|
|
global_frame = @frame();
|
|
}
|
|
const F = @TypeOf(async amain(x - 1));
|
|
const frame = @intToPtr(*F, @ptrToInt(&buf));
|
|
return await @asyncCall(frame, {}, amain, .{x - 1});
|
|
}
|
|
};
|
|
_ = async S.amain(@as(u32, 1));
|
|
resume S.global_frame;
|
|
try expect(S.global_ok);
|
|
}
|
|
|
|
test "recursive call of await @asyncCall with struct return type" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_ok = false;
|
|
|
|
var buf: [100]u8 align(16) = undefined;
|
|
|
|
fn amain(x: anytype) Foo {
|
|
if (x == 0) {
|
|
global_ok = true;
|
|
return Foo{ .x = 1, .y = 2, .z = 3 };
|
|
}
|
|
suspend {
|
|
global_frame = @frame();
|
|
}
|
|
const F = @TypeOf(async amain(x - 1));
|
|
const frame = @intToPtr(*F, @ptrToInt(&buf));
|
|
return await @asyncCall(frame, {}, amain, .{x - 1});
|
|
}
|
|
|
|
const Foo = struct {
|
|
x: u64,
|
|
y: u64,
|
|
z: u64,
|
|
};
|
|
};
|
|
var res: S.Foo = undefined;
|
|
var frame: @TypeOf(async S.amain(@as(u32, 1))) = undefined;
|
|
_ = @asyncCall(&frame, &res, S.amain, .{@as(u32, 1)});
|
|
resume S.global_frame;
|
|
try expect(S.global_ok);
|
|
try expect(res.x == 1);
|
|
try expect(res.y == 2);
|
|
try expect(res.z == 3);
|
|
}
|
|
|
|
test "nosuspend function call" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn doTheTest() !void {
|
|
const result = nosuspend add(50, 100);
|
|
try expect(result == 150);
|
|
}
|
|
fn add(a: i32, b: i32) i32 {
|
|
if (a > 100) {
|
|
suspend {}
|
|
}
|
|
return a + b;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "await used in expression and awaiting fn with no suspend but async calling convention" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn atest() void {
|
|
var f1 = async add(1, 2);
|
|
var f2 = async add(3, 4);
|
|
|
|
const sum = (await f1) + (await f2);
|
|
expect(sum == 10) catch @panic("test failure");
|
|
}
|
|
fn add(a: i32, b: i32) callconv(.Async) i32 {
|
|
return a + b;
|
|
}
|
|
};
|
|
_ = async S.atest();
|
|
}
|
|
|
|
test "await used in expression after a fn call" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn atest() void {
|
|
var f1 = async add(3, 4);
|
|
var sum: i32 = 0;
|
|
sum = foo() + await f1;
|
|
expect(sum == 8) catch @panic("test failure");
|
|
}
|
|
fn add(a: i32, b: i32) callconv(.Async) i32 {
|
|
return a + b;
|
|
}
|
|
fn foo() i32 {
|
|
return 1;
|
|
}
|
|
};
|
|
_ = async S.atest();
|
|
}
|
|
|
|
test "async fn call used in expression after a fn call" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn atest() void {
|
|
var sum: i32 = 0;
|
|
sum = foo() + add(3, 4);
|
|
expect(sum == 8) catch @panic("test failure");
|
|
}
|
|
fn add(a: i32, b: i32) callconv(.Async) i32 {
|
|
return a + b;
|
|
}
|
|
fn foo() i32 {
|
|
return 1;
|
|
}
|
|
};
|
|
_ = async S.atest();
|
|
}
|
|
|
|
test "suspend in for loop" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: ?anyframe = null;
|
|
|
|
fn doTheTest() void {
|
|
_ = async atest();
|
|
while (global_frame) |f| resume f;
|
|
}
|
|
|
|
fn atest() void {
|
|
expect(func(&[_]u8{ 1, 2, 3 }) == 6) catch @panic("test failure");
|
|
}
|
|
fn func(stuff: []const u8) u32 {
|
|
global_frame = @frame();
|
|
var sum: u32 = 0;
|
|
for (stuff) |x| {
|
|
suspend {}
|
|
sum += x;
|
|
}
|
|
global_frame = null;
|
|
return sum;
|
|
}
|
|
};
|
|
S.doTheTest();
|
|
}
|
|
|
|
test "suspend in while loop" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: ?anyframe = null;
|
|
|
|
fn doTheTest() void {
|
|
_ = async atest();
|
|
while (global_frame) |f| resume f;
|
|
}
|
|
|
|
fn atest() void {
|
|
expect(optional(6) == 6) catch @panic("test failure");
|
|
expect(errunion(6) == 6) catch @panic("test failure");
|
|
}
|
|
fn optional(stuff: ?u32) u32 {
|
|
global_frame = @frame();
|
|
defer global_frame = null;
|
|
while (stuff) |val| {
|
|
suspend {}
|
|
return val;
|
|
}
|
|
return 0;
|
|
}
|
|
fn errunion(stuff: anyerror!u32) u32 {
|
|
global_frame = @frame();
|
|
defer global_frame = null;
|
|
while (stuff) |val| {
|
|
suspend {}
|
|
return val;
|
|
} else |err| {
|
|
err catch {};
|
|
return 0;
|
|
}
|
|
}
|
|
};
|
|
S.doTheTest();
|
|
}
|
|
|
|
test "correctly spill when returning the error union result of another async fn" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
expect((atest() catch unreachable) == 1234) catch @panic("test failure");
|
|
}
|
|
|
|
fn atest() !i32 {
|
|
return fallible1();
|
|
}
|
|
|
|
fn fallible1() anyerror!i32 {
|
|
suspend {
|
|
global_frame = @frame();
|
|
}
|
|
return 1234;
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
resume S.global_frame;
|
|
}
|
|
|
|
test "spill target expr in a for loop" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
var foo = Foo{
|
|
.slice = &[_]i32{ 1, 2 },
|
|
};
|
|
expect(atest(&foo) == 3) catch @panic("test failure");
|
|
}
|
|
|
|
const Foo = struct {
|
|
slice: []const i32,
|
|
};
|
|
|
|
fn atest(foo: *Foo) i32 {
|
|
var sum: i32 = 0;
|
|
for (foo.slice) |x| {
|
|
suspend {
|
|
global_frame = @frame();
|
|
}
|
|
sum += x;
|
|
}
|
|
return sum;
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
resume S.global_frame;
|
|
resume S.global_frame;
|
|
}
|
|
|
|
test "spill target expr in a for loop, with a var decl in the loop body" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
|
|
fn doTheTest() !void {
|
|
var foo = Foo{
|
|
.slice = &[_]i32{ 1, 2 },
|
|
};
|
|
expect(atest(&foo) == 3) catch @panic("test failure");
|
|
}
|
|
|
|
const Foo = struct {
|
|
slice: []const i32,
|
|
};
|
|
|
|
fn atest(foo: *Foo) i32 {
|
|
var sum: i32 = 0;
|
|
for (foo.slice) |x| {
|
|
// Previously this var decl would prevent spills. This test makes sure
|
|
// the for loop spills still happen even though there is a VarDecl in scope
|
|
// before the suspend.
|
|
var anything = true;
|
|
_ = anything;
|
|
suspend {
|
|
global_frame = @frame();
|
|
}
|
|
sum += x;
|
|
}
|
|
return sum;
|
|
}
|
|
};
|
|
_ = async S.doTheTest();
|
|
resume S.global_frame;
|
|
resume S.global_frame;
|
|
}
|
|
|
|
test "async call with @call" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
fn doTheTest() void {
|
|
_ = @call(.{ .modifier = .async_kw }, atest, .{});
|
|
resume global_frame;
|
|
}
|
|
fn atest() void {
|
|
var frame = @call(.{ .modifier = .async_kw }, afoo, .{});
|
|
const res = await frame;
|
|
expect(res == 42) catch @panic("test failure");
|
|
}
|
|
fn afoo() i32 {
|
|
suspend {
|
|
global_frame = @frame();
|
|
}
|
|
return 42;
|
|
}
|
|
};
|
|
S.doTheTest();
|
|
}
|
|
|
|
test "async function passed 0-bit arg after non-0-bit arg" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_int: i32 = 0;
|
|
|
|
fn foo() void {
|
|
bar(1, .{}) catch unreachable;
|
|
}
|
|
|
|
fn bar(x: i32, args: anytype) anyerror!void {
|
|
_ = args;
|
|
global_frame = @frame();
|
|
suspend {}
|
|
global_int = x;
|
|
}
|
|
};
|
|
_ = async S.foo();
|
|
resume S.global_frame;
|
|
try expect(S.global_int == 1);
|
|
}
|
|
|
|
test "async function passed align(16) arg after align(8) arg" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_int: u128 = 0;
|
|
|
|
fn foo() void {
|
|
var a: u128 = 99;
|
|
bar(10, .{a}) catch unreachable;
|
|
}
|
|
|
|
fn bar(x: u64, args: anytype) anyerror!void {
|
|
try expect(x == 10);
|
|
global_frame = @frame();
|
|
suspend {}
|
|
global_int = args[0];
|
|
}
|
|
};
|
|
_ = async S.foo();
|
|
resume S.global_frame;
|
|
try expect(S.global_int == 99);
|
|
}
|
|
|
|
test "async function call resolves target fn frame, comptime func" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_int: i32 = 9;
|
|
|
|
fn foo() anyerror!void {
|
|
const stack_size = 1000;
|
|
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
|
|
return await @asyncCall(&stack_frame, {}, bar, .{});
|
|
}
|
|
|
|
fn bar() anyerror!void {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
global_int += 1;
|
|
}
|
|
};
|
|
_ = async S.foo();
|
|
resume S.global_frame;
|
|
try expect(S.global_int == 10);
|
|
}
|
|
|
|
test "async function call resolves target fn frame, runtime func" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_int: i32 = 9;
|
|
|
|
fn foo() anyerror!void {
|
|
const stack_size = 1000;
|
|
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
|
|
var func: fn () callconv(.Async) anyerror!void = bar;
|
|
return await @asyncCall(&stack_frame, {}, func, .{});
|
|
}
|
|
|
|
fn bar() anyerror!void {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
global_int += 1;
|
|
}
|
|
};
|
|
_ = async S.foo();
|
|
resume S.global_frame;
|
|
try expect(S.global_int == 10);
|
|
}
|
|
|
|
test "properly spill optional payload capture value" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var global_int: usize = 2;
|
|
|
|
fn foo() void {
|
|
var opt: ?usize = 1234;
|
|
if (opt) |x| {
|
|
bar();
|
|
global_int += x;
|
|
}
|
|
}
|
|
|
|
fn bar() void {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
global_int += 1;
|
|
}
|
|
};
|
|
_ = async S.foo();
|
|
resume S.global_frame;
|
|
try expect(S.global_int == 1237);
|
|
}
|
|
|
|
test "handle defer interfering with return value spill" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame1: anyframe = undefined;
|
|
var global_frame2: anyframe = undefined;
|
|
var finished = false;
|
|
var baz_happened = false;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async testFoo();
|
|
resume global_frame1;
|
|
resume global_frame2;
|
|
try expect(baz_happened);
|
|
try expect(finished);
|
|
}
|
|
|
|
fn testFoo() void {
|
|
expectError(error.Bad, foo()) catch @panic("test failure");
|
|
finished = true;
|
|
}
|
|
|
|
fn foo() anyerror!void {
|
|
defer baz();
|
|
return bar() catch |err| return err;
|
|
}
|
|
|
|
fn bar() anyerror!void {
|
|
global_frame1 = @frame();
|
|
suspend {}
|
|
return error.Bad;
|
|
}
|
|
|
|
fn baz() void {
|
|
global_frame2 = @frame();
|
|
suspend {}
|
|
baz_happened = true;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "take address of temporary async frame" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var global_frame: anyframe = undefined;
|
|
var finished = false;
|
|
|
|
fn doTheTest() !void {
|
|
_ = async asyncDoTheTest();
|
|
resume global_frame;
|
|
try expect(finished);
|
|
}
|
|
|
|
fn asyncDoTheTest() void {
|
|
expect(finishIt(&async foo(10)) == 1245) catch @panic("test failure");
|
|
finished = true;
|
|
}
|
|
|
|
fn foo(arg: i32) i32 {
|
|
global_frame = @frame();
|
|
suspend {}
|
|
return arg + 1234;
|
|
}
|
|
|
|
fn finishIt(frame: anyframe->i32) i32 {
|
|
return (await frame) + 1;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
}
|
|
|
|
test "nosuspend await" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var finished = false;
|
|
|
|
fn doTheTest() !void {
|
|
var frame = async foo(false);
|
|
try expect(nosuspend await frame == 42);
|
|
finished = true;
|
|
}
|
|
|
|
fn foo(want_suspend: bool) i32 {
|
|
if (want_suspend) {
|
|
suspend {}
|
|
}
|
|
return 42;
|
|
}
|
|
};
|
|
try S.doTheTest();
|
|
try expect(S.finished);
|
|
}
|
|
|
|
test "nosuspend on function calls" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S0 = struct {
|
|
b: i32 = 42,
|
|
};
|
|
const S1 = struct {
|
|
fn c() S0 {
|
|
return S0{};
|
|
}
|
|
fn d() !S0 {
|
|
return S0{};
|
|
}
|
|
};
|
|
try expectEqual(@as(i32, 42), nosuspend S1.c().b);
|
|
try expectEqual(@as(i32, 42), (try nosuspend S1.d()).b);
|
|
}
|
|
|
|
test "nosuspend on async function calls" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S0 = struct {
|
|
b: i32 = 42,
|
|
};
|
|
const S1 = struct {
|
|
fn c() S0 {
|
|
return S0{};
|
|
}
|
|
fn d() !S0 {
|
|
return S0{};
|
|
}
|
|
};
|
|
var frame_c = nosuspend async S1.c();
|
|
try expectEqual(@as(i32, 42), (await frame_c).b);
|
|
var frame_d = nosuspend async S1.d();
|
|
try expectEqual(@as(i32, 42), (try await frame_d).b);
|
|
}
|
|
|
|
// test "resume nosuspend async function calls" {
|
|
// if (builtin.zig_backend != .stage1) return error.SkipZigTest; // if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
// const S0 = struct {
|
|
// b: i32 = 42,
|
|
// };
|
|
// const S1 = struct {
|
|
// fn c() S0 {
|
|
// suspend {}
|
|
// return S0{};
|
|
// }
|
|
// fn d() !S0 {
|
|
// suspend {}
|
|
// return S0{};
|
|
// }
|
|
// };
|
|
// var frame_c = nosuspend async S1.c();
|
|
// resume frame_c;
|
|
// try expectEqual(@as(i32, 42), (await frame_c).b);
|
|
// var frame_d = nosuspend async S1.d();
|
|
// resume frame_d;
|
|
// try expectEqual(@as(i32, 42), (try await frame_d).b);
|
|
// }
|
|
|
|
test "nosuspend resume async function calls" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S0 = struct {
|
|
b: i32 = 42,
|
|
};
|
|
const S1 = struct {
|
|
fn c() S0 {
|
|
suspend {}
|
|
return S0{};
|
|
}
|
|
fn d() !S0 {
|
|
suspend {}
|
|
return S0{};
|
|
}
|
|
};
|
|
var frame_c = async S1.c();
|
|
nosuspend resume frame_c;
|
|
try expectEqual(@as(i32, 42), (await frame_c).b);
|
|
var frame_d = async S1.d();
|
|
nosuspend resume frame_d;
|
|
try expectEqual(@as(i32, 42), (try await frame_d).b);
|
|
}
|
|
|
|
test "avoid forcing frame alignment resolution implicit cast to *anyopaque" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
var x: ?*anyopaque = null;
|
|
|
|
fn foo() bool {
|
|
suspend {
|
|
x = @frame();
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
var frame = async S.foo();
|
|
resume @ptrCast(anyframe->bool, @alignCast(@alignOf(@Frame(S.foo)), S.x));
|
|
try expect(nosuspend await frame);
|
|
}
|
|
|
|
test "@asyncCall with pass-by-value arguments" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const F0: u64 = 0xbeefbeefbeefbeef;
|
|
const F1: u64 = 0xf00df00df00df00d;
|
|
const F2: u64 = 0xcafecafecafecafe;
|
|
|
|
const S = struct {
|
|
pub const ST = struct { f0: usize, f1: usize };
|
|
pub const AT = [5]u8;
|
|
|
|
pub fn f(_fill0: u64, s: ST, _fill1: u64, a: AT, _fill2: u64) callconv(.Async) void {
|
|
_ = s;
|
|
_ = a;
|
|
// Check that the array and struct arguments passed by value don't
|
|
// end up overflowing the adjacent fields in the frame structure.
|
|
expectEqual(F0, _fill0) catch @panic("test failure");
|
|
expectEqual(F1, _fill1) catch @panic("test failure");
|
|
expectEqual(F2, _fill2) catch @panic("test failure");
|
|
}
|
|
};
|
|
|
|
var buffer: [1024]u8 align(@alignOf(@Frame(S.f))) = undefined;
|
|
// The function pointer must not be comptime-known.
|
|
var t = S.f;
|
|
var frame_ptr = @asyncCall(&buffer, {}, t, .{
|
|
F0,
|
|
.{ .f0 = 1, .f1 = 2 },
|
|
F1,
|
|
[_]u8{ 1, 2, 3, 4, 5 },
|
|
F2,
|
|
});
|
|
_ = frame_ptr;
|
|
}
|
|
|
|
test "@asyncCall with arguments having non-standard alignment" {
|
|
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
|
|
if (builtin.os.tag == .wasi) return error.SkipZigTest; // TODO
|
|
|
|
const F0: u64 = 0xbeefbeef;
|
|
const F1: u64 = 0xf00df00df00df00d;
|
|
|
|
const S = struct {
|
|
pub fn f(_fill0: u32, s: struct { x: u64 align(16) }, _fill1: u64) callconv(.Async) void {
|
|
_ = s;
|
|
// The compiler inserts extra alignment for s, check that the
|
|
// generated code picks the right slot for fill1.
|
|
expectEqual(F0, _fill0) catch @panic("test failure");
|
|
expectEqual(F1, _fill1) catch @panic("test failure");
|
|
}
|
|
};
|
|
|
|
var buffer: [1024]u8 align(@alignOf(@Frame(S.f))) = undefined;
|
|
// The function pointer must not be comptime-known.
|
|
var t = S.f;
|
|
var frame_ptr = @asyncCall(&buffer, {}, t, .{ F0, undefined, F1 });
|
|
_ = frame_ptr;
|
|
}
|