Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,338 @@
|
||||
import context
|
||||
import time
|
||||
import x.async as xasync
|
||||
|
||||
fn test_with_cancel_sets_error_and_closes_done() {
|
||||
mut ctx, cancel := xasync.with_cancel()
|
||||
cancel()
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
assert ctx.err().msg() == 'context canceled'
|
||||
}
|
||||
1 * time.second {
|
||||
assert false, 'cancel did not close the context done channel'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_group_success() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
done := chan int{cap: 2}
|
||||
group.go(fn [done] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
done <- 1
|
||||
})!
|
||||
group.go(fn [done] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
done <- 2
|
||||
})!
|
||||
group.wait()!
|
||||
assert (<-done) + (<-done) == 3
|
||||
}
|
||||
|
||||
fn test_group_error_returns_first_error() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('group failed')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'group failed'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_default_does_not_collect_errors() {
|
||||
mut group := xasync.new_group(context.background())
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('group failed')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'group failed'
|
||||
assert group.errors().len == 0
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_rejects_invalid_error_collection_config() {
|
||||
xasync.new_group_with_config(context.background(), collect_errors: true, max_errors: 0) or {
|
||||
assert err.msg() == 'async: group max_errors must be positive'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_wait_without_tasks() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
group.wait()!
|
||||
}
|
||||
|
||||
fn test_group_refuses_go_after_wait() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
group.wait()!
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
}) or {
|
||||
assert err.msg() == 'async: group does not accept new tasks after wait starts'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_refuses_second_wait() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
group.wait()!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'async: group wait was already called'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_refuses_nil_job() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
nil_job := unsafe { xasync.JobFn(nil) }
|
||||
group.go(nil_job) or {
|
||||
assert err.msg() == 'async: job function is nil'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_cancels_siblings_cooperatively() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
cancelled := chan bool{cap: 1}
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('stop siblings')
|
||||
})!
|
||||
group.go(fn [cancelled] (mut ctx context.Context) ! {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
cancelled <- true
|
||||
}
|
||||
1 * time.second {
|
||||
cancelled <- false
|
||||
}
|
||||
}
|
||||
})!
|
||||
group.wait() or { assert err.msg() == 'stop siblings' }
|
||||
assert <-cancelled
|
||||
}
|
||||
|
||||
fn test_group_first_error_remains_stable_with_concurrent_secondary_errors() {
|
||||
parent := context.background()
|
||||
mut group := xasync.new_group(parent)
|
||||
secondary_ready := chan bool{cap: 8}
|
||||
for _ in 0 .. 8 {
|
||||
group.go(fn [secondary_ready] (mut ctx context.Context) ! {
|
||||
secondary_ready <- true
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return error('secondary error after cancellation')
|
||||
}
|
||||
1 * time.second {
|
||||
return error('secondary error timeout')
|
||||
}
|
||||
}
|
||||
})!
|
||||
}
|
||||
for _ in 0 .. 8 {
|
||||
assert <-secondary_ready
|
||||
}
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('primary error')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'primary error'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_collects_errors_without_changing_wait_error() {
|
||||
mut group := xasync.new_group_with_config(context.background(),
|
||||
collect_errors: true
|
||||
max_errors: 4
|
||||
)!
|
||||
secondary_ready := chan bool{cap: 3}
|
||||
for i in 0 .. 3 {
|
||||
group.go(fn [secondary_ready, i] (mut ctx context.Context) ! {
|
||||
secondary_ready <- true
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return error('secondary collected error ${i}')
|
||||
}
|
||||
1 * time.second {
|
||||
return error('secondary timeout ${i}')
|
||||
}
|
||||
}
|
||||
})!
|
||||
}
|
||||
for _ in 0 .. 3 {
|
||||
assert <-secondary_ready
|
||||
}
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('primary collected error')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'primary collected error'
|
||||
errs := group.errors()
|
||||
assert errs.len == 4
|
||||
assert error_messages_contain(errs, 'primary collected error')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_error_collection_is_bounded_and_includes_first_error() {
|
||||
mut group := xasync.new_group_with_config(context.background(),
|
||||
collect_errors: true
|
||||
max_errors: 2
|
||||
)!
|
||||
secondary_ready := chan bool{cap: 4}
|
||||
for i in 0 .. 4 {
|
||||
group.go(fn [secondary_ready, i] (mut ctx context.Context) ! {
|
||||
secondary_ready <- true
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return error('bounded secondary error ${i}')
|
||||
}
|
||||
1 * time.second {
|
||||
return error('bounded secondary timeout ${i}')
|
||||
}
|
||||
}
|
||||
})!
|
||||
}
|
||||
for _ in 0 .. 4 {
|
||||
assert <-secondary_ready
|
||||
}
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('bounded primary error')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'bounded primary error'
|
||||
errs := group.errors()
|
||||
assert errs.len == 2
|
||||
assert error_messages_contain(errs, 'bounded primary error')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_errors_returns_snapshot_copy() {
|
||||
mut group := xasync.new_group_with_config(context.background(),
|
||||
collect_errors: true
|
||||
max_errors: 1
|
||||
)!
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('snapshot source error')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'snapshot source error'
|
||||
mut snapshot := group.errors()
|
||||
assert snapshot.len == 1
|
||||
snapshot[0] = error('mutated snapshot error')
|
||||
second_snapshot := group.errors()
|
||||
assert second_snapshot.len == 1
|
||||
assert second_snapshot[0].msg() == 'snapshot source error'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_many_short_jobs_return_first_error() {
|
||||
jobs := 64
|
||||
mut group := xasync.new_group(context.background())
|
||||
done := chan int{cap: jobs}
|
||||
for i in 0 .. jobs {
|
||||
group.go(fn [done, i] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
done <- i
|
||||
})!
|
||||
}
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
return error('group stress failure')
|
||||
})!
|
||||
group.wait() or {
|
||||
assert err.msg() == 'group stress failure'
|
||||
mut seen := []bool{len: jobs}
|
||||
for _ in 0 .. jobs {
|
||||
select {
|
||||
i := <-done {
|
||||
assert i >= 0
|
||||
assert i < jobs
|
||||
assert !seen[i]
|
||||
seen[i] = true
|
||||
}
|
||||
1 * time.second {
|
||||
assert false, 'group short job did not finish'
|
||||
}
|
||||
}
|
||||
}
|
||||
for was_seen in seen {
|
||||
assert was_seen
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_group_parent_cancellation_is_observed_by_cooperative_job() {
|
||||
parent_ctx, cancel := xasync.with_cancel()
|
||||
mut group := xasync.new_group(parent_ctx)
|
||||
observed := chan string{cap: 1}
|
||||
group.go(fn [observed] (mut ctx context.Context) ! {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
err := ctx.err()
|
||||
observed <- err.msg()
|
||||
return err
|
||||
}
|
||||
1 * time.second {
|
||||
observed <- 'not canceled'
|
||||
return error('parent cancellation was not observed')
|
||||
}
|
||||
}
|
||||
})!
|
||||
cancel()
|
||||
group.wait() or { assert err.msg() == 'context canceled' }
|
||||
select {
|
||||
msg := <-observed {
|
||||
assert msg == 'context canceled'
|
||||
}
|
||||
2 * time.second {
|
||||
assert false, 'cooperative group job did not observe parent cancellation'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn error_messages_contain(errs []IError, msg string) bool {
|
||||
for err in errs {
|
||||
if err.msg() == msg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user