178 lines
5.2 KiB
Lua
178 lines
5.2 KiB
Lua
-- make sure we are pointing to the local copas first
|
|
package.path = string.format("../src/?.lua;%s", package.path)
|
|
|
|
local copas = require "copas"
|
|
local future = copas.future
|
|
|
|
|
|
local test_complete = false
|
|
copas.loop(function()
|
|
|
|
-- get(): waits for task, returns true + results on success (pcall-style)
|
|
do
|
|
local f = future.addthread(function()
|
|
copas.pause(0.05)
|
|
return "hello", "world"
|
|
end)
|
|
local ok, a, b = f:get()
|
|
assert(ok == true, "expected true on success")
|
|
assert(a == "hello" and b == "world", "unexpected results: "..tostring(a)..", "..tostring(b))
|
|
print("ok: get() success")
|
|
end
|
|
|
|
|
|
-- get(): returns false + errmsg when task errors
|
|
do
|
|
local f = future.addthread(function()
|
|
error("boom")
|
|
end)
|
|
local ok, err = f:get()
|
|
assert(ok == false, "expected false on error, got: "..tostring(ok))
|
|
assert(type(err) == "string" and err:find("boom"), "expected 'boom' in error, got: "..tostring(err))
|
|
print("ok: get() error propagation")
|
|
end
|
|
|
|
|
|
-- get() can be called multiple times on the same future
|
|
do
|
|
local f = future.addthread(function() return 42 end)
|
|
local ok1, v1 = f:get()
|
|
local ok2, v2 = f:get()
|
|
assert(ok1 == true and v1 == 42, "first get() failed")
|
|
assert(ok2 == true and v2 == 42, "second get() failed")
|
|
print("ok: get() idempotent")
|
|
end
|
|
|
|
|
|
-- callable future: f(...) is equivalent to f:get(...)
|
|
do
|
|
local f = future.addthread(function() return "via call" end)
|
|
local ok, v = f()
|
|
assert(ok == true and v == "via call", "callable future failed")
|
|
print("ok: future callable via __call")
|
|
end
|
|
|
|
|
|
-- multiple threads waiting on the same future all receive the result
|
|
do
|
|
local f = future.addthread(function()
|
|
copas.pause(0.1)
|
|
return "shared"
|
|
end)
|
|
local results = {}
|
|
for i = 1, 5 do
|
|
local idx = i
|
|
copas.addthread(function()
|
|
local ok, v = f:get()
|
|
results[idx] = ok and v
|
|
end)
|
|
end
|
|
copas.pause(0.3)
|
|
for i = 1, 5 do
|
|
assert(results[i] == "shared", "thread "..i.." did not get result, got: "..tostring(results[i]))
|
|
end
|
|
print("ok: multiple waiters on get()")
|
|
end
|
|
|
|
|
|
-- try(): returns PENDING while task is still running
|
|
do
|
|
local f = future.addthread(function()
|
|
copas.pause(0.2)
|
|
return "done"
|
|
end)
|
|
local status = f:try()
|
|
assert(status == future.PENDING, "expected PENDING, got: "..tostring(status))
|
|
f:get() -- wait for completion
|
|
print("ok: try() PENDING")
|
|
end
|
|
|
|
|
|
-- try(): returns SUCCESS + results after task completes
|
|
do
|
|
local f = future.addthread(function() return 1, 2, 3 end)
|
|
f:get()
|
|
local status, a, b, c = f:try()
|
|
assert(status == future.SUCCESS, "expected SUCCESS, got: "..tostring(status))
|
|
assert(a == 1 and b == 2 and c == 3, "unexpected results from try()")
|
|
print("ok: try() SUCCESS")
|
|
end
|
|
|
|
|
|
-- try(): returns ERROR + errmsg after task errors
|
|
do
|
|
local f = future.addthread(function()
|
|
error("oh no")
|
|
end)
|
|
f:get()
|
|
local status, err = f:try()
|
|
assert(status == future.ERROR, "expected ERROR, got: "..tostring(status))
|
|
assert(type(err) == "string" and err:find("oh no"), "unexpected error message: "..tostring(err))
|
|
print("ok: try() ERROR")
|
|
end
|
|
|
|
|
|
-- cancel(): cancels a pending task, get() returns false + "cancelled"
|
|
do
|
|
local task_ran = false
|
|
local f = future.addthread(function()
|
|
copas.pause(5)
|
|
task_ran = true
|
|
return "should not reach"
|
|
end)
|
|
local cancelled = f:cancel()
|
|
assert(cancelled == true, "expected cancel() to return true")
|
|
local ok, err = f:get()
|
|
assert(ok == false, "expected false after cancel, got: "..tostring(ok))
|
|
assert(err == "cancelled", "expected 'cancelled', got: "..tostring(err))
|
|
copas.pause(0.1)
|
|
assert(not task_ran, "task body should not have run after cancel()")
|
|
print("ok: cancel() pending task")
|
|
end
|
|
|
|
|
|
-- cancel(): try() returns ERROR + "cancelled" after cancel
|
|
do
|
|
local f = future.addthread(function()
|
|
copas.pause(5)
|
|
end)
|
|
f:cancel()
|
|
local status, err = f:try()
|
|
assert(status == future.ERROR, "expected ERROR after cancel, got: "..tostring(status))
|
|
assert(err == "cancelled", "expected 'cancelled', got: "..tostring(err))
|
|
print("ok: try() ERROR after cancel()")
|
|
end
|
|
|
|
|
|
-- cancel(): returns false if task already completed
|
|
do
|
|
local f = future.addthread(function() return "done" end)
|
|
f:get()
|
|
local cancelled = f:cancel()
|
|
assert(cancelled == false, "expected cancel() to return false when already done")
|
|
print("ok: cancel() on completed future returns false")
|
|
end
|
|
|
|
|
|
-- addnamedthread: returns a usable future
|
|
do
|
|
local f = future.addnamedthread("test-task", function() return "named" end)
|
|
local ok, v = f:get()
|
|
assert(ok == true and v == "named", "addnamedthread() failed")
|
|
print("ok: addnamedthread()")
|
|
end
|
|
|
|
|
|
-- typo guard: accessing an unknown field throws a meaningful error
|
|
do
|
|
local ok, err = pcall(function() return future.PNEDING end)
|
|
assert(not ok, "expected an error for unknown field")
|
|
assert(err:find("future%.PNEDING"), "expected field name in error message, got: "..tostring(err))
|
|
print("ok: typo guard on unknown field")
|
|
end
|
|
|
|
|
|
test_complete = true
|
|
end)
|
|
assert(test_complete, "test did not complete!")
|
|
print("test success!")
|